Riduci al minimo l'effetto degli aggiornamenti regolari

Le richieste che la tua app invia alla rete sono una delle principali cause di consumo della batteria perché attivano le radio cellulare o Wi-Fi che consumano energia. Oltre alla potenza necessaria per inviare e ricevere pacchetti, queste radio consumano energia extra solo per accendersi e rimanere attive. Un'operazione semplice come una richiesta di rete ogni 15 secondi può mantenere la radio mobile accesa continuamente e consumare rapidamente la batteria.

Esistono tre tipi generali di aggiornamenti regolari:

  • Avviata dall'utente. Eseguire un aggiornamento in base al comportamento di alcuni utenti, ad esempio un gesto di trascinamento verso il basso per aggiornare.
  • Avviato dall'app. Eseguire 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 aspetti e illustra altri modi in cui possono essere ottimizzati per ridurre il consumo eccessivo della batteria.

Ottimizzare le richieste avviate dagli utenti

Le richieste avviate dagli utenti in genere si verificano in risposta a un comportamento dell'utente. Ad esempio, un'app utilizzata per leggere gli articoli di notizie più recenti può consentire all'utente di eseguire un gesto di trascinamento verso il basso per aggiornare la pagina e controllare se sono presenti nuovi articoli. Puoi utilizzare le seguenti tecniche per rispondere alle richieste avviate dagli utenti ottimizzando l'utilizzo della rete.

Limitare le richieste degli utenti

Potresti voler ignorare alcune richieste avviate dagli utenti se non sono necessarie, ad esempio più gesti di trascinamento verso il basso in un breve periodo di tempo per verificare la presenza di nuovi dati mentre quelli attuali sono ancora aggiornati. L'elaborazione di ogni richiesta potrebbe sprecare una quantità significativa di energia mantenendo la radio attiva. Un approccio più efficiente consiste nel limitare le richieste avviate dall'utente in modo che possa essere effettuata una sola richiesta in un determinato periodo di tempo, riducendo la frequenza di utilizzo della radio.

Utilizzare una cache

Memorizzando nella cache i dati dell'app, crei una copia locale delle informazioni a cui l'app deve fare riferimento. La tua app può quindi accedere più volte alla stessa copia locale delle informazioni senza dover aprire una connessione di rete per effettuare nuove richieste.

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

Su Android 11 e versioni successive, la tua app può utilizzare gli stessi set di dati di grandi dimensioni utilizzati 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ò prima controllare se esiste una versione memorizzata nella cache prima di tentare di scaricare una nuova copia. Per saperne di più sui set di dati condivisi, consulta Accedere ai set di dati condivisi.

Utilizzare una larghezza di banda maggiore per scaricare più dati meno spesso

Quando la connessione avviene tramite una radio wireless, una larghezza di banda maggiore in genere comporta un costo maggiore della batteria, il che significa che il 5G in genere consuma più energia dell'LTE, che a sua volta è più costoso del 3G.

Ciò significa che, sebbene lo stato della radio sottostante vari in base alla tecnologia radio, in generale l'impatto relativo sulla batteria del tempo di coda del cambio di stato è maggiore per le radio con larghezza di banda più elevata. Per saperne di più sul tail-time, consulta La macchina a stati della radio.

Allo stesso tempo, la larghezza di banda più elevata ti consente di eseguire il precaricamento in modo più aggressivo, scaricando più dati nello stesso periodo di tempo. 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 per ridurre la frequenza degli aggiornamenti.

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

Puoi utilizzare ConnectivityManager per registrare un listener per la rete predefinita e TelephonyManager per registrare un PhoneStateListener per determinare il tipo di connessione del dispositivo corrente. Una volta noto 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;
}

Ottimizzare le richieste avviate dalle app

Le richieste avviate dalle app in genere vengono eseguite in base a una pianificazione, ad esempio un'app che invia log o analisi a un servizio di backend. Quando gestisci richieste avviate dalle app, considera la priorità di queste richieste, se possono essere raggruppate e se possono essere posticipate fino a quando il dispositivo non è in carica o connesso a una rete senza tariffa a consumo. Queste richieste possono essere ottimizzate con una pianificazione attenta 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 consumano molta energia. Per questo motivo, l'elaborazione di singole richieste in momenti casuali può consumare molta energia e ridurre la durata della batteria. Un approccio più efficiente consiste nel mettere in coda un insieme di richieste di rete ed elaborarle insieme. In questo modo, il sistema paga il costo dell'accensione della radio una sola volta e riceve comunque tutti i dati richiesti da un'app.

Utilizzare WorkManager

Puoi utilizzare la libreria WorkManager per eseguire il lavoro in modo efficiente che tenga conto del rispetto di condizioni specifiche, come la disponibilità della rete e lo stato di alimentazione. Ad esempio, supponi di avere una sottoclasse Worker chiamata DownloadHeadlinesWorker che recupera i titoli delle ultime notizie. Questo worker può essere programmato per l'esecuzione ogni ora, a condizione che il dispositivo sia connesso a una rete senza tariffa a consumo e la batteria del dispositivo non sia in esaurimento, con una strategia di ripetizione personalizzata in caso di problemi di 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 rete, ad esempio il polling. Per scoprire di più sull'utilizzo di questi strumenti, consulta la Guida all'elaborazione in background.

Ottimizzare le richieste avviate dal server

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

Inviare aggiornamenti del server con Firebase Cloud Messaging

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

Rispetto al polling, in cui l'app deve eseguire regolarmente il ping del server per eseguire query per nuovi dati, questo modello basato sugli 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. In questo modo si riduce al minimo il numero di connessioni persistenti e la piattaforma può ottimizzare la larghezza di banda e ridurre al minimo l'impatto associato sulla durata della batteria.