Les requêtes que votre application envoie au réseau sont une cause majeure d'usure de la batterie, car elles activent les radios cellulaires ou Wi-Fi énergivores. En plus de l'énergie nécessaire pour envoyer et recevoir des paquets, ces radios consomment de l'énergie supplémentaire simplement pour s'allumer et rester actives. Une requête réseau toutes les 15 secondes peut maintenir la radio mobile allumée en permanence et décharger rapidement la batterie.
Il existe trois types généraux de mises à jour régulières:
- Déclenché par l'utilisateur Effectuer une mise à jour en fonction d'un comportement utilisateur, tel qu'un geste de balayage pour actualiser.
- Appli lancée Effectuer une mise à jour de façon récurrente
- Initiatée par le serveur Effectuer une mise à jour en réponse à une notification d'un serveur
Cet article examine chacun de ces éléments et décrit d'autres façons de les optimiser pour réduire l'épuisement de la batterie.
Optimiser les requêtes déclenchées par l'utilisateur
Les requêtes déclenchées par l'utilisateur se produisent généralement en réponse à un comportement de l'utilisateur. Par exemple, une application permettant de lire les derniers articles d'actualités peut autoriser l'utilisateur à effectuer un geste de balayage pour actualiser la page et vérifier si de nouveaux articles sont disponibles. Vous pouvez utiliser les techniques suivantes pour répondre aux requêtes déclenchées par l'utilisateur tout en optimisant l'utilisation du réseau.
Limiter les requêtes des utilisateurs
Vous pouvez ignorer certaines requêtes déclenchées par l'utilisateur si elles ne sont pas nécessaires, par exemple plusieurs gestes de balayage pour actualiser sur une courte période afin de rechercher de nouvelles données alors que les données actuelles sont encore à jour. Le traitement de chaque requête pourrait gaspiller une quantité importante d'énergie en maintenant la radio active. Une approche plus efficace consiste à limiter les requêtes déclenchées par l'utilisateur afin qu'une seule requête puisse être effectuée sur une période donnée, ce qui réduit la fréquence d'utilisation de la radio.
Utiliser un cache
En mettant en cache les données de votre application, vous créez une copie locale des informations que votre application doit référencer. Votre application peut ensuite accéder à la même copie locale des informations plusieurs fois sans avoir à ouvrir de connexion réseau pour effectuer de nouvelles requêtes.
Vous devez mettre en cache les données aussi agressivement que possible, y compris les ressources statiques et les téléchargements à la demande tels que les images en taille réelle. Vous pouvez utiliser des en-têtes de cache HTTP pour vous assurer que votre stratégie de mise en cache n'entraîne pas l'affichage de données obsolètes par votre application. Pour en savoir plus sur la mise en cache des réponses réseau, consultez la section Éviter les téléchargements redondants.
Sous Android 11 ou version ultérieure, votre application peut utiliser les mêmes grands ensembles de données que les autres applications pour des cas d'utilisation tels que le machine learning et la lecture de contenus multimédias. Lorsque votre application a besoin d'accéder à un ensemble de données partagé, elle peut d'abord rechercher une version mise en cache avant de tenter de télécharger une nouvelle copie. Pour en savoir plus sur les ensembles de données partagés, consultez Accéder aux ensembles de données partagés.
Utiliser une bande passante plus élevée pour télécharger plus de données moins souvent
Lorsqu'il est connecté via une radio sans fil, une bande passante plus élevée se traduit généralement par une augmentation de la consommation de la batterie. Cela signifie que la 5G consomme généralement plus d'énergie que la LTE, qui est à son tour plus chère que la 3G.
Cela signifie que, bien que l'état radio sous-jacent varie en fonction de la technologie radio, en général, l'impact relatif de la batterie sur la durée de latence de changement d'état est plus important pour les radios à bande passante plus élevée. Pour en savoir plus sur le délai de latence, consultez la section Machine d'état de la radio.
En même temps, une bande passante plus élevée vous permet de précharger plus agressivement, en téléchargeant plus de données sur la même période. Peut-être moins intuitivement, car le coût de la batterie en fin de session est relativement plus élevé, il est également plus efficace de maintenir la radio active pendant de plus longues périodes lors de chaque session de transfert afin de réduire la fréquence des mises à jour.
Par exemple, si la bande passante d'une radio LTE est deux fois plus élevée et que son coût énergétique est deux fois plus élevé que celui de la 3G, vous devriez télécharger quatre fois plus de données à chaque session, soit jusqu'à 10 Mo. Lorsque vous téléchargez autant de données, il est important de prendre en compte l'impact de votre préchargement sur l'espace de stockage local disponible et d'effacer régulièrement votre cache de préchargement.
Vous pouvez utiliser ConnectivityManager
pour enregistrer un écouteur pour le réseau par défaut et TelephonyManager
pour enregistrer un PhoneStateListener
afin de déterminer le type de connexion actuel de l'appareil. Une fois le type de connexion connu, vous pouvez modifier vos routines de préchargement en conséquence:
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; }
Optimiser les requêtes déclenchées par l'application
Les requêtes déclenchées par l'application se produisent généralement de manière planifiée, par exemple lorsqu'une application envoie des journaux ou des analyses à un service backend. Lorsque vous traitez des requêtes lancées par l'application, tenez compte de leur priorité, de la possibilité de les regrouper et de la possibilité de les différer jusqu'à ce que l'appareil soit en charge ou connecté à un réseau non facturé à l'usage. Ces requêtes peuvent être optimisées grâce à une planification minutieuse et à l'utilisation de bibliothèques telles que WorkManager.
Requêtes réseau par lot
Sur un appareil mobile, le processus d'allumage de la radio, de connexion et de maintien de la radio active consomme une grande quantité d'énergie. Pour cette raison, le traitement de requêtes individuelles à des moments aléatoires peut consommer une quantité d'énergie importante et réduire l'autonomie de la batterie. Une approche plus efficace consiste à mettre en file d'attente un ensemble de requêtes réseau et à les traiter ensemble. Cela permet au système de payer le coût énergétique de l'activation de la radio une seule fois, tout en obtenant toutes les données demandées par une application.
Utiliser WorkManager
Vous pouvez utiliser la bibliothèque WorkManager
pour effectuer des tâches selon un calendrier efficace qui tient compte de la satisfaction de conditions spécifiques, telles que la disponibilité du réseau et l'état de l'alimentation. Par exemple, supposons que vous disposiez d'une sous-classe Worker
appelée DownloadHeadlinesWorker
qui récupère les derniers titres d'actualité. Vous pouvez planifier l'exécution de ce worker toutes les heures, à condition que l'appareil soit connecté à un réseau illimité et que la batterie de l'appareil ne soit pas faible, avec une stratégie de nouvelle tentative personnalisée en cas de problème de récupération des données, comme indiqué ci-dessous:
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);
En plus de WorkManager, la plate-forme Android fournit plusieurs autres outils pour vous aider à créer un calendrier efficace pour effectuer des tâches de mise en réseau, telles que l'interrogation. Pour en savoir plus sur l'utilisation de ces outils, consultez le guide sur le traitement en arrière-plan.
Optimiser les requêtes déclenchées par le serveur
Les requêtes initiées par le serveur se produisent généralement en réponse à une notification d'un serveur. Par exemple, une application utilisée pour lire les derniers articles d'actualités peut recevoir une notification concernant un nouveau lot d'articles correspondant aux préférences de personnalisation de l'utilisateur, qu'elle télécharge ensuite.
Envoyer des mises à jour de serveur avec Firebase Cloud Messaging
Firebase Cloud Messaging (FCM) est un mécanisme léger utilisé pour transmettre des données d'un serveur à une instance d'application particulière. Avec FCM, votre serveur peut informer votre application exécutée sur un appareil particulier que de nouvelles données sont disponibles pour elle.
Contrairement à l'interrogation, où votre application doit régulièrement envoyer un ping au serveur pour interroger de nouvelles données, ce modèle basé sur les événements ne permet à votre application de créer une nouvelle connexion que lorsqu'elle sait qu'il y a des données à télécharger. Le modèle minimise les connexions inutiles et réduit la latence lors de la mise à jour des informations dans votre application.
FCM est implémenté à l'aide d'une connexion TCP/IP persistante. Cela réduit le nombre de connexions persistantes et permet à la plate-forme d'optimiser la bande passante et de minimiser l'impact associé sur l'autonomie de la batterie.