Giảm thiểu tác động của bản cập nhật định kỳ

Các yêu cầu mà ứng dụng của bạn gửi đến mạng là nguyên nhân chính gây tiêu hao pin vì họ bật đài di động hoặc sóng Wi-Fi tốn điện. Quá sức mạnh cần thiết để gửi và nhận các gói, những sóng vô tuyến này sẽ tốn thêm năng lượng bật và không khoá màn hình. Việc gì đó đơn giản như một yêu cầu mạng cứ 15 giây có thể giữ cho đài phát trên thiết bị di động ở trạng thái bật liên tục và nhanh chóng làm tiêu hao pin nguồn.

Có 3 loại nội dung cập nhật định kỳ chung:

  • Do người dùng yêu cầu. Cập nhật dựa trên một số hành vi của người dùng, chẳng hạn như cử chỉ kéo để làm mới.
  • Do ứng dụng khởi tạo. Thực hiện cập nhật định kỳ.
  • Do máy chủ khởi tạo. Cập nhật theo thông báo từ một máy chủ.

Chủ đề này xem xét từng phương pháp và thảo luận thêm về các cách khác có thể được được tối ưu hoá để giảm hiện tượng tiêu hao pin.

Tối ưu hoá các yêu cầu do người dùng đưa ra

Các yêu cầu do người dùng đưa ra thường xuất hiện để phản hồi một số hành vi của người dùng. Cho Ví dụ: một ứng dụng dùng để đọc những tin bài mới nhất có thể cho phép người dùng thực hiện cử chỉ kéo để làm mới để kiểm tra các bài viết mới. Bạn có thể sử dụng các kỹ thuật sau để phản hồi yêu cầu do người dùng đưa ra, đồng thời tối ưu hoá sử dụng mạng.

Hạn chế yêu cầu của người dùng

Bạn nên bỏ qua một số yêu cầu do người dùng đưa ra nếu không cần thiết chẳng hạn như nhiều cử chỉ kéo để làm mới trong một khoảng thời gian ngắn để kiểm tra dữ liệu mới trong khi dữ liệu hiện tại vẫn còn mới. Hành động trên mỗi có thể làm lãng phí một lượng pin đáng kể khi giữ cho đài ở trạng thái bật. Đáp phương pháp hiệu quả hơn là điều tiết các yêu cầu do người dùng đưa ra để chỉ có thể thực hiện một yêu cầu trong một khoảng thời gian, nhờ đó giảm tần suất sẽ được sử dụng.

Sử dụng bộ nhớ đệm

Bằng cách lưu dữ liệu của ứng dụng vào bộ nhớ đệm, bạn đang tạo một bản sao thông tin trên thiết bị mà ứng dụng của bạn cần tham chiếu. Sau đó, ứng dụng của bạn có thể truy cập vào cùng một sao chép thông tin nhiều lần mà không phải mở mạng để thực hiện các yêu cầu mới.

Bạn nên lưu dữ liệu vào bộ nhớ đệm càng nhiều càng tốt, bao gồm cả dữ liệu tĩnh và các tệp tải xuống theo yêu cầu như hình ảnh với kích thước đầy đủ. Bạn có thể sử dụng HTTP tiêu đề bộ nhớ đệm để đảm bảo rằng chiến lược lưu vào bộ nhớ đệm không làm ứng dụng của bạn hiển thị dữ liệu cũ. Để biết thêm thông tin về cách lưu các phản hồi của mạng vào bộ nhớ đệm, hãy xem Tránh thừa nội dung tải xuống.

Trên Android 11 trở lên, ứng dụng của bạn có thể dùng cùng các tập dữ liệu lớn mà ứng dụng cho các trường hợp sử dụng như học máy và phát nội dung đa phương tiện. Khi ứng dụng cần truy cập vào một tập dữ liệu dùng chung. Trước tiên, ứng dụng này có thể kiểm tra phiên bản đã lưu vào bộ nhớ đệm trước khi thử tải bản sao mới xuống. Để tìm hiểu thêm về tập dữ liệu dùng chung, hãy xem phần Truy cập tập dữ liệu được chia sẻ.

