Minimizar o efeito de atualizações regulares

As solicitações que seu app faz à rede são uma causa importante de consumo da bateria, porque ativam rádios celulares ou Wi-Fi que consomem energia. Além da energia necessária para enviar e receber pacotes, esses rádios gastam energia extra apenas ligando e mantendo ativado. Algo simples como uma solicitação de rede a cada 15 segundos pode manter o rádio móvel ligado de forma contínua e usar a bateria rapidamente.

Existem três tipos gerais de atualizações regulares:

  • Iniciado pelo usuário. Realização de uma atualização com base em algum comportamento do usuário, como um gesto de puxar para atualizar.
  • Iniciado pelo app. Fazer atualizações de forma recorrente.
  • Iniciado pelo servidor. Realizar uma atualização em resposta a uma notificação de um servidor.

Este tópico analisa cada um deles e discute maneiras adicionais de otimizá-los para reduzir o consumo de bateria.

Otimizar solicitações iniciadas pelo usuário

As solicitações iniciadas pelo usuário geralmente ocorrem em resposta a algum comportamento do usuário. Por exemplo, um app usado para ler as notícias mais recentes pode permitir que o usuário faça um gesto de puxar a tela de cima para baixo para atualizar e conferir se há novas matérias. Use as técnicas a seguir para responder a solicitações iniciadas pelo usuário enquanto otimiza o uso da rede.

limitar as solicitações dos usuários;

Desconsidere algumas solicitações iniciadas pelo usuário se elas não forem necessárias, como vários gestos de arrastar para atualizar em um curto período, para verificar se há novos dados enquanto os dados atuais ainda estão atualizados. Atuar em cada solicitação pode desperdiçar uma quantidade significativa de energia mantendo o rádio ativado. Uma abordagem mais eficiente é limitar as solicitações iniciadas pelo usuário para que apenas uma solicitação possa ser feita durante um período, reduzindo a frequência de uso do rádio.

Usar um cache

Ao armazenar os dados do seu app em cache, você cria uma cópia local das informações que ele precisa referenciar. Assim, o app poderá acessar a mesma cópia local das informações várias vezes sem precisar abrir uma conexão de rede para fazer novas solicitações.

É preciso armazenar os dados em cache da maneira mais agressiva possível, incluindo recursos estáticos e downloads sob demanda, como imagens em tamanho original. É possível usar cabeçalhos de cache HTTP para garantir que sua estratégia de armazenamento em cache não resulte na exibição de dados desatualizados pelo app. Para mais informações sobre como armazenar respostas de rede em cache, consulte Evitar downloads redundantes.

No Android 11 e versões mais recentes, o app pode usar os mesmos grandes conjuntos de dados que outros apps usam para casos de uso, como aprendizado de máquina e reprodução de mídia. Quando o app precisa acessar um conjunto de dados compartilhado, ele pode primeiro verificar se há uma versão em cache antes de tentar fazer o download de uma nova cópia. Para saber mais sobre conjuntos de dados compartilhados, consulte Acessar conjuntos de dados compartilhados.

Usar uma maior largura de banda para fazer o download de mais dados com menos frequência

Quando conectado por um rádio sem fio, uma largura de banda maior geralmente tem um custo de bateria maior, o que significa que o 5G geralmente consome mais energia do que o LTE, que é mais caro que o 3G.

Isso significa que, embora o estado de rádio subjacente varie de acordo com a tecnologia de rádio, em geral, o impacto relativo na bateria do tempo de cauda da mudança de estado é maior para rádios com largura de banda maior. Para mais informações sobre tempo de cauda, consulte A máquina de estado de rádio (link em inglês).

Ao mesmo tempo, a maior largura de banda possibilita uma pré-busca mais agressiva, fazendo o download de mais dados de uma só vez. Talvez de forma menos intuitiva, já que o custo da bateria no tempo de cauda é relativamente maior, também é mais eficiente manter o rádio ativo por períodos mais longos durante cada sessão de transferência para reduzir a frequência das atualizações.

