Réduire l'impact des mises à jour régulières

Les requêtes envoyées au réseau par votre application sont une cause majeure de décharge de la batterie, car elles allument les radios cellulaires ou Wi-Fi qui consomment de l'énergie. Au-delà de la puissance nécessaire pour envoyer et recevoir des paquets, ces signaux radio consomment de l'énergie supplémentaire, simplement s'ils s'allument et restent activés. Une simple requête réseau toutes les 15 secondes peut permettre de maintenir le signal radio mobile activé en continu et en consommant rapidement la batterie.

Il existe trois types généraux de mises à jour régulières:

  • Déclenchement par l'utilisateur : Effectuer une mise à jour en fonction d'un comportement de l'utilisateur, tel qu'un geste d'extraction pour actualiser
  • Création déclenchée par l'application : Effectuer une mise à jour récurrente.
  • Déclenchement par le serveur. Effectuer une mise à jour en réponse à une notification d'un serveur

Cet article examine chacun de ces éléments et présente d'autres moyens de les optimiser afin de réduire la décharge de la batterie.

Optimiser les requêtes déclenchées par l'utilisateur

Ces requêtes sont généralement déclenchées en réponse à un certain comportement de l'utilisateur. Par exemple, une application utilisée pour lire les derniers articles d'actualité peut permettre à l'utilisateur d'effectuer un geste d'extraction pour rechercher de nouveaux articles. 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 cela n'est pas nécessaire. Par exemple, vous pouvez effectuer plusieurs gestes d'extraction pour actualiser sur une courte période afin de rechercher de nouvelles données tant que les données actuelles sont encore à jour. Le fait de répondre à chaque demande pourrait gaspiller une quantité importante d'énergie en laissant la radio activée. 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 au cours d'une période donnée, ce qui réduit la fréquence d'utilisation de l'option.

Utiliser un cache

En mettant en cache les données de votre application, vous créez une copie locale des informations auxquelles votre application doit se référer. Votre application peut ensuite accéder plusieurs fois à la même copie locale des informations sans avoir à ouvrir une connexion réseau pour effectuer de nouvelles requêtes.

Vous devez mettre en cache les données de manière aussi agressive 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.

Sur Android 11 et versions ultérieures, votre application peut utiliser les mêmes ensembles de données volumineux que d'autres applications pour des cas d'utilisation tels que le machine learning et la lecture de contenus multimédias. Lorsque votre application doit accéder à un ensemble de données partagé, elle peut d'abord rechercher une version mise en cache avant d'essayer de télécharger une nouvelle copie. Pour en savoir plus sur les ensembles de données partagés, consultez la section Accéder aux ensembles de données partagés.

Utiliser une plus grande bande passante pour télécharger plus de données moins souvent

Lorsqu'elle est connectée via une radio sans fil, l'augmentation de la bande passante a généralement un coût plus élevé que la 5G, ce qui signifie que la 5G consomme généralement plus d'énergie que le LTE, qui est à son tour plus chère que la 3G.

Cela signifie que bien que l'état du signal radio varie en fonction de la technologie radio, de manière générale, l'impact relatif sur la batterie du temps de fin du changement d'état est plus important pour les radios à bande passante plus élevée. Pour en savoir plus sur le temps de traîne, consultez la section Machine à états radio.

En parallèle, plus la bande passante est élevée, plus vous pouvez précharger de manière plus agressive, en téléchargeant davantage de données en même temps. Ce serait peut-être moins intuitif, car le coût de la batterie en fin de vie est relativement plus élevé. Il est également plus efficace de garder le signal radio actif plus longtemps lors de chaque session de transfert afin de réduire la fréquence des mises à jour.

Par exemple, si une radio LTE offre deux fois plus de bande passante et deux fois le coût énergétique de la 3G, vous devez télécharger quatre fois plus de données par session (ou potentiellement jusqu'à 10 Mo). Lorsque vous téléchargez autant de données, il est important de prendre en compte l'effet du préchargement sur l'espace de stockage local disponible et de vider 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 déroulent généralement selon un calendrier, par exemple une application qui envoie des journaux ou des analyses à un service de backend. Lorsque vous traitez des requêtes déclenchées par l'application, déterminez leur priorité, si elles peuvent être regroupées et si elles peuvent être différées jusqu'à ce que l'appareil soit en charge ou connecté à un réseau non facturé à l'usage. Ces requêtes peuvent être optimisées à l'aide d'une planification minutieuse et de bibliothèques telles que WorkManager.

Requêtes réseau par lot

Sur un appareil mobile, le processus d'activation du signal radio, d'établissement d'une connexion et de maintien du signal activé utilise une grande quantité d'énergie. Pour cette raison, le traitement de requêtes individuelles de manière aléatoire peut consommer une énergie importante et réduire l'autonomie de la batterie. Une approche plus efficace consiste à mettre un ensemble de requêtes réseau en file d'attente et à les traiter ensemble. Cela permet au système de ne payer qu'une seule fois le coût de l'activation de la radio, 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 une programmation efficace qui tient compte si des conditions spécifiques sont remplies, telles que la disponibilité du réseau et l'état de l'alimentation. Par exemple, supposons que vous ayez une sous-classe Worker appelée DownloadHeadlinesWorker qui récupère les derniers titres de l'actualité. Ce worker peut être programmé pour s'exécuter toutes les heures, à condition que l'appareil soit connecté à un réseau illimité et que la batterie de l'appareil n'est 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 une planification efficace pour l'exécution 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 déclenché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é 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 les mises à jour du 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. À l'aide de FCM, votre serveur peut avertir votre application en cours d'exécution sur un appareil particulier que de nouvelles données sont disponibles.

Contrairement à l'interrogation, où votre application doit régulièrement pinguer le serveur pour obtenir de nouvelles données, ce modèle basé sur les événements ne permet à votre application de créer une connexion que lorsqu'elle sait que des données sont à 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 mis en œuvre à l'aide d'une connexion TCP/IP persistante. Cela réduit le nombre de connexions persistantes, permet à la plate-forme d'optimiser la bande passante et de minimiser l'impact associé sur l'autonomie de la batterie.