Sử dụng băng thông lớn hơn để tải nhiều dữ liệu xuống ít thường xuyên hơn

Khi được kết nối qua vô tuyến không dây, băng thông cao hơn thường đạt đến chi phí pin cao hơn, nghĩa là 5G thường tốn nhiều năng lượng hơn so với LTE, nhưng mạng này lại đắt hơn 3G.

Điều này có nghĩa là mặc dù trạng thái radio cơ bản thay đổi dựa trên công nghệ vô tuyến, nói chung là mức tác động tương đối của trạng thái đối với pin thời gian sau thay đổi lớn hơn đối với radio băng thông cao hơn. Để biết thêm thông tin về xem Trạng thái đài phát thanh máy tính bảng.

Đồng thời, băng thông cao hơn đồng nghĩa với việc bạn có thể tìm nạp trước được nhiều hơn tải xuống nhiều dữ liệu hơn trong cùng một thời điểm. Có thể ít hơn trực quan, vì chi phí pin tại thời điểm cuối tương đối cao hơn, nên hiệu quả hơn để duy trì đài phát hoạt động lâu hơn trong mỗi lần chuyển đài để giảm tần suất cập nhật.

Ví dụ: nếu một đài LTE có băng thông gấp đôi và chi phí năng lượng tăng gấp đôi của mạng 3G, bạn nên tải dữ liệu xuống nhiều gấp 4 lần trong mỗi phiên—hoặc có thể lên tới 10MB. Khi tải lượng dữ liệu lớn này xuống, bạn phải xem xét tác động của việc tìm nạp trước đối với dung lượng bộ nhớ cục bộ còn trống và kích hoạt thường xuyên tìm nạp trước bộ nhớ đệm.

Bạn có thể sử dụng ConnectivityManager để đăng ký trình nghe cho mạng mặc định và TelephonyManager để đăng ký PhoneStateListener đến xác định loại kết nối hiện tại của thiết bị. Sau khi xác định được loại kết nối, bạn có thể sửa đổi quy trình tìm nạp trước cho phù hợp:

Kotlin

val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val tm = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager

private var hasWifi = false
private var hasCellular = false
private var cellModifier: Float = 1f

private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    // Network capabilities have changed for the network
    override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
    ) {
        super.onCapabilitiesChanged(network, networkCapabilities)
        hasCellular = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
        hasWifi = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
    }
}

private val phoneStateListener = object : PhoneStateListener() {
override fun onPreciseDataConnectionStateChanged(
    dataConnectionState: PreciseDataConnectionState
) {
  cellModifier = when (dataConnectionState.networkType) {
      TelephonyManager.NETWORK_TYPE_LTE or TelephonyManager.NETWORK_TYPE_HSPAP -> 4f
      TelephonyManager.NETWORK_TYPE_EDGE or TelephonyManager.NETWORK_TYPE_GPRS -> 1/2f
      else -> 1f

  }
}

private class NetworkState {
    private var defaultNetwork: Network? = null
    private var defaultCapabilities: NetworkCapabilities? = null
    fun setDefaultNetwork(network: Network?, caps: NetworkCapabilities?) = synchronized(this) {
        defaultNetwork = network
        defaultCapabilities = caps
    }
    val isDefaultNetworkWifi
        get() = synchronized(this) {
            defaultCapabilities?.hasTransport(TRANSPORT_WIFI) ?: false
        }
    val isDefaultNetworkCellular
        get() = synchronized(this) {
            defaultCapabilities?.hasTransport(TRANSPORT_CELLULAR) ?: false
        }
    val isDefaultNetworkUnmetered
        get() = synchronized(this) {
            defaultCapabilities?.hasCapability(NET_CAPABILITY_NOT_METERED) ?: false
        }
    var cellNetworkType: Int = TelephonyManager.NETWORK_TYPE_UNKNOWN
        get() = synchronized(this) { field }
        set(t) = synchronized(this) { field = t }
    private val cellModifier: Float
        get() = synchronized(this) {
            when (cellNetworkType) {
                TelephonyManager.NETWORK_TYPE_LTE or TelephonyManager.NETWORK_TYPE_HSPAP -> 4f
                TelephonyManager.NETWORK_TYPE_EDGE or TelephonyManager.NETWORK_TYPE_GPRS -> 1 / 2f
                else -> 1f
            }
        }
    val prefetchCacheSize: Int
        get() = when {
            isDefaultNetworkWifi -> MAX_PREFETCH_CACHE
            isDefaultNetworkCellular -> (DEFAULT_PREFETCH_CACHE * cellModifier).toInt()
            else -> DEFAULT_PREFETCH_CACHE
        }
}
private val networkState = NetworkState()
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    // Network capabilities have changed for the network
    override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
    ) {
        networkState.setDefaultNetwork(network, networkCapabilities)
    }

    override fun onLost(network: Network?) {
        networkState.setDefaultNetwork(null, null)
    }
}