Por exemplo, se um rádio LTE tiver o dobro da largura de banda e o dobro do custo de energia do 3G, você precisará fazer o download de quatro vezes mais dados durante cada sessão, ou possivelmente até 10 MB. Ao fazer o download de tantos dados, é importante considerar o efeito da pré-busca no armazenamento local disponível e limpar o cache de pré-busca regularmente.

Você pode usar o ConnectivityManager para registrar um listener para a rede padrão, e o TelephonyManager para registrar um PhoneStateListener e determinar o tipo de conexão atual do dispositivo. Depois que o tipo de conexão for conhecido, será possível modificar suas rotinas de pré-busca conforme necessário:

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;
}

Otimizar solicitações iniciadas pelo app

As solicitações iniciadas pelo app geralmente ocorrem com base em uma programação, como um app que envia registros ou análises para um serviço de back-end. Ao lidar com solicitações iniciadas pelo app, considere a prioridade delas, se elas podem ser agrupadas e se podem ser adiadas até que o dispositivo esteja carregando ou conectado a uma rede ilimitada. Essas solicitações podem ser otimizadas com uma programação cuidadosa e usando bibliotecas como o WorkManager.

Solicitações de rede em lote

Em um dispositivo móvel, o processo de ligar o rádio, fazer uma conexão e manter o rádio ativado consome uma grande quantidade de energia. Por esse motivo, processar solicitações individuais em momentos aleatórios pode consumir energia significativamente e reduzir a duração da bateria. Uma abordagem mais eficiente é enfileirar um conjunto de solicitações de rede e processá-las juntas. Isso permite que o sistema pague o custo de energia de ligar o rádio apenas uma vez e ainda receba todos os dados solicitados por um app.

Usar o WorkManager

É possível usar a biblioteca WorkManager para realizar o trabalho em uma programação eficiente que considera se condições específicas foram atendidas, como disponibilidade de rede e status de energia. Por exemplo, suponha que você tem uma subclasse Worker chamada DownloadHeadlinesWorker que recupera as manchetes mais recentes. Esse worker pode ser programado para execução a cada hora, desde que o dispositivo esteja conectado a uma rede ilimitada e que a bateria não esteja baixa, com uma estratégia de repetição personalizada caso haja problemas para recuperar os dados, conforme mostrado abaixo:

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);

Além do WorkManager, a Plataforma Android oferece várias outras ferramentas para ajudar a criar uma programação eficiente para concluir tarefas de rede, como pesquisas. Para saber mais sobre como usar essas ferramentas, consulte o Guia para o processamento em segundo plano.

Otimizar solicitações iniciadas pelo servidor

As solicitações iniciadas pelo servidor geralmente ocorrem em resposta a uma notificação de um servidor. Por exemplo, um app usado para ler as notícias mais recentes pode receber uma notificação sobre um novo lote de artigos que se encaixem nas preferências de personalização do usuário, que será transferido por download.

Enviar atualizações do servidor com o Firebase Cloud Messaging

O Firebase Cloud Messaging (FCM) é um mecanismo leve usado para transmitir dados de um servidor para uma instância de app específica. Usando o FCM, o servidor pode notificar seu app em execução em um dispositivo específico de que há novos dados disponíveis para ele.

Em comparação com a pesquisa, em que seu app precisa dar um ping regularmente no servidor para consultar novos dados, esse modelo orientado a eventos permite que o app crie uma nova conexão somente quando sabe que há dados para fazer o download. O modelo minimiza conexões desnecessárias e reduz a latência ao atualizar informações no seu app.

O FCM é implementado usando uma conexão TCP/IP persistente. Isso minimiza o número de conexões persistentes e permite que a plataforma otimize a largura de banda e minimize o impacto associado na duração da bateria.