Minimizar o efeito de atualizações regulares

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

Existem 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. Realizar uma atualização de maneira recorrente.
  • Iniciado pelo servidor. A execução de uma atualização em resposta a uma notificação do 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. Para exemplo, um app usado para ler as notícias mais recentes pode permitir que o usuário faça um gesto de puxar para atualizar para conferir novos artigos. Você pode usar o técnicas para responder a solicitações iniciadas pelo usuário e otimizar uso da rede.

Limitar solicitações do usuário

Desconsidere algumas solicitações iniciadas pelo usuário se não houver necessidade como vários gestos de puxar para atualizar em um curto período para verificar se há 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 HTTP cabeçalhos de cache para garantir que sua estratégia de armazenamento em cache não resulte no seu aplicativo exibindo dados desatualizados. Para mais informações sobre como armazenar respostas de rede em cache, consulte Evitar redundância para download.

No Android 11 e versões mais recentes, seu app pode usar os mesmos conjuntos de dados grandes que outros 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 custo de bateria mais alto, o que significa que o 5G normalmente consome mais energia do que o LTE, que é mais caro do 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 tempo de cauda, consulte O estado do rádio máquina virtual.

Ao mesmo tempo, a largura de banda maior significa que você pode pré-buscar mais agressivamente, fazendo o download de mais dados ao mesmo tempo. 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 de 3G, faça 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 transferir o cache de pré-busca regularmente.

Você pode usar o ConnectivityManager para se registrar um listener para a rede padrão, e o TelephonyManager para se registrar PhoneStateListener para determinar o tipo atual de conexão do dispositivo. 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 de acordo com uma programação, como quando um app envia 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 cuidado de programação e usando bibliotecas como 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 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 a energia de ligar o rádio apenas uma vez e ainda assim receber todos os dados solicitados pelo de 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 um cronograma eficiente para concluir tarefas de rede, como como sondagem. Para saber mais sobre como usar essas ferramentas, consulte a 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 das preferências de personalização, que são baixadas por download.

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

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

Em comparação com a sondagem, em que o app precisa dar um ping regularmente no servidor para consultar novos dados, esse modelo orientado a eventos permite que seu app crie uma nova conexão apenas quando souber que há dados para 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.