private val telephonyCallback = object : TelephonyCallback(), TelephonyCallback.PreciseDataConnectionStateListener {
    override fun onPreciseDataConnectionStateChanged(dataConnectionState: PreciseDataConnectionState) {
        networkState.cellNetworkType = dataConnectionState.networkType
    }
}

connectivityManager.registerDefaultNetworkCallback(networkCallback)
telephonyManager.registerTelephonyCallback(telephonyCallback)


private val prefetchCacheSize: Int
get() {
    return when {
        hasWifi -> MAX_PREFETCH_CACHE
        hasCellular -> (DEFAULT_PREFETCH_CACHE * cellModifier).toInt()
        else -> DEFAULT_PREFETCH_CACHE
    }
}

}

Java

ConnectivityManager cm =
 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
TelephonyManager tm =
  (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

private boolean hasWifi = false;
private boolean hasCellular = false;
private float cellModifier = 1f;

private ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onCapabilitiesChanged(
    @NonNull Network network,
    @NonNull NetworkCapabilities networkCapabilities
) {
        super.onCapabilitiesChanged(network, networkCapabilities);
        hasCellular = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
        hasWifi = networkCapabilities
    .hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
}
};

private PhoneStateListener phoneStateListener = new PhoneStateListener() {
@Override
public void onPreciseDataConnectionStateChanged(
    @NonNull PreciseDataConnectionState dataConnectionState
    ) {
    switch (dataConnectionState.getNetworkType()) {
        case (TelephonyManager.NETWORK_TYPE_LTE |
            TelephonyManager.NETWORK_TYPE_HSPAP):
            cellModifier = 4;
            Break;
        case (TelephonyManager.NETWORK_TYPE_EDGE |
            TelephonyManager.NETWORK_TYPE_GPRS):
            cellModifier = 1/2.0f;
            Break;
        default:
            cellModifier = 1;
            Break;
    }
}
};

cm.registerDefaultNetworkCallback(networkCallback);
tm.listen(
phoneStateListener,
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
);

public int getPrefetchCacheSize() {
if (hasWifi) {
    return MAX_PREFETCH_SIZE;
}
if (hasCellular) {
    return (int) (DEFAULT_PREFETCH_SIZE * cellModifier);
    }
return DEFAULT_PREFETCH_SIZE;
}

Tối ưu hoá các yêu cầu do ứng dụng khởi tạo

Các yêu cầu do ứng dụng đưa ra thường diễn ra theo lịch biểu, chẳng hạn như một ứng dụng gửi nhật ký hoặc số liệu phân tích vào dịch vụ phụ trợ. Khi xử lý các trường hợp ứng dụng khởi tạo hãy xem xét mức độ ưu tiên của các yêu cầu đó, liệu chúng có thể được phân theo lô không cũng như liệu chúng có thể bị trì hoãn cho đến khi thiết bị đang sạc hoặc kết nối với mạng không đo lượng dữ liệu. Bạn có thể tối ưu hoá các yêu cầu này bằng sự bằng cách sử dụng các thư viện như WorkManager.

Yêu cầu mạng theo lô

