Auswirkungen regelmäßiger Updates minimieren

Anfragen, die Ihre App an das Netzwerk stellt, sind eine Hauptursache für eine schnelle Akkuentladung, da sie energieverbrauchende Mobilfunk- oder WLAN-Funkschnittstellen aktivieren. Neben der Leistung, die zum Senden und Empfangen von Paketen erforderlich ist, verbrauchen diese Funkschnittstellen zusätzliche Energie, wenn sie eingeschaltet werden und aktiv bleiben. Mit einer einfachen Netzwerkanfrage alle 15 Sekunden kann die Mobilfunkverbindung kontinuierlich eingeschaltet bleiben und schnell Akkuleistung verbrauchen.

Es gibt drei allgemeine Arten regelmäßiger Updates:

  • Vom Nutzer initiiert. Durchführung einer Aktualisierung auf der Grundlage eines bestimmten Nutzerverhaltens, z. B. eine Geste zum Herunterladen zum Aktualisieren
  • Von der App initiiert. Regelmäßige Aktualisierung durchführen.
  • Vom Server initiiert: Sie führt ein Update als Reaktion auf eine Benachrichtigung von einem Server durch.

In diesem Thema werden alle diese Aspekte betrachtet und weitere Möglichkeiten erörtert, wie sie optimiert werden können, um die Entladung des Akkus zu reduzieren.

Vom Nutzer initiierte Anfragen optimieren

Vom Nutzer initiierte Anfragen werden in der Regel als Reaktion auf ein bestimmtes Nutzerverhalten ausgelöst. Beispielsweise kann eine App, mit der die neuesten Nachrichtenartikel gelesen werden, dem Nutzer ermöglichen, eine Geste zum Aktualisieren durch Ziehen auszuführen, um nach neuen Artikeln zu suchen. Mit den folgenden Methoden können Sie auf vom Nutzer initiierte Anfragen antworten und gleichzeitig die Netzwerknutzung optimieren.

Nutzeranfragen drosseln

Sie können einige vom Nutzer initiierte Anfragen ignorieren, wenn sie nicht benötigt werden, z. B. mehrere Gesten zum Herunterladen zum Aktualisieren innerhalb eines kurzen Zeitraums, um nach neuen Daten zu suchen, solange die aktuellen Daten noch aktuell sind. Je nachdem, auf welche Anfragen Sie reagieren, könnte dies eine erhebliche Menge an Energie verschwenden, da das Radio aktiv bleibt. Ein effizienterer Ansatz besteht darin, die vom Nutzer initiierten Anfragen zu drosseln, sodass innerhalb eines bestimmten Zeitraums nur eine Anfrage gestellt werden kann. Dadurch wird die Häufigkeit der Funkübertragung reduziert.

Cache verwenden

Durch das Caching der Daten Ihrer Anwendung erstellen Sie eine lokale Kopie der Informationen, auf die Ihre Anwendung verweisen muss. Ihre Anwendung kann dann mehrmals auf dieselbe lokale Kopie dieser 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 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 nutzen. Wenn Ihre Anwendung auf ein freigegebenes Dataset zugreifen muss, kann sie zuerst nach einer im Cache gespeicherten Version suchen, bevor versucht wird, eine neue Kopie herunterzuladen. Weitere Informationen zu freigegebenen Datasets finden Sie unter Auf freigegebene Datasets zugreifen.

Höhere Bandbreite verwenden, um mehr Daten seltener herunterladen

Bei einer Verbindung über eine drahtlose Funkschnittstelle hat eine höhere Bandbreite in der Regel den Preis einer höheren Akkukosten zur Folge. Dies bedeutet, dass 5G in der Regel mehr Energie verbraucht als LTE, was wiederum teurer ist als 3G.

Das bedeutet, dass zwar der zugrunde liegende Funkzustand je nach Funktechnologie variiert, aber im Allgemeinen ist die relative Auswirkung der Batterie bei Funkschnittstellen mit höherer Bandbreite im Allgemeinen größer. Weitere Informationen zur Endzeit finden Sie unter Der Funkzustandsautomat.

Gleichzeitig bedeutet die höhere Bandbreite, dass Sie aggressiver im Voraus abrufen und mehr Daten gleichzeitig herunterladen können. Da die Extremwert-Akkukosten relativ hoch sind, ist es vielleicht weniger intuitiv, weil es auch effizienter ist, das Funkgerät während jeder Übertragungssitzung länger aktiv zu halten, um die Häufigkeit von Updates zu reduzieren.

