Auswirkungen regelmäßiger Updates minimieren

Anfragen, die Ihre App an das Netzwerk sendet, sind eine Hauptursache für die Entladung des Akkus, da sie stromfressende Mobilfunk- oder WLAN-Module aktivieren. Neben dem Strom, der zum Senden und Empfangen von Paketen benötigt wird, verbrauchen diese Funkmodule zusätzlichen Strom, nur um sich einzuschalten und aktiv zu bleiben. Eine einfache Netzwerkanfrage alle 15 Sekunden kann dazu führen, dass das Mobilfunkmodem ständig aktiv ist und der Akku schnell entladen wird.

Es gibt drei allgemeine Arten von regelmäßigen Updates:

  • Vom Nutzer initiiert. Aktualisierung aufgrund eines Nutzerverhaltens, z. B. einer Pull-to-Refresh-Geste.
  • Von der App initiiert: Regelmäßige Aktualisierung
  • Serverinitiiert: Aktualisierung als Reaktion auf eine Benachrichtigung von einem Server durchführen

In diesem Thema werden die einzelnen Punkte genauer betrachtet und zusätzliche Möglichkeiten zur Optimierung zur Verringerung der Akkuentladung erläutert.

Vom Nutzer initiierte Anfragen optimieren

Nutzerinitiierte Anfragen erfolgen in der Regel als Reaktion auf ein bestimmtes Nutzerverhalten. In einer App, mit der die neuesten Nachrichtenartikel gelesen werden, kann der Nutzer beispielsweise durch Ziehen nach unten prüfen, ob es neue Artikel gibt. Mit den folgenden Techniken können Sie auf vom Nutzer initiierte Anfragen reagieren und gleichzeitig die Netzwerknutzung optimieren.

Nutzeranfragen drosseln

Möglicherweise möchten Sie einige nutzerinitiierte Anfragen ignorieren, wenn sie nicht erforderlich sind, z. B. mehrere Pull-to-Refresh-Gesten innerhalb kurzer Zeit, um nach neuen Daten zu suchen, während die aktuellen Daten noch aktuell sind. Wenn jede Anfrage bearbeitet wird, kann das viel Strom verbrauchen, da das Funkmodul aktiv bleibt. Ein effizienterer Ansatz besteht darin, die vom Nutzer initiierten Anfragen zu drosseln, sodass über einen bestimmten Zeitraum nur eine Anfrage gestellt werden kann. Dadurch wird die Funkverbindung seltener verwendet.

Cache verwenden

Durch das Zwischenspeichern der Daten Ihrer App 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 öffnen zu müssen, um neue Anfragen zu stellen.

Sie sollten Daten so aggressiv wie möglich im Cache speichern, einschließlich statischer Ressourcen und On-Demand-Downloads wie Bilder in voller Größe. Mit HTTP-Cache-Headern können Sie dafür sorgen, dass Ihre Caching-Strategie nicht dazu führt, dass in Ihrer App veraltete Daten angezeigt werden. Weitere Informationen zum Caching von Netzwerkantworten finden Sie unter Redundante Downloads vermeiden.

Unter Android 11 und höher kann Ihre App dieselben großen Datasets verwenden, die andere Apps für Anwendungsfälle wie maschinelles Lernen und Medienwiedergabe nutzen. Wenn Ihre App auf ein freigegebenes Dataset zugreifen muss, kann sie zuerst nach einer im Cache gespeicherten Version suchen, bevor sie versucht, eine neue Kopie herunterzuladen. Weitere Informationen zu freigegebenen Datasets finden Sie unter Auf freigegebene Datasets zugreifen.

Höhere Bandbreite verwenden, um seltener mehr Daten herunterzuladen

Bei einer Verbindung über ein drahtloses Funknetzwerk geht eine höhere Bandbreite in der Regel mit einem höheren Akkuverbrauch einher. Das bedeutet, dass 5G in der Regel mehr Energie verbraucht als LTE, was wiederum mehr Energie verbraucht als 3G.

Das bedeutet, dass sich die relative Akku-Auswirkung der Tail-Time für den Statuswechsel bei Funkgeräten mit höherer Bandbreite in der Regel stärker auswirkt, obwohl der zugrunde liegende Funkstatus je nach Funktechnologie variiert. Weitere Informationen zur Tail-Time finden Sie unter Der Funkstatusautomat.