Trên thiết bị di động, quá trình bật đài, tạo kết nối, và để đài ở chế độ bật sẽ tốn một lượng lớn năng lượng. Vì lý do này, việc xử lý từng yêu cầu riêng lẻ tại những thời điểm ngẫu nhiên có thể tốn nhiều năng lượng và giảm thời lượng pin. Một phương pháp hiệu quả hơn là đưa một tập hợp mạng vào hàng đợi yêu cầu và xử lý chúng cùng nhau. Điều này cho phép hệ thống trả điện chỉ cần bật đài một lần mà vẫn nhận được tất cả dữ liệu được yêu cầu ứng dụng.

Sử dụng WorkManager

Bạn có thể sử dụng thư viện WorkManager để thực hiện công việc theo một lịch biểu hiệu quả xem xét liệu có đáp ứng các điều kiện cụ thể hay không, chẳng hạn như khả năng sử dụng mạng và trạng thái nguồn điện. Ví dụ, giả sử bạn có một Đã gọi lớp con Worker DownloadHeadlinesWorker truy xuất các tiêu đề tin tức mới nhất. Trình chạy này có thể được lên lịch chạy mỗi giờ, miễn là thiết bị được kết nối với mạng không đo lượng dữ liệu và pin của thiết bị không yếu, nhờ chiến lược thử lại tuỳ chỉnh nếu có bất kỳ vấn đề nào khi truy xuất dữ liệu, như được hiển thị dưới đây:

Kotlin

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .setRequiresBatteryNotLow(true)
    .build()
val request =
    PeriodicWorkRequestBuilder<DownloadHeadlinesWorker>(1, TimeUnit.HOURS)
        .setConstraints(constraints)
        .setBackoffCriteria(BackoffPolicy.LINEAR, 1L, TimeUnit.MINUTES)
        .build()
WorkManager.getInstance(context).enqueue(request)

Java

Constraints constraints = new Constraints.Builder()
        .setRequiredNetworkType(NetworkType.UNMETERED)
        .setRequiresBatteryNotLow(true)
        .build();
WorkRequest request = new PeriodicWorkRequest.Builder(DownloadHeadlinesWorker.class, 1, TimeUnit.HOURS)
        .setBackoffCriteria(BackoffPolicy.LINEAR, 1L, TimeUnit.MINUTES)
        .build();
WorkManager.getInstance(this).enqueue(request);

Ngoài WorkManager, nền tảng Android còn cung cấp một số công cụ khác giúp bạn tạo lịch biểu hiệu quả để hoàn thành các công việc kết nối mạng, chẳng hạn như làm cuộc thăm dò ý kiến. Để tìm hiểu thêm về cách sử dụng các công cụ này, hãy xem Hướng dẫn xử lý ở chế độ nền.

Tối ưu hoá các yêu cầu do máy chủ khởi tạo

Các yêu cầu do máy chủ đưa ra thường xảy ra để phản hồi một thông báo từ máy chủ. Ví dụ: một ứng dụng dùng để đọc các tin bài mới nhất có thể nhận được thông báo về một loạt bài viết mới phù hợp với các lựa chọn ưu tiên của cá nhân hoá, sau đó hệ thống sẽ tải dữ liệu này xuống.

Gửi bản cập nhật máy chủ bằng Giải pháp gửi thông báo qua đám mây của Firebase

Gửi thông báo qua đám mây của Firebase (FCM) là một ứng dụng cơ chế dùng để truyền dữ liệu từ máy chủ đến một phiên bản ứng dụng cụ thể. Bằng cách sử dụng FCM, máy chủ có thể thông báo cho ứng dụng đang chạy trên một thiết bị cụ thể rằng có dữ liệu mới cho nó.

So với việc thăm dò ý kiến, trong đó ứng dụng của bạn phải thường xuyên ping máy chủ để truy vấn dữ liệu mới, mô hình dựa trên sự kiện này cho phép ứng dụng của bạn tạo kết nối mới chỉ khi biết có dữ liệu để tải xuống. Mô hình giảm thiểu kết nối và giảm độ trễ khi cập nhật thông tin trong ứng dụng.

FCM được triển khai bằng kết nối TCP/IP liên tục. Việc này giúp giảm thiểu số lượng kết nối liên tục và cho phép nền tảng tối ưu hoá băng thông và giảm thiểu tác động liên quan đến thời lượng pin.