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 hao pin vì chúng bật các đài di động hoặc Wi-Fi tiêu thụ điện năng. Ngoài năng lượng cần thiết để gửi và nhận các gói, những đài này còn tiêu thụ thêm năng lượng chỉ để bật và duy trì trạng thái hoạt động. Một thao tác đơn giản như yêu cầu kết nối mạng cứ 15 giây một lần có thể khiến đài di động luôn bật và nhanh chóng tiêu hao pin.
Có 3 loại nội dung cập nhật thường xuyên:
- Do người dùng yêu cầu. Thực hiện một bản 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. Thực hiện một bản cập nhật để phản hồi thông báo từ một máy chủ.
Chủ đề này xem xét từng yếu tố trong số này và thảo luận về những cách khác để tối ưu hoá nhằm giảm mức tiêu thụ pin.
Tối ưu hoá các yêu cầu do người dùng khởi tạo
Yêu cầu do người dùng khởi tạo thường xảy ra để phản hồi một số hành vi của người dùng. Ví dụ: một ứng dụng dùng để đọc các bài viết tin tức 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 nhằm 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 các yêu cầu do người dùng khởi tạo trong khi tối ưu hoá việc sử dụng mạng.
Chặn yêu cầu của người dùng
Bạn có thể muốn bỏ qua một số yêu cầu do người dùng khởi tạo 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. Việc xử lý từng yêu cầu có thể lãng phí một lượng điện năng đáng kể bằng cách duy trì trạng thái hoạt động của đài. Một phương pháp hiệu quả hơn là điều chỉnh các yêu cầu do người dùng bắt đầu để chỉ một yêu cầu có thể được thực hiện trong một khoảng thời gian, giảm tần suất sử dụng đài.
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 cục bộ của thông tin mà ứng dụng cần tham chiếu. Sau đó, ứng dụng của bạn có thể truy cập vào cùng một bản sao cục bộ của thông tin nhiều lần mà không cần mở kết nối mạng để đưa ra các yêu cầu mới.
Bạn nên lưu dữ liệu vào bộ nhớ đệm một cách tích cực nhất có thể, bao gồm cả các tài nguyên tĩnh và nội dung tải xuống theo yêu cầu, chẳng hạn như hình ảnh có kích thước đầy đủ. Bạn có thể sử dụng tiêu đề bộ nhớ đệm HTTP để đảm bảo rằng chiến lược lưu vào bộ nhớ đệm không khiến ứng dụng của bạn hiển thị dữ liệu cũ. Để biết thêm thông tin về việc lưu phản hồi mạng vào bộ nhớ đệm, hãy xem phần Tránh tải xuống dư thừa.
Trên Android 11 trở lên, ứng dụng của bạn có thể sử dụng cùng một tập dữ liệu lớn mà các ứng dụng khác sử dụng cho các trường hợp sử dụng như học máy và phát nội dung nghe nhìn. Khi cần truy cập vào một tập dữ liệu dùng chung, trước tiên, ứng dụng của bạn có thể kiểm tra phiên bản được lưu vào bộ nhớ đệm trước khi tìm cách tải một 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 vào tập dữ liệu dùng chung.
Sử dụng băng thông lớn hơn để tải nhiều dữ liệu xuống hơn nhưng ít thường xuyên hơn
Khi kết nối qua sóng vô tuyến, băng thông càng cao thì mức tiêu hao pin càng lớn. Điều này có nghĩa là 5G thường tiêu thụ nhiều năng lượng hơn LTE, mà LTE lại tiêu thụ nhiều năng lượng hơn 3G.
Điều này có nghĩa là mặc dù trạng thái vô tuyến cơ bản thay đổi tuỳ theo công nghệ vô tuyến, nhưng nói chung, tác động tương đối của pin đối với thời gian đuôi thay đổi trạng thái sẽ lớn hơn đối với các đài có băng thông cao hơn. Để biết thêm thông tin về thời gian chờ, hãy xem phần Máy trạng thái của đài.
Đồng thời, băng thông cao hơn có nghĩa là bạn có thể tìm nạp trước một cách tích cực hơn, tải nhiều dữ liệu hơn trong cùng một khoảng thời gian. Có lẽ ít trực quan hơn, vì chi phí pin trong thời gian chờ tương đối cao hơn, nên việc duy trì trạng thái hoạt động của đài trong thời gian dài hơn trong mỗi phiên truyền cũng hiệu quả hơn để 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 gấp đôi so với 3G, bạn nên tải xuống lượng dữ liệu gấp 4 lần trong mỗi phiên (hoặc có thể lên đến 10 MB). Khi tải nhiều dữ liệu như vậy xuống, bạn cần cân nhắc ảnh hưởng của việc tìm nạp trước đối với bộ nhớ cục bộ có sẵn và thường xuyên xoá bộ nhớ đệm tìm nạp trước.
Bạn có thể dùng ConnectivityManager
để đăng ký một trình nghe cho mạng mặc định và TelephonyManager
để đăng ký một PhoneStateListener
nhằm xác định loại kết nối hiện tại của thiết bị. Sau khi biết loại kết nối, bạn có thể sửa đổi các 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 khởi tạo thường xảy ra theo lịch trình, chẳng hạn như một ứng dụng gửi nhật ký hoặc số liệu phân tích đến một dịch vụ phụ trợ. Khi xử lý các yêu cầu do ứng dụng khởi tạo, hãy cân nhắc mức độ ưu tiên của các yêu cầu đó, liệu chúng có thể được xử lý hàng loạt hay không và liệu chúng có thể bị hoãn lại cho đến khi thiết bị đang sạc hoặc kết nối với một mạng không đo lượng dữ liệu hay không. Bạn có thể tối ưu hoá các yêu cầu này bằng cách lên lịch cẩn thận và 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, thiết lập kết nối và duy trì trạng thái hoạt động của đài tiêu thụ một lượng điện năng lớn. Vì lý do này, việc xử lý các yêu cầu riêng lẻ vào những thời điểm ngẫu nhiên có thể tiêu thụ đáng kể năng lượng và làm giảm thời lượng pin. Một phương pháp hiệu quả hơn là xếp hàng đợi cho một nhóm yêu cầu mạng và xử lý chúng cùng nhau. Điều này cho phép hệ thống chỉ trả chi phí điện năng cho việc bật đài một lần và vẫn nhận được tất cả dữ liệu mà một ứng dụng yêu cầu.
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 trình hiệu quả, có tính đến việc liệu các điều kiện cụ thể có được đáp ứng hay không, chẳng hạn như tình trạng có mạng và trạng thái nguồn. Ví dụ: giả sử bạn có một lớp con Worker
tên là DownloadHeadlinesWorker
, lớp này truy xuất các dòng tiêu đề tin tức mới nhất. Bạn có thể lên lịch chạy worker nà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 ở mức thấp, với một chiến lược thử lại tuỳ chỉnh nếu có vấn đề khi truy xuất dữ liệu, như minh hoạ 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 một lịch biểu hiệu quả để hoàn thành các tác vụ liên quan đến mạng, chẳng hạn như 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ý 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ủ khởi tạo 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 bài viết tin tức 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 cá nhân hoá của người dùng, sau đó ứng dụng sẽ tải loạt bài viết 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
Giải pháp gửi thông báo qua đám mây của Firebase (FCM) là một cơ chế gọn nhẹ dùng để truyền dữ liệu từ máy chủ đến một phiên bản ứng dụng cụ thể. Khi dùng FCM, máy chủ của bạn 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 ứng dụng đó.
So với việc thăm dò (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 chỉ tạo một kết nối mới khi biết có dữ liệu cần tải xuống. Mô hình này giảm thiểu các kết nối không cần thiết và giảm độ trễ khi cập nhật thông tin trong ứng dụng của bạn.
FCM được triển khai bằng cách sử dụng kết nối TCP/IP liên tục. Điều 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, đồng thời giảm thiểu tác động liên quan đến thời lượng pin.