Riduci al minimo l'effetto degli aggiornamenti regolari

Le richieste inviate dall'app alla rete sono una delle principali cause di consumo eccessivo della batteria, perché attivano i segnali radio Wi-Fi o cellulari che consumano molta energia. Oltre all'energia necessaria per inviare e ricevere pacchetti, queste radio consumano energia aggiuntiva quando si accendono e rimangono attive. Un semplice messaggio di richiesta di rete ogni 15 secondi consente di mantenere attiva la radio mobile in modo continuo e di consumare rapidamente la batteria.

Esistono tre tipi generali di aggiornamenti regolari:

  • Avviato dall'utente. Esecuzione di un aggiornamento in base ad alcuni comportamenti dell'utente, ad esempio il gesto di pull-to-refresh.
  • Avvio dall'app. Esecuzione di un aggiornamento su base ricorrente.
  • Avviato dal server. Esecuzione di un aggiornamento in risposta a una notifica da un server.

Questo argomento esamina ciascuno di questi elementi e illustra ulteriori modi in cui possono essere ottimizzati per ridurre il consumo della batteria.

Ottimizza le richieste avviate dall'utente

Le richieste avviate dall'utente solitamente vengono inviate in risposta ad alcuni comportamenti degli utenti. Ad esempio, un'app usata per leggere le ultime notizie potrebbe consentire all'utente di eseguire un gesto di pull-to-refresh per cercare nuovi articoli. Puoi utilizzare le tecniche seguenti per rispondere alle richieste avviate dall'utente e al contempo ottimizzare l'utilizzo della rete.

Limita le richieste degli utenti

Puoi ignorare alcune richieste avviate dall'utente se non sono necessarie, ad esempio più gesti di pull per l'aggiornamento in un breve periodo di tempo per verificare la presenza di nuovi dati quando quelli attuali sono ancora aggiornati. Se rispondi a ogni richiesta, potresti sprecare una quantità significativa di energia mantenendo attiva la radio. Un approccio più efficiente consiste nel limitare le richieste avviate dall'utente in modo da poter effettuare una sola richiesta per un determinato periodo di tempo, riducendo la frequenza di utilizzo della radio.

Utilizza una cache

La memorizzazione nella cache dei dati dell'app consente di creare 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 il più possibile, includendo risorse statiche e download on demand come immagini di dimensioni complete. Puoi utilizzare le intestazioni cache HTTP per assicurarti che la tua strategia di memorizzazione nella cache non comporti la visualizzazione di dati inattivi nell'app. Per maggiori informazioni sulla memorizzazione nella cache delle risposte della rete, consulta Evitare download ridondanti.

Su Android 11 e versioni successive, la tua app può utilizzare gli stessi set di dati di grandi dimensioni usati da altre app per casi d'uso come il machine learning e la 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 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

Quando si è connessi a una radio wireless, una larghezza di banda superiore in genere si traduce in un costo maggiore della batteria, il che significa che il 5G in genere consuma più energia rispetto alla tecnologia LTE, che a sua volta è più costosa del 3G.

Ciò significa che mentre lo stato della radio di base varia in base alla tecnologia radio, in generale l'impatto relativo della durata del cambiamento di stato sulla batteria è maggiore per le radio con larghezza di banda superiore. Per ulteriori informazioni sulla tail-time, consulta La macchina a stati radio.

Allo stesso tempo, maggiore è la larghezza di banda, quindi puoi precaricare in modo più aggressivo, scaricando più dati contemporaneamente. Forse in modo meno intuitivo, poiché il costo della batteria in 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 il doppio della larghezza di banda e il costo dell'energia del 3G, dovresti scaricare il quadruplo di dati 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 attuale del dispositivo. Una volta individuato il tipo di connessione, puoi modificare di conseguenza le tue 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. Quando gestisci le richieste avviate dall'app, valuta 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 unmetered. Queste richieste possono essere ottimizzate con un'attenta pianificazione e utilizzando librerie come WorkManager.

Richieste di rete batch

Su un dispositivo mobile, l'accensione della radio, la connessione e il mantenimento della radio attiva consuma molta energia. Per questo motivo, l'elaborazione di singole richieste in momenti casuali può comportare un consumo eccessivo di energia e ridurre la durata della batteria. Un approccio più efficiente consiste nel mettere in coda una serie di richieste di rete ed elaborarle insieme. Ciò consente al sistema di pagare il costo dell'energia necessario per accendere la radio una sola volta, pur mantenendo tutti i dati richiesti da un'app.

Utilizzare WorkManager

Puoi utilizzare la libreria WorkManager per eseguire operazioni in base a una programmazione efficiente, che valuta se sono 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 l'esecuzione ogni ora, a condizione che il dispositivo sia connesso a una rete senza limiti e che il livello della batteria del dispositivo non sia basso, con una strategia di nuovo tentativo personalizzata in caso di 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 offre molti altri strumenti per aiutarti a creare una programmazione 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 solitamente si verificano in risposta a una notifica da un server. Ad esempio, un'app utilizzata per leggere le ultime notizie 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 di app. Con FCM, il server può inviare una notifica all'app in esecuzione su un determinato dispositivo per comunicare la disponibilità di nuovi dati.

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

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