Auswirkungen regelmäßiger Updates minimieren

Anfragen, die Ihre App an das Netzwerk sendet, sind eine Hauptursache für die Akkuentladung, da sie stromverbrauchende Mobilfunk- oder WLAN-Funkmodule aktivieren. Zusätzlich zu der Energie, die zum Senden und Empfangen von Paketen erforderlich ist, verbrauchen diese Funkmodule zusätzliche Energie, nur um sich einzuschalten und aktiv zu bleiben. Schon eine einfache Netzwerkanfrage alle 15 Sekunden kann dazu führen, dass das Mobilfunkmodul ständig eingeschaltet bleibt und der Akku schnell leer ist.

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

  • Vom Nutzer initiiert. Aktualisierung basierend auf dem Nutzerverhalten, z. B. einer Geste zum Aktualisieren nach unten ziehen.
  • Von der App initiiert. Regelmäßige Aktualisierung
  • Vom Server initiiert. Aktualisierung als Reaktion auf eine Benachrichtigung von einem Server ausführen.

In diesem Thema werden die einzelnen Punkte erläutert und weitere Möglichkeiten zur Optimierung des Akkuverbrauchs beschrieben.

Vom Nutzer initiierte Anfragen optimieren

Nutzerinitiierte Anfragen erfolgen in der Regel als Reaktion auf ein bestimmtes Nutzerverhalten. In einer App, die zum Lesen der neuesten Nachrichtenartikel verwendet wird, kann der Nutzer beispielsweise durch Ziehen nach unten nach neuen Artikeln suchen. 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 vom Nutzer initiierte Anfragen ignorieren, wenn sie nicht erforderlich sind, z. B. mehrere Gesten zum Aktualisieren nach unten ziehen innerhalb kurzer Zeit, um nach neuen Daten zu suchen, während die aktuellen Daten noch aktuell sind. Wenn jede Anfrage bearbeitet wird, kann dies zu einem erheblichen Stromverbrauch führen, 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 Häufigkeit der Nutzung des Funksenders reduziert.

Cache verwenden

Durch das Caching 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 für neue Anfragen herstellen zu müssen.

Du solltest 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 Ihre App veraltete 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 Datasets verwenden, die andere Apps für Anwendungsfälle wie maschinelles Lernen und Medienwiedergabe verwenden. 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 mehr Daten seltener herunterzuladen

Bei einer Verbindung über ein Mobilfunknetz 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 der zugrunde liegende Funkstatus je nach Funktechnologie variiert, aber im Allgemeinen ist die relative Auswirkung der Nachlaufzeit der Statusänderung auf den Akku bei Funktechnologien mit höherer Bandbreite größer. Weitere Informationen zur Nachlaufzeit finden Sie unter Die Funk-Zustandsmaschine.

Gleichzeitig bedeutet die höhere Bandbreite, dass Sie mehr Daten im Voraus abrufen und mehr Daten im selben Zeitraum herunterladen können. Vielleicht weniger intuitiv: Da die Akkukosten für die Nachlaufzeit relativ hoch sind, ist es auch effizienter, das Funkmodul während jeder Übertragungssitzung länger aktiv zu halten, um die Häufigkeit von Aktualisierungen zu reduzieren.

Wenn ein LTE-Funkmodul beispielsweise die doppelte Bandbreite und die doppelten Energiekosten von 3G hat, solltest du 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 Prefetchings 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 einen PhoneStateListener, um den aktuellen Verbindungstyp des Geräts 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

Von der App initiierte Anfragen erfolgen in der Regel nach einem Zeitplan, z. B. wenn eine App Logs oder Analysen an einen Backend-Dienst sendet. Bei von der App initiierten Anfragen solltest du die Priorität dieser Anfragen berücksichtigen, ob sie zusammengefasst werden können und ob sie verschoben werden können, bis das Gerät aufgeladen wird oder mit einem Netzwerk ohne Datenlimit verbunden ist. Diese Anfragen können durch sorgfältige Planung und die Verwendung von Bibliotheken wie WorkManager optimiert werden.

Netzwerkanfragen im Batch senden

Auf einem Mobilgerät verbraucht das Einschalten des Funkmoduls, das Herstellen einer Verbindung und das Aufrechterhalten der Verbindung viel Energie. Aus diesem Grund kann die Verarbeitung einzelner Anfragen zu zufälligen Zeiten viel Energie verbrauchen und die Akkulaufzeit verkürzen. Ein effizienterer Ansatz besteht darin, eine Reihe von Netzwerkanfragen in eine Warteschlange zu stellen und sie zusammen zu verarbeiten. So muss das System nur einmal die Stromkosten für das Einschalten des Funkmoduls 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, der berücksichtigt, ob bestimmte Bedingungen erfüllt sind, z. B. Netzwerkverfügbarkeit und Energiestatus. Angenommen, Sie haben eine Worker-Unterklasse namens DownloadHeadlinesWorker, die die neuesten Nachrichten abruft. Dieser Worker kann so geplant werden, dass er jede Stunde ausgeführt wird, sofern das Gerät mit einem Netzwerk ohne Datenlimit verbunden ist und der Akkustand des Geräts nicht niedrig ist. Außerdem kann eine benutzerdefinierte Wiederholungsstrategie verwendet werden, falls beim Abrufen der Daten Probleme auftreten, 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 du einen effizienten Zeitplan für die Erledigung von Netzwerkaufgaben wie Polling erstellen kannst. Weitere Informationen zur Verwendung dieser Tools finden Sie im Leitfaden zur Hintergrundverarbeitung.

Vom Server initiierte Anfragen optimieren

Vom Server initiierte Anfragen erfolgen in der Regel als Reaktion auf eine Benachrichtigung von einem Server. Beispielsweise kann eine App, die zum Lesen der neuesten Nachrichtenartikel verwendet wird, eine Benachrichtigung über eine neue Reihe 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 einfacher 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, darüber benachrichtigen, dass neue Daten für sie verfügbar sind.

Im Vergleich zum Polling, bei dem Ihre App den Server regelmäßig pingen muss, um neue Daten abzufragen, ermöglicht dieses ereignisgesteuerte Modell Ihrer App, nur dann eine neue Verbindung herzustellen, wenn sie weiß, dass 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 damit verbundenen Auswirkungen auf die Akkulaufzeit minimieren.