Anfragen, die Ihre App an das Netzwerk sendet, sind eine Hauptursache für den Akkuverbrauch, da sie stromintensive Mobilfunk- oder WLAN-Funkschnittstellen aktivieren. Neben der Energie, die zum Senden und Empfangen von Paketen benötigt wird, verbrauchen diese Funkschnittstellen zusätzliche Energie, um sich einzuschalten und in Betrieb zu bleiben. Sogar eine einfache Netzwerkanfrage alle 15 Sekunden kann dazu führen, dass das Mobilfunkradio dauerhaft eingeschaltet bleibt und der Akku schnell entladen wird.
Es gibt drei allgemeine Arten von regelmäßigen Updates:
- Vom Nutzer initiiert. Aktualisierung aufgrund eines Nutzerverhaltens, z. B. eines Wisch-Gestos zum Aktualisieren
- Von der App initiiert Aktualisierung auf wiederkehrender Basis
- Serverseitig Aktualisierung als Reaktion auf eine Benachrichtigung von einem Server
In diesem Thema werden diese Funktionen näher betrachtet und es werden weitere Möglichkeiten zur Optimierung erläutert, um die Akkuentladung zu reduzieren.
Vom Nutzer initiierte Anfragen optimieren
Von Nutzern initiierte Anfragen erfolgen in der Regel als Reaktion auf bestimmtes Nutzerverhalten. In einer App, mit der die neuesten Nachrichtenartikel gelesen werden, kann der Nutzer beispielsweise eine Wischgeste ausführen, um nach neuen Artikeln zu suchen. Mit den folgenden Methoden können Sie auf vom Nutzer initiierte Anfragen reagieren und gleichzeitig die Netzwerknutzung optimieren.
Nutzeranfragen drosseln
Sie können einige von Nutzern initiierte Anfragen ignorieren, wenn sie nicht erforderlich sind, z. B. mehrere Wischbewegungen zum Aktualisieren innerhalb kurzer Zeit, um nach neuen Daten zu suchen, während die aktuellen Daten noch aktuell sind. Wenn auf jede Anfrage reagiert wird, kann dies zu einem erheblichen Stromverbrauch führen, da das Funkmodul ständig aktiv bleibt. Eine effizientere Methode besteht darin, die von Nutzern initiierten Anfragen so zu drosseln, dass über einen bestimmten Zeitraum nur eine Anfrage gesendet werden kann. So wird die Häufigkeit der Nutzung des Funkschnittstellen verringert.
Cache verwenden
Wenn Sie die Daten Ihrer App im Cache speichern, erstellen Sie eine lokale Kopie der Informationen, auf die Ihre App verweisen muss. Ihre App kann dann mehrmals auf dieselbe lokale Kopie der Informationen zugreifen, ohne eine Netzwerkverbindung für neue Anfragen öffnen zu müssen.
Sie sollten Daten so aggressiv wie möglich im Cache speichern, einschließlich statischer Ressourcen und On-Demand-Downloads wie Bilder in Originalgröße. Mithilfe von HTTP-Cache-Headern können Sie dafür sorgen, dass Ihre App aufgrund Ihrer Caching-Strategie keine veralteten Daten anzeigt. Weitere Informationen zum Caching von Netzwerkantworten finden Sie unter Redundante Downloads vermeiden.
Unter Android 11 und höher kann Ihre App dieselben großen Datensätze verwenden, die andere Apps für Anwendungsfälle wie maschinelles Lernen und Medienwiedergabe verwenden. Wenn Ihre App auf einen freigegebenen Datensatz zugreifen muss, kann sie zuerst nach einer Version im Cache suchen, bevor sie versucht, eine neue Kopie herunterzuladen. Weitere Informationen zu freigegebenen Datasets finden Sie unter Auf freigegebene Datasets zugreifen.
Höhere Bandbreite nutzen, um weniger oft mehr Daten herunterzuladen
Bei einer Verbindung über ein WLAN ist eine höhere Bandbreite in der Regel mit höheren Akkukosten verbunden. Das bedeutet, dass 5G in der Regel mehr Energie verbraucht als LTE, was wiederum teurer ist als 3G.
Das bedeutet, dass der zugrunde liegende Funkstatus je nach Funktechnologie variiert. Im Allgemeinen ist die relative Akkubelastung durch die Nachlaufzeit der Statusänderung bei Funkschnittstellen mit höherer Bandbreite jedoch höher. Weitere Informationen zur Tail-Time finden Sie unter Der Radio-Statusautomat.
Gleichzeitig bedeutet die höhere Bandbreite, dass Sie aggressiver vorab laden und so in der gleichen Zeit mehr Daten herunterladen können. Weniger intuitiv ist, dass es auch effizienter ist, das Funkgerät während jeder Übertragungssitzung länger aktiv zu halten, um die Häufigkeit der Aktualisierungen zu verringern, da die Akkukosten in der Endzeit relativ höher sind.
Wenn ein LTE-Funkschnittstelle beispielsweise die doppelte Bandbreite und die doppelten Energiekosten von 3G hat, sollten Sie während jeder Sitzung viermal so viele Daten herunterladen – möglicherweise bis zu 10 MB. Wenn Sie so viele Daten herunterladen, sollten Sie die Auswirkungen des Prefetching auf den verfügbaren lokalen Speicher berücksichtigen und den Prefetch-Cache regelmäßig leeren.
Mit ConnectivityManager
kannst du einen Listener für das Standardnetzwerk registrieren und mit TelephonyManager
einen PhoneStateListener
, um den aktuellen Geräteverbindungstyp zu ermitteln. Sobald der Verbindungstyp bekannt ist, können Sie Ihre prefetching-Abläufe entsprechend anpassen:
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; }
Von der App initiierte Anfragen optimieren
Von Apps initiierte Anfragen erfolgen in der Regel nach einem Zeitplan, z. B. bei einer App, die Protokolle oder Analysen an einen Back-End-Dienst sendet. Berücksichtigen Sie bei der Verarbeitung von App-initiierten Anfragen die Priorität dieser Anfragen, ob sie zusammengefasst werden können und ob sie verschoben werden können, bis das Gerät geladen oder mit einem unbegrenzten Netzwerk verbunden ist. Diese Anfragen können durch sorgfältiges Planen und mithilfe von Bibliotheken wie WorkManager optimiert werden.
Netzwerkanfragen im Batch
Auf einem Mobilgerät verbraucht das Einschalten des Funkschnittstellenmoduls, das Herstellen einer Verbindung und das Aktivhalten des Funkschnittstellenmoduls viel Strom. Daher kann die Verarbeitung einzelner Anfragen zu zufälligen Zeiten viel Energie verbrauchen und die Akkulaufzeit verkürzen. Effizient ist es, mehrere Netzwerkanfragen in eine Warteschlange zu stellen und sie gemeinsam zu verarbeiten. So muss das System nur einmal die Energiekosten für das Einschalten des Funkschnittstellenmoduls bezahlen und erhält trotzdem alle von einer App angeforderten Daten.
WorkManager verwenden
Mit der WorkManager
-Bibliothek können Sie Aufgaben nach einem effizienten Zeitplan ausführen, bei dem berücksichtigt wird, ob bestimmte Bedingungen erfüllt sind, z. B. Netzwerkverfügbarkeit und Betriebsstatus. Angenommen, Sie haben eine Worker
-Unterklasse namens DownloadHeadlinesWorker
, die die neuesten Nachrichtenschlagzeilen abruft. Dieser Worker kann so geplant werden, dass er stündlich ausgeführt wird, sofern das Gerät mit einem unbegrenzten Netzwerk verbunden ist und der Akku des Geräts nicht leer ist. Bei Problemen beim Abrufen der Daten kann eine benutzerdefinierte Wiederholungsstrategie verwendet werden (siehe unten):
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);
Neben WorkManager bietet die Android-Plattform mehrere andere Tools, mit denen Sie einen effizienten Zeitplan für die Ausführung von Netzwerkaufgaben wie Abfragen erstellen können. Weitere Informationen zur Verwendung dieser Tools finden Sie im Leitfaden zur Hintergrundverarbeitung.
Serverseitig initiierte Anfragen optimieren
Serverinitiierte Anfragen erfolgen in der Regel als Reaktion auf eine Benachrichtigung von einem Server. Beispielsweise kann eine App, mit der die neuesten Nachrichtenartikel gelesen werden, eine Benachrichtigung über eine neue Gruppe von Artikeln erhalten, die den Personalisierungseinstellungen des Nutzers entsprechen, und diese dann herunterladen.
Serverupdates mit Firebase Cloud Messaging senden
Firebase Cloud Messaging (FCM) ist ein schlanker Mechanismus, mit dem Daten von einem Server an eine bestimmte App-Instanz übertragen werden. Mit FCM kann Ihr Server Ihre App, die auf einem bestimmten Gerät ausgeführt wird, benachrichtigen, dass neue Daten verfügbar sind.
Im Vergleich zum Polling, bei dem Ihre App regelmäßig den Server anpingen muss, um nach neuen Daten zu fragen, kann Ihre App mit diesem ereignisgesteuerten Modell nur dann eine neue Verbindung herstellen, wenn sie weiß, dass Daten heruntergeladen werden müssen. Das Modell minimiert unnötige Verbindungen und reduziert die Latenz beim Aktualisieren von Informationen in Ihrer App.
FCM wird mit einer dauerhaften TCP/IP-Verbindung implementiert. So wird die Anzahl der dauerhaften Verbindungen minimiert und die Plattform kann die Bandbreite optimieren und die damit verbundenen Auswirkungen auf die Akkulaufzeit minimieren.