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-Funkmodule 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 Mobilfunkmodul kontinuierlich eingeschaltet bleibt und den Akku schnell entlädt.

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

  • Vom Nutzer initiiert Ein Update wird aufgrund eines Nutzerverhaltens ausgeführt, z. B. durch eine Pull-to-Refresh-Geste.
  • Von der App initiiert Ein Update wird regelmäßig ausgeführt.
  • Vom Server initiiert Ein Update wird als Reaktion auf eine Benachrichtigung von einem Server ausgeführt.

In diesem Thema werden diese Arten von Updates näher betrachtet und zusätzliche Möglichkeiten zur Optimierung besprochen, um die Akkuentladung zu reduzieren.

Vom Nutzer initiierte Anfragen optimieren

Vom Nutzer initiierte Anfragen werden in der Regel als Reaktion auf ein bestimmtes Nutzerverhalten gesendet. Beispielsweise kann eine App, mit der die neuesten Nachrichtenartikel gelesen werden, dem Nutzer die Möglichkeit bieten, eine Pull-to-Refresh-Geste auszuführen, um nach neuen Artikeln zu 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 Pull-to-Refresh-Gesten in kurzer Zeit, um nach neuen Daten zu suchen, während die aktuellen Daten noch aktuell sind. Wenn Sie auf jede Anfrage reagieren, kann eine erhebliche Menge an Strom verbraucht werden, da das Funkmodul aktiv bleibt. Ein effizienterer Ansatz besteht darin, die vom Nutzer initiierten Anfragen zu drosseln, sodass nur eine Anfrage über einen bestimmten Zeitraum gesendet werden kann. Dadurch wird die Nutzung des Funkmoduls reduziert.

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 Netzwerk Verbindung öffnen zu müssen, um neue Anfragen zu senden.

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 App durch Ihre 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 auch von anderen Apps für Anwendungsfälle wie maschinelles Lernen und Medienwiedergabe verwendet werden. Wenn Ihre App auf einen freigegebenen Datensatz zugreifen muss, kann sie zuerst nach einer im Cache gespeicherten Version suchen, bevor sie versucht, eine neue Kopie herunterzuladen. Weitere Informationen zu freigegebenen Datensätzen finden Sie unter Auf freigegebene Datensätze zugreifen.

Höhere Bandbreite verwenden, um seltener mehr Daten herunterzuladen

Bei einer Verbindung über ein Funkmodul mit höherer Bandbreite sind in der Regel auch die Akkukosten höher. Das bedeutet, dass 5G in der Regel mehr Energie verbraucht als LTE, was wiederum teurer ist als 3G.

Das bedeutet, dass sich der zugrunde liegende Funkmodulstatus je nach der Funktechnologie unterscheidet. Im Allgemeinen ist die relative Auswirkung der Status änderung auf den Akku bei Funkmodulen mit höherer Bandbreite jedoch größer. Weitere Informationen zur Tail-Time finden Sie unter Der Funkmodulstatus automat.

Gleichzeitig bedeutet die höhere Bandbreite, dass Sie aggressiver vorab abrufen können und in derselben Zeit mehr Daten herunterladen können. Weniger intuitiv ist, dass es aufgrund der relativ höheren Akkukosten für die Tail-Time auch effizienter ist, das Funkmodul während jeder Übertragungssitzung länger aktiv zu halten, um die Häufigkeit von Updates zu reduzieren.

Wenn ein LTE-Funkmodul beispielsweise die doppelte Bandbreite und die doppelten Energiekosten von 3G hat, sollten Sie bei jeder Sitzung viermal so viele Daten herunterladen – oder potenziell bis zu 10 MB. Wenn Sie so viele Daten herunterladen, ist es wichtig, die Auswirkungen des Vorabrufs auf den verfügbaren lokalen Speicher zu berücksichtigen und den Vorabruf-Cache regelmäßig zu leeren.

Mit dem ConnectivityManager können Sie einen Listener für das Standardnetzwerk registrieren und mit dem TelephonyManager einen PhoneStateListener registrieren, um den aktuellen Verbindungstyp des Geräts zu ermitteln. Sobald der Verbindungstyp bekannt ist, können Sie Ihre Vorabrufroutinen 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 der App initiierte Anfragen werden in der Regel nach einem Zeitplan gesendet, z. B. wenn eine App Logs oder Analysen an einen Back-End-Dienst sendet. Bei von der App initiierten Anfragen sollten Sie die Priorität dieser Anfragen berücksichtigen, ob sie zusammengefasst werden können und ob sie verschoben werden können, bis das Gerät geladen wird oder mit einem Netzwerk mit unbegrenztem Datentarif verbunden ist. Diese Anfragen können durch sorgfältiges Planen und die Verwendung von Bibliotheken wie WorkManageroptimiert werden.

Netzwerkanfragen zusammenfassen

Auf einem Mobilgerät verbraucht das Einschalten des Funkmoduls, das Herstellen einer Verbindung, und das Aktivhalten des Funkmoduls viel Strom. Aus diesem Grund kann die Verarbeitung einzelner Anfragen zu zufälligen Zeiten erhebliche Mengen an Strom verbrauchen und die Akkulaufzeit verkürzen. Ein effizienterer Ansatz besteht darin, eine Reihe von Netzwerk anfragen in die Warteschlange zu stellen und gemeinsam zu verarbeiten. So muss das System die Energie kosten 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 nach einem effizienten Zeitplan ausführen bei dem berücksichtigt wird, ob bestimmte Bedingungen erfüllt sind, z. B. Netzwerkverfügbarkeit und Akkustatus. Angenommen, Sie haben eine Worker Unterklasse namens DownloadHeadlinesWorker die die neuesten Nachrichtenüberschriften abruft. Dieser Worker kann so geplant werden, dass er stündlich ausgeführt wird, sofern das Gerät mit einem Netzwerk mit unbegrenztem Datentarif verbunden ist und der Akkustand des Geräts nicht niedrig ist. Wenn beim Abrufen der Daten Probleme auftreten, wird eine benutzerdefinierte Wiederholungsstrategie verwendet, 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 das 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 in der Regel als Reaktion auf eine Benachrichtigung von einem Server gesendet. Beispielsweise kann eine App, mit der die neuesten Nachrichtenartikel gelesen werden, 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 zum Übertragen von Daten von einem Server an eine bestimmte App-Instanz. Mit FCM kann Ihr Server Ihre App auf einem bestimmten Gerät benachrichtigen, dass neue Daten für sie verfügbar sind.

Im Vergleich zum Polling, bei dem Ihre App regelmäßig den Server pingen muss, um nach neuen Daten zu suchen, 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 über eine persistente 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.