Minimalizuj wpływ regularnych aktualizacji

Prośby wysyłane przez aplikację do sieci są główną przyczyną wyczerpywania baterii, ponieważ włączają one radio komórkowe lub Wi-Fi, które zużywa dużo energii. Za mocą niezbędną do wysyłania i odbierania pakietów, takie urządzenia radiowe zużywają dodatkową moc włączania się i uśpienia. 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ę. Przeprowadzanie aktualizacji cyklicznie.
  • 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 ich wykorzystania. są zoptymalizowane pod kątem mniejszego zużycia baterii.

Optymalizacja żądań inicjowanych przez użytkownika

Żądania inicjowane przez użytkownika zwykle występują w odpowiedzi na jego działania. 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. Aby odpowiadać na żądania inicjowane przez użytkownika, jednocześnie optymalizując korzystanie z sieci, możesz użyć tych technik.

ograniczać liczbę żą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 świeże. Realizowanie każdej takiej prośby może powodować znaczne zużycie energii, ponieważ radio będzie cały czas aktywne. Bardziej wydajnym podejściem jest ograniczanie żądań inicjowanych przez użytkownika, tak aby w danym przedziale czasu można było wysłać tylko jedno żądanie, co ogranicza częstotliwość korzystania z radia.

Korzystanie z pamięci podręcznej

Przechowując dane aplikacji w pamięci podręcznej, tworzysz lokalną kopię informacji, do których aplikacja musi się odwoływać. Aplikacja może wielokrotnie uzyskiwać dostęp do tej samej lokalnej kopii informacji bez konieczności nawiązywania połączenia z siecią w celu 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 pobrane pliki.

W Androidzie 11 i nowszych Twoja aplikacja może korzystać z tych samych dużych zbiorów danych, których używają inne aplikacje do takich zastosowań jak uczenie maszynowe czy odtwarzanie multimediów. Gdy aplikacja potrzebuje dostępu do współdzielonego zbioru danych, może najpierw sprawdzić, czy istnieje wersja w pamięci podręcznej, zanim spróbuje pobrać nową kopię. Więcej informacji o wspólnych zbiorach danych znajdziesz w artykule Dostęp do wspólnych zbiorów danych.

Użyj większej przepustowości, aby rzadziej pobierać więcej 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 biorąc pod uwagę względny wpływ państwa na wykorzystanie baterii. jest dłuższy w przypadku systemów radiowych o większej przepustowości. Więcej informacji na temat: tail-time, patrz stan radiowy na komputerze.

Jednocześnie większa przepustowość oznacza, że można pobierać z wyprzedzeniem więcej danych, agresywniej, uzyskując w tym samym czasie więcej danych. Może mniej ponieważ koszt akumulatora przy obrocie czasowym jest stosunkowo wyższy, sprawniejsze, aby radio było aktywne przez dłuższy czas podczas każdego transferu , 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 poznasz typ połączenia, możesz odpowiednio zmodyfikować swoje procedury wstępnego pobierania:

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ę są zwykle wysyłane zgodnie z harmonogramem, np. aplikacja wysyła logi lub dane analityczne do usługi backendowej. 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 zużywać znaczną ilość energii i skracać czas pracy na baterii. Bardziej wydajnym podejściem jest umieszczenie w kolejce zestawu żądań sieci i przetworzenie ich razem. Dzięki temu system płaci za energię tylko raz, gdy włącza radio, i nadal uzyskuje wszystkie dane wymagane przez aplikację.

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.

Optymalizacja żądań inicjowanych przez serwer

Żądania inicjowane przez serwer zazwyczaj występują w odpowiedzi na powiadomienia wysyłane przez serwera. Na przykład aplikacja służąca do czytania najnowszych wiadomości może otrzymać powiadomienie o nowej partii artykułów, które pasują do preferencji personalizacji użytkownika, i może je pobrać.

Wysyłanie aktualizacji serwera za pomocą Komunikacji w chmurze Firebase

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 modelu opartym na zdarzeniach aplikacja tworzy nowe połączenie tylko wtedy, gdy wie, że są dostępne dane do pobrania. W modelu opartym na zdarzeniach aplikacja tworzy nowe połączenie tylko wtedy, gdy wie, że są dostępne dane do pobrania. Model minimalizuje niepotrzebne połączeń i zmniejsza opóźnienia podczas aktualizowania informacji w aplikacji.

FCM jest implementowane przy użyciu stałego połączenia TCP/IP. Pozwala to zminimalizować liczbę trwałych połączeń i umożliwia platformie optymalizację przepustowości oraz zminimalizowanie wpływu na czas pracy na baterii.