As solicitações que seu app faz à rede são uma das principais causas de consumo elevado da bateria porque ativam rádios Wi-Fi ou celular que consomem muita energia. Além do poder necessários para enviar e receber pacotes, esses rádios consomem ligando e mantendo-o acordado. Algo tão simples quanto uma solicitação de rede a cada 15 em segundos pode manter o rádio móvel ligado de forma contínua e consumir bateria rapidamente poder
Há três tipos gerais de atualizações regulares:
- Iniciado pelo usuário. Realizar uma atualização com base em algum comportamento do usuário, como um gesto de puxar para atualizar.
- Iniciado pelo app. Executar uma atualização de forma recorrente.
- Iniciada pelo servidor. Fazer uma atualização em resposta a uma notificação de um servidor.
Este tópico analisa cada um deles e discute maneiras adicionais de otimizada 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 últimas notícias pode permitir que o usuário execute um gesto de puxar para atualizar para verificar novos artigos. É possível usar as técnicas a seguir para responder a solicitações iniciadas pelo usuário enquanto otimiza o uso da rede.
Limitar solicitações do usuário
Talvez seja necessário desconsiderar algumas solicitações iniciadas pelo usuário se não houver necessidade delas, como vários gestos de puxar para atualizar em um curto período de tempo para verificar novos dados enquanto os dados atuais ainda estão atualizados. Agir em cada solicitação poderia desperdiçar uma quantidade significativa de energia ao manter o rádio ativado. Um abordagem mais eficiente é limitar as solicitações iniciadas pelo usuário para que apenas uma solicitação pode ser feita durante um período, reduzindo a frequência o rádio é usado.
Usar um cache
Ao armazenar os dados do app em cache, você cria uma cópia local das informações que seu app precisa referenciar. Assim, o app poderá acessar os mesmos locais copiar as informações várias vezes sem precisar abrir uma rede para fazer novas solicitações.
Você deve armazenar em cache os dados o mais agressivamente possível, incluindo e downloads sob demanda, como imagens em tamanho original. É possível usar cabeçalhos de cache HTTP para garantir que a estratégia de armazenamento em cache não resulte em dados desatualizados no app. Para mais informações sobre o armazenamento em cache de respostas de rede, consulte Evitar downloads redundantes.
No Android 11 e versões mais recentes, seu 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 seu app precisa acessar um conjunto de dados compartilhado, ele pode verificar primeiro 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 o dispositivo está conectado por um rádio sem fio, uma largura de banda maior geralmente tem um maior custo de bateria. Isso significa que o 5G normalmente consome mais energia do que o LTE, que tem um custo maior que o 3G.
Isso significa que, embora o estado de rádio subjacente varie de acordo com tecnologia de rádio, de modo geral, o impacto relativo da bateria alterar o tempo de cauda é maior para rádios com largura de banda maior. Para mais informações sobre o tempo de espera, consulte A máquina de estado de rádio.
Ao mesmo tempo, a maior largura de banda permite uma pré-busca mais agressiva, fazendo o download de mais dados de uma só vez. Talvez menos intuitivamente, como o custo da bateria no tempo de cauda é relativamente maior, também mais eficiente para manter o rádio ativo por períodos mais longos durante cada 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 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.
É possível usar o
ConnectivityManager
para registrar
um listener para a rede padrão e o
TelephonyManager
para registrar
um PhoneStateListener
para
determinar o tipo de conexão do dispositivo atual. Depois que o tipo de conexão for conhecido,
é 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 em uma programação, como um app que envia registros ou análises para um serviço de back-end. Ao lidar com comandos iniciados pelo app, as solicitações, considere a prioridade delas, se elas podem ser agrupadas juntos e se eles podem ser adiados até que o dispositivo esteja carregando ou conectadas 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, estabelecer uma conexão e manter o rádio ativo consome uma grande quantidade de energia. Por isso, o processamento de solicitações individuais em momentos aleatórios pode consumir muita energia e reduzir a duração da bateria. Uma abordagem mais eficiente é enfileirar um conjunto de solicitações e processá-las em conjunto. Isso permite que o sistema pague o custo de energia para ligar o rádio apenas uma vez e ainda assim receba todos os dados solicitados por um app.
Usar o WorkManager
Você pode usar a biblioteca WorkManager
para trabalhar de acordo com uma programação eficiente
que considera se condições específicas são atendidas, como disponibilidade de rede
e o status de energia. Por exemplo, suponha que você tenha
Subclasse Worker
chamada
DownloadHeadlinesWorker
que recupera as manchetes mais recentes. Este worker
pode ser programado para execução a cada hora, desde que o dispositivo esteja conectado a uma
rede ilimitada e se a bateria do dispositivo não está baixa, com uma estratégia personalizada de repetição
se houver algum problema na recuperação dos 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 você a criar uma programação eficiente para concluir tarefas de rede, como pesquisas. Para saber mais sobre o uso dessas ferramentas, consulte o Guia para 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 os artigos de notícias mais recentes pode receber uma notificação sobre um novo lote de artigos que se encaixam nas preferências de personalização do usuário, que são baixadas.
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.
Comparado à pesquisa, em que seu app precisa dar um ping regularmente no servidor para consultar novos dados, esse modelo orientado por eventos permite que o app crie uma nova conexão apenas quando souber que há dados para transferir por download. O modelo minimiza o conexões, além de reduzir a latência ao atualizar informações no aplicativo.
O FCM é implementado usando uma conexão TCP/IP persistente. Isso minimiza a o número de conexões persistentes e permite que a plataforma otimize a largura de banda e minimizar o impacto associado na duração da bateria.