Requests that your app makes to the network are a major cause of battery drain because they turn on power-consuming cellular or Wi-Fi radios. Beyond the power needed to send and receive packets, these radios expend extra power just turning on and keeping awake. Something as simple as a network request every 15 seconds can keep the mobile radio on continuously and quickly use up battery power.
Существует три основных типа регулярных обновлений:
- Инициируется пользователем. Обновление выполняется на основе действий пользователя, например, жеста «потянуть для обновления».
- Инициировано приложением. Выполняется периодическое обновление.
- Инициировано сервером. Выполняется обновление в ответ на уведомление от сервера.
В этой статье рассматривается каждый из этих аспектов и обсуждаются дополнительные способы их оптимизации для снижения расхода заряда батареи.
Оптимизация запросов, инициированных пользователем.
Запросы, инициированные пользователем, обычно возникают в ответ на какое-либо действие пользователя. Например, приложение для чтения последних новостных статей может позволять пользователю выполнять жест обновления страницы, чтобы проверить наличие новых статей. Для оптимизации использования сети и реагирования на запросы, инициированные пользователем, можно использовать следующие методы.
Ограничение запросов пользователей
You may want to disregard some user-initiated requests if there is no need for them, such as multiple pull-to-refresh gestures over a short period of time to check for new data while the current data is still fresh. Acting on each request could waste a significant amount of power by keeping the radio awake. A more efficient approach is to throttle the user-initiated requests so that only one request can be made over a period of time, reducing how often the radio is used.
Используйте кэш
Кэширование данных вашего приложения создает локальную копию информации, к которой приложению необходимо обращаться. Затем приложение может многократно обращаться к одной и той же локальной копии информации, не открывая сетевое соединение для выполнения новых запросов.
Следует максимально активно кэшировать данные, включая статические ресурсы и загрузки по запросу, такие как изображения в полном размере. Для обеспечения того, чтобы ваша стратегия кэширования не приводила к отображению устаревших данных в приложении, можно использовать заголовки HTTP-кэширования. Дополнительную информацию о кэшировании сетевых ответов см. в разделе «Избегайте избыточных загрузок» .
На Android 11 и выше ваше приложение может использовать те же большие наборы данных, что и другие приложения, для таких задач, как машинное обучение и воспроизведение мультимедиа. Когда вашему приложению потребуется доступ к общему набору данных, оно может сначала проверить наличие кэшированной версии, прежде чем пытаться загрузить новую копию. Чтобы узнать больше об общих наборах данных, см. раздел «Доступ к общим наборам данных» .
Используйте большую пропускную способность, чтобы загружать больше данных реже.
При использовании беспроводной связи более высокая пропускная способность обычно достигается за счет более высокого расхода заряда батареи, а это значит, что 5G, как правило, потребляет больше энергии, чем LTE, который, в свою очередь, дороже, чем 3G.
Это означает, что хотя базовое состояние радиомодуля варьируется в зависимости от используемой радиотехнологии, в целом, относительное влияние времени задержки изменения состояния на расход заряда батареи больше для радиомодулей с более высокой пропускной способностью. Для получения дополнительной информации о времени задержки см. раздел «Конечный автомат радиомодуля» .
В то же время, более высокая пропускная способность позволяет более активно осуществлять предварительную загрузку, загружая больше данных за то же время. Возможно, менее очевидно, что из-за относительно более высокой стоимости батареи в режиме ожидания, также эффективнее поддерживать радиоактивность в течение более длительных периодов во время каждой сессии передачи, чтобы уменьшить частоту обновлений.
Например, если LTE-модуль имеет вдвое большую пропускную способность и вдвое большее энергопотребление, чем 3G, вам следует загружать в четыре раза больше данных за каждый сеанс — или потенциально до 10 МБ. При загрузке такого объема данных важно учитывать влияние предварительной загрузки на доступное локальное хранилище и регулярно очищать кэш предварительной загрузки.
Вы можете использовать ConnectivityManager для регистрации прослушивателя для сети по умолчанию, а TelephonyManager для регистрации PhoneStateListener , определяющего текущий тип подключения устройства. После того, как тип подключения станет известен, вы можете соответствующим образом изменить свои процедуры предварительной выборки:
Котлин
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; }
Оптимизация запросов, инициированных приложением.
App-initiated requests typically occur on a schedule, such as an app that sends logs or analytics to a backend service. When dealing with app-initiated requests, consider the priority of those requests, whether they can be batched together, and whether they can be deferred until the device is charging or connected to an unmetered network. These requests can be optimized with careful scheduling and by using libraries such as WorkManager .
Пакетные сетевые запросы
On a mobile device, the process of turning on the radio, making a connection, and keeping the radio awake uses a large amount of power. For this reason, processing individual requests at random times can consume significant power and reduce battery life. A more efficient approach is to queue a set of network requests and process them together. This allows the system to pay the power cost of turning on the radio just once, and still get all the data requested by an app.
Используйте WorkManager
You can use the WorkManager library to perform work on an efficient schedule that considers whether specific conditions are met, such as network availability and power status. For example, suppose you have a Worker subclass called DownloadHeadlinesWorker that retrieves the latest news headlines. This worker can be scheduled to run every hour, provided the device is connected to an unmetered network and device's battery isn't low, with a custom retry strategy if there are any problems retrieving the data, as shown below:
Котлин
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);
Помимо WorkManager, платформа Android предоставляет ряд других инструментов, которые помогут вам создать эффективное расписание для выполнения сетевых задач, таких как опрос. Чтобы узнать больше об использовании этих инструментов, см. Руководство по фоновой обработке .
Оптимизация запросов, инициированных сервером.
Запросы, инициированные сервером, обычно происходят в ответ на уведомление от сервера. Например, приложение для чтения последних новостных статей может получить уведомление о новой партии статей, соответствующих персонализированным настройкам пользователя, после чего оно их загружает.
Отправляйте обновления сервера с помощью Firebase Cloud Messaging.
Firebase Cloud Messaging (FCM) — это легковесный механизм, используемый для передачи данных с сервера на конкретный экземпляр приложения. С помощью FCM ваш сервер может уведомлять ваше приложение, работающее на конкретном устройстве, о наличии новых доступных данных.
В отличие от метода опроса, при котором вашему приложению необходимо регулярно отправлять запросы на сервер для получения новых данных, эта событийно-ориентированная модель позволяет вашему приложению создавать новое соединение только тогда, когда оно знает, что есть данные для загрузки. Модель минимизирует ненужные соединения и уменьшает задержку при обновлении информации внутри вашего приложения.
В FCM используется постоянное TCP/IP-соединение. Это минимизирует количество постоянных соединений и позволяет платформе оптимизировать пропускную способность, а также минимизировать связанное с этим влияние на время работы от батареи.