Gleichzeitig können Sie durch die höhere Bandbreite aggressiver vorab abrufen und in derselben Zeit mehr Daten herunterladen. Weniger intuitiv ist, dass es aufgrund der relativ hohen Akkukosten in der Tail-Time auch effizienter ist, das Funkgerät während jeder Übertragungssitzung länger aktiv zu lassen, um die Häufigkeit von Updates zu verringern.

Wenn ein LTE-Funkmodul beispielsweise die doppelte Bandbreite und den doppelten Energieverbrauch von 3G hat, sollten Sie während jeder Sitzung viermal so viele Daten herunterladen – oder 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 können Sie einen Listener für das Standardnetzwerk registrieren und mit TelephonyManager ein PhoneStateListener registrieren, um den aktuellen Gerätetyp zu ermitteln. Sobald der Verbindungstyp bekannt ist, können Sie Ihre Prefetching-Routinen 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

App-initiierte Anfragen erfolgen in der Regel nach einem Zeitplan, z. B. wenn eine App Protokolle oder Analysen an einen Backend-Dienst sendet. Berücksichtigen Sie bei App-initiierten Anfragen die Priorität dieser Anfragen, ob sie zusammengefasst werden können und ob sie aufgeschoben werden können, bis das Gerät geladen wird oder mit einem Netzwerk mit unbegrenztem Datenvolumen verbunden ist. Diese Anfragen lassen sich durch sorgfältige Planung und die Verwendung von Bibliotheken wie WorkManager optimieren.

Netzwerkanfragen in Batches senden

Auf einem Mobilgerät verbraucht das Einschalten des Funkmoduls, das Herstellen einer Verbindung und das Aufrechterhalten des Funkmoduls viel Energie. Aus diesem Grund kann die Verarbeitung einzelner Anfragen zu zufälligen Zeiten viel Strom verbrauchen und die Akkulaufzeit verkürzen. Ein effizienterer Ansatz besteht darin, eine Reihe von Netzwerkanfragen in die Warteschlange zu stellen und gemeinsam zu verarbeiten. So muss das System die Stromkosten für das Einschalten des Funkmoduls nur einmal bezahlen und erhält trotzdem alle von einer App angeforderten Daten.

WorkManager verwenden

Mit der WorkManager-Bibliothek können Sie Aufgaben in einem effizienten Zeitplan ausführen, der berücksichtigt, ob bestimmte Bedingungen erfüllt sind, z. B. die Netzwerkverfügbarkeit und der Akkustatus. Angenommen, Sie haben eine Worker-Unterklasse namens DownloadHeadlinesWorker, die die neuesten Schlagzeilen abruft. Dieser Worker kann stündlich ausgeführt werden, sofern das Gerät mit einem Netzwerk mit unbegrenztem Datenvolumen verbunden ist und der Akku des Geräts nicht leer ist. Bei Problemen beim Abrufen der Daten kann eine benutzerdefinierte Wiederholungsstrategie verwendet werden, wie unten gezeigt:

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 dem Polling erstellen können. Weitere Informationen zur Verwendung dieser Tools finden Sie im Leitfaden zur Hintergrundverarbeitung.

Vom Server initiierte Anfragen optimieren

Serverinitiierte Anfragen erfolgen in der Regel als Reaktion auf eine Benachrichtigung von einem Server. Eine App, mit der die neuesten Nachrichtenartikel gelesen werden, kann beispielsweise eine Benachrichtigung über eine neue Gruppe von Artikeln erhalten, die den Personalisierungseinstellungen des Nutzers entsprechen, und diese dann herunterladen.

Serveraktualisierungen mit Firebase Cloud Messaging senden

Firebase Cloud Messaging (FCM) ist ein einfacher Mechanismus zum Übertragen von Daten von einem Server an eine bestimmte App-Instanz. Mit FCM kann Ihr Server Ihre App, die auf einem bestimmten Gerät ausgeführt wird, darüber informieren, dass neue Daten für sie verfügbar sind.

Im Gegensatz zum Polling, bei dem Ihre App den Server regelmäßig anpingt, um nach neuen Daten zu suchen, kann Ihre App bei diesem ereignisgesteuerten Modell nur dann eine neue Verbindung herstellen, wenn Daten zum Herunterladen vorhanden sind. Das Modell minimiert unnötige Verbindungen und reduziert die Latenz beim Aktualisieren von Informationen in Ihrer App.

FCM wird über eine dauerhafte TCP/IP-Verbindung implementiert. Dadurch wird die Anzahl der persistenten Verbindungen minimiert und die Plattform kann die Bandbreite optimieren und die Auswirkungen auf die Akkulaufzeit minimieren.