Wenn eine LTE-Funkschnittstelle beispielsweise doppelt so viel Bandbreite und doppelt so viel Energie hat wie 3G, sollten Sie pro Sitzung viermal so viele Daten herunterladen – oder eventuell auch bis zu 10 MB. Wenn Sie so viele Daten herunterladen, ist es wichtig, die Auswirkungen des Prefetch-Vorgangs auf den verfügbaren lokalen Speicher zu berücksichtigen und den Prefetch-Cache regelmäßig zu leeren.

Sie können den ConnectivityManager verwenden, um einen Listener für das Standardnetzwerk zu registrieren, und den TelephonyManager, um ein PhoneStateListener zu registrieren, um den aktuellen Geräteverbindungstyp zu ermitteln. Sobald der Verbindungstyp bekannt ist, können Sie die 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 Apps initiierte Anfragen optimieren

Von der App initiierte Anfragen werden in der Regel nach einem Zeitplan ausgeführt, z. B. von einer Anwendung, die Protokolle oder Analysen an einen Back-End-Dienst sendet. Berücksichtigen Sie bei der Verarbeitung von von der App initiierten Anfragen die Priorität dieser Anfragen, ob sie zusammen in Batches zusammengefasst werden können und ob sie bis zum Aufladen des Geräts oder zur Verbindung mit einem kostenlosen Netzwerk zurückgestellt werden können. Diese Anfragen können mit sorgfältiger Planung und mit Bibliotheken wie WorkManager optimiert werden.

Batch-Netzwerkanfragen

Auf einem Mobilgerät verbraucht das Einschalten des Funkgeräts, das Herstellen einer Verbindung und das Aktivieren des Funkgeräts viel Energie. Aus diesem Grund kann die Verarbeitung einzelner Anfragen zu zufälligen Zeiten viel Strom verbrauchen und die Akkulaufzeit verringern. Ein effizienterer Ansatz besteht darin, eine Reihe von Netzwerkanfragen in die Warteschlange zu stellen und sie zusammen zu verarbeiten. Dadurch kann das System die Stromkosten für das einmalige Einschalten des Radios bezahlen und erhält trotzdem alle von einer App angeforderten Daten.

WorkManager verwenden

Mit der Bibliothek WorkManager können Sie Arbeiten nach einem effizienten Zeitplan ausführen, der berücksichtigt, ob bestimmte Bedingungen wie Netzwerkverfügbarkeit und Energiestatus erfüllt sind. Angenommen, Sie haben eine Worker-Unterklasse namens DownloadHeadlinesWorker, die die neuesten Schlagzeilen abruft. Für diesen Worker kann eine stündliche Ausführung geplant werden, sofern das Gerät mit einem kostenlosen Netzwerk verbunden ist und der Akkustand des Geräts nicht niedrig ist. Für den Fall, dass beim Abrufen der Daten Probleme auftreten, kann eine benutzerdefinierte Wiederholungsstrategie verwendet werden, wie unten dargestellt:

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);

Zusätzlich zu WorkManager bietet die Android-Plattform mehrere weitere Tools, mit denen Sie einen effizienten Zeitplan für die Durchführung von Netzwerkaufgaben wie Abfragen 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 von einem Server ausgeführt. Beispielsweise kann eine App, die zum Lesen der neuesten Nachrichtenartikel verwendet wird, eine Benachrichtigung über eine neue Gruppe von Artikeln erhalten, die den Personalisierungseinstellungen des Nutzers entspricht, die dann heruntergeladen wird.

Serverupdates mit Firebase Cloud Messaging senden

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

Im Gegensatz zum Polling, bei dem Ihre App regelmäßig einen Ping an den Server senden muss, um neue Daten abzufragen, ermöglicht dieses ereignisgesteuerte Modell der App nur dann eine neue Verbindung, wenn ihr bekannt ist, dass Daten zum Herunterladen vorhanden sind. Das Modell minimiert unnötige Verbindungen und verringert die Latenz beim Aktualisieren von Informationen in Ihrer Anwendung.

FCM wird über eine persistente TCP/IP-Verbindung implementiert. Dies minimiert die Anzahl der persistenten Verbindungen und ermöglicht der Plattform, die Bandbreite zu optimieren und die damit verbundenen Auswirkungen auf die Akkulaufzeit zu minimieren.