Auswirkungen regelmäßiger Updates minimieren

Anfragen, die deine App an das Netzwerk sendet, sind eine Hauptursache für eine schnelle Akkuentladung, da sie energieverbrauchende Mobilfunk- oder WLAN-Funkschnittstellen aktivieren. Diese Funkschnittstellen benötigen nicht nur die Energie, die zum Senden und Empfangen von Paketen erforderlich ist, sondern verbrauchen auch zusätzliche Energie, wenn sie eingeschaltet und aktiv bleiben. Mit einer einfachen Netzwerkanfrage alle 15 Sekunden kann die Mobilfunkverbindung kontinuierlich aktiviert bleiben und der Akku wird schnell belastet.

Es gibt drei Arten regelmäßiger Updates:

  • Vom Nutzer initiiert. Eine Aktualisierung basierend auf einem bestimmten Nutzerverhalten durchführen, z. B. eine Geste zum Aktualisieren durch Ziehen.
  • Von der App initiiert: Sie führen regelmäßig eine Aktualisierung durch.
  • Vom Server initiiert. Ausführen einer Aktualisierung als Reaktion auf eine Benachrichtigung von einem Server

In diesem Artikel werden diese Punkte näher betrachtet und es werden weitere Optimierungsmöglichkeiten erläutert, um den Akkuverbrauch zu reduzieren.

Vom Nutzer initiierte Anfragen optimieren

Von Nutzern initiierte Anfragen werden normalerweise als Reaktion auf ein bestimmtes Nutzerverhalten ausgeführt. Beispielsweise kann eine App, die zum Lesen der neuesten Nachrichtenartikel verwendet wird, dem Nutzer ermöglichen, mit einer Geste zum Aktualisieren nach neuen Artikeln zu suchen. Mit den folgenden Methoden können Sie auf von Nutzern initiierte Anfragen antworten und gleichzeitig die Netzwerknutzung optimieren.

Nutzeranfragen drosseln

Sie können einige vom Nutzer initiierte Anfragen ignorieren, wenn sie nicht erforderlich sind. Dies können beispielsweise mehrere Gesten zum Aktualisieren durch Ziehen in einem kurzen Zeitraum sein, um zu prüfen, ob neue Daten vorhanden sind, während die aktuellen Daten noch aktuell sind. Wenn Sie auf jede Anfrage reagieren, kann viel Energie verschwendet werden, da das Radio aktiv bleibt. Ein effizienterer Ansatz besteht darin, die vom Nutzer initiierten Anfragen so zu drosseln, dass über einen bestimmten Zeitraum nur eine Anfrage gestellt werden kann. Dadurch verringert sich die Häufigkeit der Mobilfunknutzung.

Cache verwenden

Durch das Caching der App-Daten erstellen Sie eine lokale Kopie der Informationen, auf die Ihre App verweisen muss. Ihre Anwendung kann dann mehrmals auf dieselbe lokale Kopie der Informationen zugreifen, ohne für neue Anfragen eine Netzwerkverbindung herstellen zu müssen.

Sie sollten Daten so offen wie möglich im Cache speichern, einschließlich statischer Ressourcen und On-Demand-Downloads wie Bilder in voller Größe. Sie können HTTP-Cache-Header verwenden, damit Ihre Caching-Strategie nicht dazu führt, dass Ihre Anwendung veraltete Daten anzeigt. Weitere Informationen zum Caching von Netzwerkantworten finden Sie unter Redundante Downloads vermeiden.

Unter Android 11 und höher kann deine App dieselben großen Datasets verwenden, die andere Apps für Anwendungsfälle wie maschinelles Lernen und Medienwiedergabe verwenden. Wenn Ihre Anwendung 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.

Größere Bandbreite verwenden, um mehr Daten seltener herunterzuladen

Bei einer Verbindung über ein WLAN führt eine höhere Bandbreite im Allgemeinen zu höheren Akkukosten, was bedeutet, dass 5G in der Regel mehr Energie verbraucht als LTE, das wiederum teurer ist als 3G.

Das bedeutet, dass der zugrunde liegende Funkzustand zwar je nach Funktechnologie variiert, die relativen Auswirkungen der Zustandsänderung auf den Akku im Allgemeinen bei Funkverbindungen mit höherer Bandbreite größer sind. Weitere Informationen zur Tail-Time finden Sie unter The Radio State Machine.

