Запросы, которые ваше приложение делает в сеть, являются основной причиной разрядки батареи, поскольку они включают энергоемкие сотовые или Wi-Fi-радиостанции. Помимо мощности, необходимой для отправки и получения пакетов, эти радиостанции потребляют дополнительную мощность просто при включении и поддержании бодрствования. Что-то такое простое, как сетевой запрос каждые 15 секунд, может держать мобильную радиостанцию постоянно включенной и быстро расходовать заряд батареи.
Существует три основных типа регулярных обновлений:
- Инициируется пользователем. Выполнение обновления на основе определенного поведения пользователя, например, жеста «потянуть для обновления».
- Инициировано приложением. Выполнение обновления на повторяющейся основе.
- Инициировано сервером. Выполнение обновления в ответ на уведомление с сервера.
В этой теме рассматривается каждый из этих факторов и обсуждаются дополнительные способы их оптимизации для снижения разряда батареи.
Оптимизируйте запросы, инициированные пользователями
Запросы, инициированные пользователем, обычно возникают в ответ на определенное поведение пользователя. Например, приложение, используемое для чтения последних новостных статей, может позволить пользователю выполнить жест «потянуть для обновления», чтобы проверить наличие новых статей. Вы можете использовать следующие методы для ответа на запросы, инициированные пользователем, оптимизируя при этом использование сети.
Регулирование запросов пользователей
Вы можете игнорировать некоторые запросы, инициированные пользователем, если в них нет необходимости, например, несколько жестов «потянуть для обновления» в течение короткого периода времени для проверки новых данных, пока текущие данные еще свежие. Реагирование на каждый запрос может привести к потере значительного количества энергии, удерживая радио в активном состоянии. Более эффективный подход — ограничить запросы, инициированные пользователем, так, чтобы за определенный период времени можно было сделать только один запрос, что снижает частоту использования радио.
Использовать кэш
Кэшируя данные вашего приложения, вы создаете локальную копию информации, на которую должно ссылаться ваше приложение. Затем ваше приложение может получить доступ к одной и той же локальной копии информации несколько раз без необходимости открывать сетевое соединение для выполнения новых запросов.
Вы должны кэшировать данные как можно более агрессивно, включая статические ресурсы и загрузки по запросу, такие как полноразмерные изображения. Вы можете использовать заголовки кэширования HTTP, чтобы гарантировать, что ваша стратегия кэширования не приведет к отображению приложением устаревших данных. Для получения дополнительной информации о кэшировании сетевых ответов см. раздел Избегайте избыточных загрузок .
На Android 11 и выше ваше приложение может использовать те же большие наборы данных, которые используют другие приложения для таких вариантов использования, как машинное обучение и воспроизведение мультимедиа. Когда вашему приложению требуется доступ к общему набору данных, оно может сначала проверить наличие кэшированной версии, прежде чем пытаться загрузить новую копию. Чтобы узнать больше об общих наборах данных, см. Доступ к общим наборам данных .
Используйте большую пропускную способность, чтобы реже загружать больше данных
При подключении по беспроводной радиосвязи более высокая пропускная способность, как правило, достигается за счет более высокой стоимости батареи, а это означает, что 5G обычно потребляет больше энергии, чем LTE, который, в свою очередь, дороже 3G.
Это означает, что хотя базовое состояние радио меняется в зависимости от радиотехнологии, в целом относительное влияние на батарею времени изменения состояния tail-time больше для радио с более высокой пропускной способностью. Для получения дополнительной информации о tail-time см. The radio state machine .
В то же время, более высокая пропускная способность означает, что вы можете выполнять предварительную выборку более агрессивно, загружая больше данных за то же время. Возможно, это менее интуитивно, поскольку стоимость батареи в хвостовом режиме относительно выше, но также более эффективно поддерживать радио активным в течение более длительных периодов во время каждого сеанса передачи, чтобы снизить частоту обновлений.
Например, если радио 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 } } }
Ява
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
, который извлекает последние заголовки новостей. Этот worker может быть запланирован на запуск каждый час, при условии, что устройство подключено к безлимитной сети и батарея устройства не разряжена, с настраиваемой стратегией повтора, если возникнут какие-либо проблемы с извлечением данных, как показано ниже:
Котлин
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)
Ява
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. Это минимизирует количество постоянных соединений и позволяет платформе оптимизировать пропускную способность и минимизировать связанное с этим влияние на срок службы батареи.