Запросы, которые ваше приложение отправляет в сеть, – основная причина разрядки батареи, поскольку они включают энергоемкие радиомодули сотовой связи или Wi-Fi. Помимо энергии, необходимой для отправки и получения пакетов, эти радиомодули тратят дополнительную энергию на включение и поддержание активности. Даже если сетевой запрос отправляется каждые 15 секунд, мобильный радиомодуль будет работать непрерывно и быстро разрядит батарею.
Существует три основных типа регулярных обновлений:
- Инициируется пользователем. Обновление на основе действий пользователя, например жеста "Потянуть, чтобы обновить".
- Инициированные приложением. Регулярное обновление.
- Инициируется сервером. Обновление в ответ на уведомление от сервера.
В этой статье рассматривается каждый из этих факторов и обсуждаются дополнительные способы их оптимизации для снижения расхода заряда батареи.
Оптимизация запросов, инициированных пользователями
Запросы, инициированные пользователем, обычно выполняются в ответ на его действия. Например, в приложении для чтения новостей пользователь может потянуть экран вниз, чтобы обновить список статей. Чтобы отвечать на запросы пользователей и при этом оптимизировать использование сети, можно применять следующие методы.
Ограничивать запросы пользователей
Вы можете игнорировать некоторые запросы, инициированные пользователем, если в них нет необходимости, например несколько жестов обновления за короткий период времени для проверки новых данных, пока текущие данные ещё актуальны. Если бы устройство реагировало на каждый запрос, оно бы тратило много энергии на поддержание радиомодуля в активном состоянии. Более эффективный подход – ограничить количество запросов, инициируемых пользователем, чтобы за определенный период времени можно было сделать только один запрос. Это позволит снизить частоту использования радиомодуля.
Использование кеша
Кешируя данные приложения, вы создаете локальную копию информации, на которую должно ссылаться ваше приложение. После этого приложение сможет обращаться к локальной копии данных, не устанавливая сетевое подключение для отправки новых запросов.
Кэшируйте как можно больше данных, включая статические ресурсы и скачиваемые по запросу файлы, например полноразмерные изображения. Чтобы в приложении не показывались устаревшие данные, используйте заголовки кеша HTTP. Подробнее о кешировании ответов сети…
В Android 11 и более поздних версиях ваше приложение может использовать те же большие наборы данных, что и другие приложения, например для машинного обучения и воспроизведения медиаконтента. Когда приложению требуется доступ к общему набору данных, оно может сначала проверить наличие кешированной версии, прежде чем пытаться скачать новую копию. Подробнее о доступе к общим наборам данных…
Использовать более высокую пропускную способность, чтобы скачивать больше данных реже
При подключении по беспроводной радиосвязи более высокая пропускная способность обычно достигается за счет более высокого расхода заряда батареи. Это означает, что 5G обычно потребляет больше энергии, чем LTE, который, в свою очередь, потребляет больше энергии, чем 3G.
Это означает, что, хотя базовое состояние радиомодуля зависит от технологии радиосвязи, в целом относительное влияние на батарею времени затухания при изменении состояния больше для радиомодулей с более высокой пропускной способностью. Подробнее о времени задержки…
В то же время более высокая пропускная способность означает, что вы можете выполнять упреждающую выборку более агрессивно, загружая больше данных за то же время. Возможно, менее интуитивно понятно, что, поскольку стоимость батареи в хвостовое время относительно выше, также более эффективно поддерживать радиоактивность в течение более длительных периодов во время каждого сеанса передачи, чтобы снизить частоту обновлений.
Например, если у LTE-модема пропускная способность и энергопотребление в два раза выше, чем у 3G, то за один сеанс нужно скачивать в четыре раза больше данных (или до 10 МБ). При скачивании такого объема данных важно учитывать влияние предварительной выборки на доступное локальное хранилище и регулярно очищать кеш предварительной выборки.
Вы можете использовать ConnectivityManager, чтобы зарегистрировать прослушиватель для сети по умолчанию, и TelephonyManager, чтобы зарегистрировать PhoneStateListener для определения текущего типа подключения устройства. Определив тип подключения, вы можете соответствующим образом изменить процедуры предварительной выборки:
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; }
Оптимизация запросов, инициированных приложением
Запросы, инициированные приложением, обычно выполняются по расписанию. Например, приложение может отправлять журналы или аналитику в бэкенд-службу. При работе с запросами, инициированными приложением, учитывайте их приоритет, возможность их пакетной обработки и возможность их отсрочки до тех пор, пока устройство не будет заряжаться или не будет подключено к сети с нетарифицируемым трафиком. Эти запросы можно оптимизировать, тщательно планируя их и используя такие библиотеки, как WorkManager.
Пакетные сетевые запросы
На мобильном устройстве процесс включения радио, установления соединения и поддержания радио в активном состоянии потребляет большое количество энергии. По этой причине обработка отдельных запросов в случайное время может потреблять значительную мощность и сокращать срок службы батареи. Более эффективный подход – поставить в очередь набор сетевых запросов и обработать их вместе. Это позволяет системе оплачивать затраты на электроэнергию, связанные с включением радио, только один раз и при этом получать все данные, запрашиваемые приложением.
Используйте WorkManager
Вы можете использовать библиотеку WorkManager, чтобы выполнять работу по эффективному расписанию, которое учитывает, выполняются ли определенные условия, такие как доступность сети и состояние питания. Например, у вас есть подкласс Worker с названием DownloadHeadlinesWorker, который получает заголовки последних новостей. Этот рабочий процесс можно запланировать на каждый час, если устройство подключено к безлимитной сети и батарея не разряжена. Также можно настроить специальную стратегию повторных попыток на случай проблем с получением данных, как показано ниже.
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);
Помимо WorkManager, платформа Android предоставляет несколько других инструментов, которые помогут вам создать эффективное расписание для выполнения сетевых задач, таких как опрос. Подробнее об использовании этих инструментов рассказывается в руководстве по фоновой обработке.
Оптимизация запросов, инициированных сервером
Запросы, инициированные сервером, обычно выполняются в ответ на уведомление от сервера. Например, приложение для чтения новостей может получить уведомление о новых статьях, соответствующих предпочтениям пользователя, и скачать их.
Отправка обновлений сервера с помощью Firebase Cloud Messaging
Firebase Cloud Messaging (FCM) – это простой механизм, который используется для передачи данных с сервера в определенный экземпляр приложения. С помощью FCM ваш сервер может уведомить приложение, запущенное на определенном устройстве, о том, что для него доступны новые данные.
В отличие от опроса, когда приложение должно регулярно отправлять запросы на сервер, чтобы получить новые данные, эта модель, управляемая событиями, позволяет приложению создавать новое подключение только тогда, когда оно знает, что есть данные для скачивания. Модель минимизирует ненужные подключения и сокращает задержку при обновлении информации в приложении.
FCM реализуется с помощью постоянного TCP/IP-подключения. Это позволяет минимизировать количество постоянных подключений, оптимизировать пропускную способность и снизить расход заряда батареи.