Minimalizuj wpływ regularnych aktualizacji

Żądania wysyłane przez aplikację do sieci są główną przyczyną wyczerpywania się baterii bo włączają zużywające energię sieci komórkowe lub Wi-Fi. Oprócz energii potrzebnej do wysyłania i odbierania pakietów te urządzenia zużywają dodatkową energię na włączanie i utrzymywanie w stanie gotowości. Coś tak prostego jak żądanie sieciowe co 15 sekunda może powodować ciągłe działanie radia mobilnego i szybko wyczerpać baterię .

Istnieją 3 ogólne typy regularnych aktualizacji:

  • Inicjowane przez użytkownika. wykonywanie aktualizacji na podstawie pewnych działań użytkownika, takich jak gest przeciągania w dół w celu odświeżenia;
  • Inicjowane przez aplikację. Cykliczne aktualizacje
  • Inicjowane przez serwer. Wykonuję aktualizację w odpowiedzi na powiadomienie z serwer.

W tym temacie omawiamy każdy z tych sposobów i omawiamy dodatkowe sposoby, w jakie można je są zoptymalizowane pod kątem mniejszego zużycia baterii.

Optymalizacja żądań inicjowanych przez użytkownika

Żądania inicjowane przez użytkownika zazwyczaj mają miejsce w reakcji na pewne zachowania użytkownika. Dla: Aplikacja używana do czytania najnowszych wiadomości może na przykład umożliwiać użytkownikowi Wykonaj gest „przeciągnij, by odświeżyć” i sprawdź, czy są nowe artykuły. Za pomocą następujące techniki reagowania na żądania inicjowane przez użytkownika przy jednoczesnej optymalizacji przez sieć.

Ograniczanie żądań użytkowników

Możesz zignorować niektóre inicjowane przez użytkownika żądania, jeśli nie są potrzebne, np. wielokrotne gesty przeciągania w dół w celu odświeżenia, wykonywane w krótkim czasie, aby sprawdzić, czy nie ma nowych danych, gdy aktualne dane są jeszcze aktualne. Działanie w każdym może zmarnować znaczną ilość energii, utrzymując aktywność radia. O skuteczniejszym sposobem jest ograniczenie liczby żądań zainicjowanych przez użytkownika, w danym okresie można przesłać tylko jedno żądanie, co zmniejsza częstotliwość i telefonu.

Korzystanie z pamięci podręcznej

Buforowanie danych aplikacji powoduje utworzenie lokalnej kopii informacji do których aplikacja musi się odwoływać. Aplikacja będzie miała wtedy dostęp do tego samego pliku lokalnego kopiować informacji kilka razy bez konieczności otwierania sieci; do wysyłania nowych żądań.

Przechowuj dane w pamięci podręcznej tak agresywnie, jak to możliwe, w tym dane statyczne zasobów i plików do pobrania na żądanie, takich jak obrazy w pełnym rozmiarze. Możesz używać nagłówków pamięci podręcznej HTTP, aby mieć pewność, że Twoja strategia dotycząca pamięci podręcznej nie spowoduje wyświetlania przez aplikację nieaktualnych danych. Więcej informacji o buforowaniu odpowiedzi sieciowych znajdziesz w artykule Unikaj nadmiarowych elementów pobierania.

Na Androidzie 11 i nowszych aplikacja może korzystać z tych samych dużych zbiorów danych, z których korzystają inne do zastosowań takich jak uczenie maszynowe czy odtwarzanie multimediów. Gdy aplikacja potrzebuje dostępu do udostępnionego zbioru danych, może najpierw sprawdzić wersję w pamięci podręcznej przed próbą pobrania nowej kopii. Aby dowiedzieć się więcej o udostępnionych zbiorach danych, Więcej informacji znajdziesz w artykule na temat uzyskiwania dostępu do udostępnionych zbiorów danych.

Korzystanie z większej przepustowości w celu rzadszego pobierania większej ilości danych

Przy połączeniu przez sieć bezprzewodową wyższą przepustowość ma zwykle wyższy koszt baterii, co oznacza, że 5G zużywa zwykle więcej energii niż LTE, które z kolei jest droższe od sieci 3G.

Oznacza to, że chociaż bazowy stan radia różni się w zależności od technologia radiowa, ogólnie rzecz biorąc, względny wpływ baterii danego państwa na baterię jest dłuższy w przypadku systemów radiowych o większej przepustowości. Więcej informacji o czasie oczekiwania znajdziesz w artykule Automat stanów radiowych (w języku angielskim).

