Riduci al minimo l'effetto degli aggiornamenti regolari

Le richieste effettuate dalla tua app alla rete sono una delle principali cause di consumo eccessivo della batteria perché accendono le radio cellulari o Wi-Fi che consumano molta energia. Al di là della potenza necessaria per inviare e ricevere pacchetti, queste radio consumano molta energia, semplicemente per accendersi e rimanere in stato di veglia. Una semplice richiesta di rete ogni 15 secondi può mantenere la radio mobile ininterrotta e consumare rapidamente la batteria.

Esistono tre tipi generali di aggiornamenti regolari:

  • Avviata dall'utente. Esecuzione di un aggiornamento in base al comportamento di alcuni utenti, ad esempio un gesto di pull-to-refresh.
  • Avvio dall'app. Eseguire un aggiornamento su base ricorrente.
  • Avviata dal server. L'esecuzione di un aggiornamento in risposta a una notifica da un server.

Questo argomento esamina ciascuno di questi elementi e illustra altri modi per ottimizzarli per ridurre il consumo della batteria.

Ottimizza le richieste avviate dall'utente

Le richieste avviate dall'utente si verificano in genere in risposta ad alcuni comportamenti degli utenti. Ad esempio, un'app utilizzata per leggere gli articoli più recenti potrebbe consentire all'utente di eseguire un gesto di aggiornamento tramite pull per controllare i nuovi articoli. Puoi utilizzare le seguenti tecniche per rispondere alle richieste avviate dall'utente, ottimizzando al contempo l'utilizzo della rete.

Limita le richieste degli utenti

Ti consigliamo di ignorare alcune richieste avviate dall'utente se non sono necessarie, ad esempio più gesti di pull-to-refresh in un breve periodo di tempo per verificare la presenza di nuovi dati mentre quelli correnti sono ancora aggiornati. Rispondere a ogni richiesta potrebbe sprecare una quantità significativa di energia mantenendo attiva la radio. Un approccio più efficiente consiste nel limitare le richieste avviate dall'utente in modo che sia possibile effettuare una sola richiesta per un periodo di tempo, riducendo la frequenza di utilizzo della radio.

Utilizza una cache

Se i dati dell'app vengono memorizzati nella cache, viene creata una copia locale delle informazioni a cui l'app deve fare riferimento. L'app può quindi accedere più volte alla stessa copia locale delle informazioni senza dover aprire una connessione di rete per effettuare nuove richieste.

Dovresti memorizzare i dati nella cache nel modo più aggressivo possibile, incluse le risorse statiche e i download on demand, come le immagini a grandezza originale. Puoi utilizzare le intestazioni cache HTTP per assicurarti che la strategia di memorizzazione nella cache non comporti la visualizzazione di dati inattivi nell'app. Per maggiori informazioni sulla memorizzazione nella cache delle risposte di rete, consulta Evitare i download ridondanti.

Su Android 11 e versioni successive, la tua app può usare gli stessi set di dati di grandi dimensioni utilizzati da altre app per casi d'uso come machine learning e riproduzione di contenuti multimediali. Quando la tua app deve accedere a un set di dati condiviso, può verificare la presenza di una versione memorizzata nella cache prima di tentare di scaricare una nuova copia. Per saperne di più sui set di dati condivisi, consulta Accedere a set di dati condivisi.

Utilizza una larghezza di banda maggiore per scaricare più dati con minore frequenza

Se collegato tramite radio wireless, una maggiore larghezza di banda di solito comporta un maggiore costo della batteria. Di conseguenza, il 5G consuma più energia dell'LTE, che a sua volta è più costoso del 3G.

Ciò significa che, anche se lo stato radio sottostante varia in base alla tecnologia radio, in generale l'impatto relativo sulla batteria del tempo di coda del cambiamento di stato è maggiore per le radio con larghezza di banda superiore. Per ulteriori informazioni su tail-time, vedi La radio state machine.

Allo stesso tempo, maggiore è la larghezza di banda, pertanto puoi precaricare in modo più aggressivo, scaricando più dati nello stesso momento. Forse in modo meno intuitivo, dato che il costo della batteria al tempo di coda è relativamente più elevato, è anche più efficiente mantenere la radio attiva per periodi più lunghi durante ogni sessione di trasferimento, in modo da ridurre la frequenza degli aggiornamenti.