Gleichzeitig bedeutet die höhere Bandbreite, dass Sie einen aggressiveren Vorabruf durchführen können, sodass mehr Daten gleichzeitig heruntergeladen werden. Da die Kosten für die Extremwert-Akkulaufzeit relativ höher sind, ist es vielleicht auch effizienter, das Funkgerät während jeder Übertragungssitzung über längere Zeit aktiv zu halten, um die Aktualisierungshäufigkeit zu verringern.

Wenn ein LTE-Funkgerät beispielsweise die Bandbreite und die Energiekosten von 3G verdoppelt, sollten Sie pro Sitzung viermal so viele Daten oder potenziell auch bis zu 10 MB herunterladen. Wenn Sie so viele Daten herunterladen, sollten Sie die Auswirkungen des Prefetches auf den verfügbaren lokalen Speicher berücksichtigen und den Prefetch-Cache regelmäßig leeren.

Sie können ConnectivityManager verwenden, um einen Listener für das Standardnetzwerk zu registrieren, und TelephonyManager, um einen PhoneStateListener zu registrieren, um den aktuellen Geräteverbindungstyp zu ermitteln. Sobald der Verbindungstyp bekannt ist, können Sie Ihre Prefetch-Routinen entsprechend ändern:

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 normalerweise nach einem Zeitplan, z. B. eine Anwendung, die Logs oder Analysen an einen Back-End-Dienst sendet. Berücksichtigen Sie bei der Verarbeitung von durch Apps initiierten Anfragen die Priorität dieser Anfragen, ob sie in Stapeln zusammengefasst werden können und ob sie verschoben werden können, bis das Gerät aufgeladen wird oder mit einem nicht getakteten Netzwerk verbunden ist. Diese Anfragen können durch eine sorgfältige Planung und die Verwendung von Bibliotheken wie WorkManager optimiert werden.

Batch-Netzwerkanfragen

Auf einem Mobilgerät verbraucht das Einschalten des Radios, das Herstellen einer Verbindung und das Aktivieren des Funkgeräts viel Strom. Aus diesem Grund kann die Verarbeitung einzelner Anfragen zu zufälligen Zeiten erheblichen Strom verbrauchen und die Akkulaufzeit verkürzen. Ein effizienterer Ansatz besteht darin, eine Reihe von Netzwerkanfragen in die Warteschlange zu stellen und zusammen zu verarbeiten. So kann das System die Energiekosten bezahlen, die für das einmalige Einschalten des Radios anfallen, und dennoch alle von einer App angeforderten Daten abrufen.

WorkManager verwenden

Sie können die Bibliothek WorkManager verwenden, um nach einem effizienten Zeitplan zu arbeiten, bei dem berücksichtigt wird, ob bestimmte Bedingungen erfüllt sind, z. B. Netzwerkverfügbarkeit und Energiestatus. Angenommen, Sie haben eine abgeleitete Worker-Klasse mit dem Namen DownloadHeadlinesWorker, die die neuesten Schlagzeilen abruft. Dieser Worker kann so geplant werden, dass er stündlich ausgeführt wird, sofern das Gerät mit einem kostenlosen Netzwerk verbunden ist und der Akku des Geräts nicht niedrig ist. Wenn beim Abrufen der Daten Probleme auftreten, können Sie eine benutzerdefinierte Wiederholungsstrategie anwenden:

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

Vom Server initiierte Anfragen optimieren

Vom Server initiierte Anfragen werden normalerweise als Reaktion auf eine Benachrichtigung eines Servers ausgeführt. 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, die dann heruntergeladen wird.

Serverupdates mit Firebase Cloud Messaging senden

Firebase Cloud Messaging (FCM) ist ein einfacher Mechanismus zur Übertragung von Daten von einem Server an eine bestimmte Anwendungsinstanz. Mit FCM kann Ihr Server Ihre auf einem bestimmten Gerät ausgeführte App darüber informieren, dass neue Daten für sie verfügbar sind.

Im Gegensatz zu Abfragen, bei denen Ihre Anwendung regelmäßig den Server anpingen muss, um neue Daten abzufragen, ermöglicht dieses ereignisgesteuerte Modell Ihrer Anwendung, nur dann eine neue Verbindung herzustellen, wenn sie weiß, dass Daten heruntergeladen werden können. Das Modell minimiert unnötige Verbindungen und verringert die Latenz beim Aktualisieren von Informationen in Ihrer Anwendung.

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.