Jednocześnie większa przepustowość oznacza, że możesz szybciej pobierać dane wstępne, pobierając więcej danych w tym samym czasie. Może mniej ponieważ koszt akumulatora przy obrocie czasowym jest stosunkowo wyższy, efektywniejsze, aby radio było aktywne przez dłuższy czas podczas każdego przesyłania , aby zmniejszyć częstotliwość aktualizacji.

Jeśli na przykład radio LTE ma dwukrotnie większą przepustowość i dwa razy wyższe koszty energii niż 3G, podczas każdej sesji należy pobrać 4 razy więcej danych, czyli potencjalnie nawet 10 MB. Przy pobieraniu tak dużej ilości danych ważne jest, weź pod uwagę wpływ pobierania z wyprzedzeniem na dostępną pamięć lokalną i opróżnij regularnie korzystać z pamięci podręcznej pobierania z wyprzedzeniem.

Za pomocą ConnectivityManager, aby się zarejestrować detektor sieci domyślnej, TelephonyManager, aby się zarejestrować PhoneStateListener do i określanie bieżącego typu połączenia urządzenia. Gdy znasz już typ połączenia, możesz odpowiednio zmodyfikować procedury pobierania z wyprzedzeniem:

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

Optymalizowanie żądań inicjowanych przez aplikację

Żądania inicjowane przez aplikację zwykle są realizowane zgodnie z harmonogramem. Na przykład aplikacja wysyła logów ani statystyk do usługi backendu. Gdy mamy do czynienia z ruchem inicjowanym przez aplikację zastanów się nad priorytetem tych żądań, czy mogą być grupowane razem oraz to, czy można je odroczyć do czasu ładowania urządzenia lub masz połączenie z siecią bez pomiaru. Te żądania można zoptymalizować, stosując ostrożne harmonogramowanie i korzystając z bibliotek takich jak WorkManager.

Zbiorcze żądania sieciowe

Na urządzeniu mobilnym włączanie radia, nawiązywanie połączenia i wybudzanie radia zużywa dużo energii. Z tego powodu przetwarzanie poszczególnych żądań w losowych momentach może znacznie zużywać energię i skrócić czas pracy na baterii. Efektywniejszym sposobem jest dodanie zbioru sieci do kolejki i przetwarzania ich razem. Dzięki temu system dostarcza energię wystarczy jednorazowe włączenie radia, aby nadal otrzymywać wszystkie dane, aplikacji.

Używanie WorkManagera

Możesz korzystać z biblioteki WorkManager, aby efektywnie pracować i wykonywać pracę który sprawdza, czy są spełnione określone warunki, np. dostępność sieci i stan zasilania. Załóżmy na przykład, że masz Została wywołana podklasa Worker DownloadHeadlinesWorker, który pobiera najnowsze nagłówki wiadomości. Ta instancja robocza można zaplanować uruchamianie co godzinę, pod warunkiem że urządzenie jest podłączone sieć bez pomiaru, a bateria urządzenia nie jest słaba, ze spersonalizowaną strategią ponawiania prób; jeśli masz problemy z pobraniem danych, jak pokazano poniżej:

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

Oprócz WorkManager platforma Android oferuje kilka innych narzędzi: aby utworzyć efektywny harmonogram wykonywania zadań związanych z siecią, takich jak jako sondy. Więcej informacji o korzystaniu z tych narzędzi znajdziesz w Przewodnik po przetwarzaniu w tle.

Optymalizuj żądania inicjowane przez serwer

Żądania inicjowane przez serwer występują zwykle w odpowiedzi na powiadomienie z serwera. Na przykład aplikacja używana do czytania najnowszych wiadomości może otrzymywać powiadomienia o nowej grupie artykułów pasujących do ustawień personalizacji, które następnie są pobierane.

Wysyłaj aktualizacje serwera za pomocą Komunikacji w chmurze Firebase (FCM)

Komunikacja w chmurze Firebase (FCM) jest niewielkim Mechanizm służący do przesyłania danych z serwera do konkretnej instancji aplikacji. Za pomocą FCM serwer może powiadomić aplikację działającą na konkretnym urządzeniu, że są dla niej dostępne nowe dane.

W porównaniu do ankiet, w których aplikacja musi regularnie wysyłać pingi do serwera w celu wysłania zapytań. nowych danych, a model oparty na zdarzeniach pozwoli aplikacji utworzyć nowe połączenie, tylko wtedy, gdy wie, że są dane do pobrania. Model minimalizuje niepotrzebne połączeń i zmniejsza opóźnienia podczas aktualizowania informacji w aplikacji.

Usługa FCM jest implementowana za pomocą trwałego połączenia TCP/IP. Pozwala to zminimalizować liczby trwałych połączeń i pozwala platformie zoptymalizować przepustowość, i zminimalizować związany z tym wpływ na żywotność baterii.