Минимизируйте эффект от регулярных обновлений

Запросы, которые ваше приложение отправляет в сеть, являются основной причиной разряда батареи, поскольку они включают энергоемкие сотовые сети или радиомодули Wi-Fi. Помимо мощности, необходимой для отправки и получения пакетов, эти радиостанции расходуют дополнительную энергию, просто включившись и не отключаясь. Такая простая вещь, как сетевой запрос каждые 15 секунд, может поддерживать мобильную радиосвязь непрерывной и быстро расходовать заряд батареи.

Существует три основных типа регулярных обновлений:

  • Инициируется пользователем. Выполнение обновления на основе определенного поведения пользователя, например жеста обновления.
  • Инициируется приложением. Выполнение обновления на регулярной основе.
  • Инициируется сервером. Выполнение обновления в ответ на уведомление с сервера.

В этом разделе рассматривается каждый из них и обсуждаются дополнительные способы их оптимизации для уменьшения расхода заряда батареи.

Оптимизация запросов, инициируемых пользователями

Запросы, инициированные пользователем, обычно возникают в ответ на определенное поведение пользователя. Например, приложение, используемое для чтения последних новостных статей, может позволять пользователю выполнять жест обновления для проверки наличия новых статей. Вы можете использовать следующие методы для ответа на запросы, инициированные пользователем, при оптимизации использования сети.

Регулирование запросов пользователей

Возможно, вы захотите игнорировать некоторые запросы, инициированные пользователем, если в них нет необходимости, например, несколько жестов обновления в течение короткого периода времени для проверки наличия новых данных, пока текущие данные еще свежие. Выполнение каждого запроса может привести к потере значительного количества энергии из-за того, что радиостанция не будет работать. Более эффективный подход — ограничить количество запросов, инициируемых пользователем, чтобы за определенный период времени можно было сделать только один запрос, что снижает частоту использования радиосвязи.

Используйте кеш

Кэшируя данные вашего приложения, вы создаете локальную копию информации, на которую ваше приложение должно ссылаться. Затем ваше приложение сможет получить доступ к одной и той же локальной копии информации несколько раз без необходимости открывать сетевое соединение для выполнения новых запросов.

Вам следует кэшировать данные как можно более агрессивно, включая статические ресурсы и загрузки по требованию, например полноразмерные изображения. Вы можете использовать заголовки кэша 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
    }
}

}

Ява

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 , который извлекает заголовки последних новостей. Этот рабочий процесс можно запланировать для запуска каждый час при условии, что устройство подключено к сети без ограничений и батарея устройства не разряжена, с настраиваемой стратегией повтора, если есть какие-либо проблемы с получением данных, как показано ниже:

Котлин

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. Это сводит к минимуму количество постоянных подключений и позволяет платформе оптимизировать пропускную способность и минимизировать связанное с этим влияние на срок службы батареи.