Ad esempio, se una radio LTE ha una larghezza di banda raddoppiata e un costo energetico raddoppiato rispetto al 3G, dovresti scaricare una quantità di dati quattro volte maggiore durante ogni sessione, o potenzialmente fino a 10 MB. Quando scarichi così tanti dati, è importante considerare l'effetto del precaricamento sullo spazio di archiviazione locale disponibile e svuotare regolarmente la cache di precaricamento.

Puoi utilizzare ConnectivityManager per registrare un listener per la rete predefinita e TelephonyManager per registrare un PhoneStateListener per determinare il tipo di connessione corrente del dispositivo. Una volta identificato il tipo di connessione, puoi modificare di conseguenza le routine di precaricamento:

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

Ottimizza le richieste avviate dall'app

Le richieste avviate dall'app in genere si verificano in base a una pianificazione, ad esempio un'app che invia log o analisi a un servizio di backend. Per quanto riguarda le richieste avviate dall'app, considera la priorità di queste richieste, se possono essere raggruppate insieme e se possono essere differite fino a quando il dispositivo non è in carica o connesso a una rete illimitata. Queste richieste possono essere ottimizzate con un'attenta pianificazione e utilizzando librerie come WorkManager.

Richieste di rete batch

Su un dispositivo mobile, il processo di accensione della radio, di connessione e di mantenimento della radio attiva utilizza una grande quantità di energia. Per questo motivo, l'elaborazione di singole richieste in momenti casuali può comportare un consumo significativo e ridurre la durata della batteria. Un approccio più efficiente consiste nell'accodare una serie di richieste di rete ed elaborarle insieme. Ciò consente al sistema di pagare i costi dell'alimentazione di accendere la radio una sola volta e di ricevere comunque tutti i dati richiesti da un'app.

Utilizzare WorkManager

Puoi utilizzare la libreria WorkManager per eseguire il lavoro in base a una pianificazione efficiente che valuta se vengono soddisfatte condizioni specifiche, come la disponibilità della rete e lo stato dell'alimentazione. Ad esempio, supponi di avere una sottoclasse Worker denominata DownloadHeadlinesWorker che recupera i titoli delle notizie più recenti. Questo worker può essere pianificato per essere eseguito ogni ora, a condizione che il dispositivo sia connesso a una rete illimitata e che la batteria del dispositivo non sia in esaurimento, con una strategia di ripetizione personalizzata se si verificano problemi durante il recupero dei dati, come mostrato di seguito:

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

Oltre a WorkManager, la piattaforma Android fornisce diversi altri strumenti per aiutarti a creare una pianificazione efficiente per il completamento delle attività di networking, come i sondaggi. Per ulteriori informazioni sull'utilizzo di questi strumenti, consulta la Guida all'elaborazione in background.

Ottimizza le richieste avviate dal server

Le richieste avviate dal server si verificano in genere in risposta a una notifica da un server. Ad esempio, un'app utilizzata per leggere gli articoli più recenti potrebbe ricevere una notifica relativa a un nuovo gruppo di articoli che soddisfano le preferenze di personalizzazione dell'utente, che poi scarica.

Invia aggiornamenti del server con Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) è un meccanismo leggero utilizzato per trasmettere i dati da un server a una determinata istanza dell'app. Utilizzando FCM, il server può notificare all'app in esecuzione su un determinato dispositivo che sono disponibili nuovi dati per il dispositivo.

Rispetto al polling, in cui la tua app deve inviare regolarmente un ping al server per eseguire query in cerca di nuovi dati, questo modello basato su eventi consente all'app di creare una nuova connessione solo quando sa che sono presenti dati da scaricare. Il modello riduce al minimo le connessioni non necessarie e riduce la latenza durante l'aggiornamento delle informazioni all'interno dell'app.

FCM viene implementato utilizzando una connessione TCP/IP permanente. Questo riduce al minimo il numero di connessioni permanenti e consente alla piattaforma di ottimizzare la larghezza di banda e ridurre al minimo il relativo impatto sulla durata della batteria.