Le richieste inviate dalla tua app alla rete sono una delle cause principali del consumo eccessivo della batteria perché accendono le radio cellulari o Wi-Fi che consumano molta energia. Oltre alla potenza necessaria per inviare e ricevere pacchetti, queste radio consumano energia extra solo per accendersi e rimanere attive. Anche una richiesta di rete ogni 15 secondi può mantenere accesa la radio mobile in modo continuo e consumare rapidamente la batteria.
Esistono tre tipi generali di aggiornamenti regolari:
- Avviata dall'utente. Eseguire un aggiornamento in base a un determinato comportamento dell'utente, ad esempio un gesto di scorrimento per aggiornare.
- Avvio da 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 ciascuna di queste funzionalità e illustra ulteriori modi per ottimizzarle al fine di ridurre il consumo della batteria.
Ottimizzare le richieste avviate dall'utente
Le richieste avviate dall'utente si verificano in genere in risposta a un determinato comportamento dell'utente. Ad esempio, un'app utilizzata per leggere gli ultimi articoli di notizie potrebbe consentire all'utente di eseguire un gesto di scorrimento verso l'alto per controllare la presenza di nuovi articoli. Puoi utilizzare le seguenti tecniche per rispondere alle richieste avviate dall'utente ottimizzando al contempo l'utilizzo della rete.
Regolare la velocità delle richieste degli utenti
Potresti ignorare alcune richieste avviate dall'utente se non sono necessarie, ad esempio più gesti di scorrimento per aggiornare in un breve periodo di tempo per verificare la presenza di nuovi dati mentre quelli attuali sono ancora aggiornati. Intervenire su ogni richiesta potrebbe comportare uno spreco significativo di energia mantenendo la radio attiva. Un approccio più efficiente è limitare le richieste avviate dall'utente in modo da poter effettuare una sola richiesta in un determinato periodo di tempo, riducendo la frequenza d'uso 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. 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 della cache HTTP per assicurarti che la tua strategia di memorizzazione nella cache non causi la visualizzazione di dati non aggiornati nella tua app. Per ulteriori informazioni sulla memorizzazione nella cache delle risposte di rete, consulta 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ò 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 ai set di dati condivisi.
Utilizza una larghezza di banda maggiore per scaricare più dati meno spesso
Quando è connesso tramite una radio wireless, una maggiore larghezza di banda comporta in genere un costo più elevato della batteria, il che significa che il 5G in genere consuma più energia rispetto all'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 sul tempo di coda, consulta la sezione La macchina a stati della radio.
Allo stesso tempo, una maggiore larghezza di banda ti consente di eseguire il pre-caricamento in modo più aggressivo, scaricando più dati nello stesso tempo. Forse meno intuitivamente, poiché il costo della batteria per il tempo di attesa è 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 il doppio della larghezza di banda e il doppio del costo energetico rispetto al 3G, dovresti scaricare quattro volte più dati durante ogni sessione o potenzialmente fino a 10 MB. Quando scarichi una quantità così elevata di dati, è importante considerare l'effetto del pre-caricamento sullo spazio di archiviazione locale disponibile e svuotare regolarmente la cache del pre-caricamento.
Puoi utilizzare ConnectivityManager
per registrare un ascoltatore per la rete predefinita e TelephonyManager
per registrare un PhoneStateListener
per determinare il tipo di connessione del dispositivo corrente. Una volta conosciuto il tipo di connessione,
puoi modificare le routine di prefetching di conseguenza:
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 dall'app
Le richieste avviate dall'app si verificano in genere in base a una pianificazione, ad esempio un'app che invia log o dati di 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 senza limiti. Queste richieste possono essere ottimizzate con un'attenta pianificazione e utilizzando librerie come WorkManager.
Richieste di rete batch
Su un dispositivo mobile, la procedura di accensione della radio, di connessione e di mantenimento della radio attiva consuma una grande quantità di 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 è mettere in coda un insieme di richieste di rete e 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 attività in base a una pianificazione efficiente che tiene conto del fatto che siano soddisfatte condizioni specifiche, come la disponibilità della rete e lo stato dell'alimentazione. Ad esempio, supponi di avere una sottoclasse
Worker
chiamata
DownloadHeadlinesWorker
che recupera gli ultimi titoli delle notizie. Questo worker può essere pianificato per l'esecuzione ogni ora, a condizione che il dispositivo sia connesso a una rete senza misurazione e che 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 offre diversi altri strumenti per aiutarti a creare una pianificazione efficiente per completare le attività di rete, come il polling. Per ulteriori informazioni sull'utilizzo di questi strumenti, consulta la Guida all'elaborazione in background.
Ottimizzare 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 le ultime notizie potrebbe ricevere una notifica relativa a un nuovo batch di articoli in linea con 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 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.
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. In questo modo si riduce al minimo il numero di connessioni permanenti e la piattaforma può ottimizzare la larghezza di banda e ridurre al minimo l'impatto associato sulla durata della batteria.