Minimalizuj wpływ regularnych aktualizacji

Żądania wysyłane przez aplikację do sieci są główną przyczyną zużywania baterii, ponieważ włączają zużywające energię sieci komórkowe lub Wi-Fi. Poza mocą niezbędną do wysyłania i odbierania pakietów, urządzenia radiowe generują też dodatkową moc, włączając i utrzymując senność. Coś tak prostego jak żądanie sieciowe co 15 sekund może sprawić, że mobilne radio będzie działać nieprzerwanie i szybko zużywa baterię.

Istnieją 3 ogólne typy regularnych aktualizacji:

  • Inicjowane przez użytkownika. Przeprowadzanie aktualizacji na podstawie niektórych zachowań użytkownika, np. gest przeciągnięcia w celu odświeżenia.
  • Inicjowane przez aplikację. Cykliczne aktualizacje
  • Inicjowane przez serwer. przeprowadzanie aktualizacji w odpowiedzi na powiadomienie z serwera,

W tym artykule omawiamy każdy z nich i dodatkowe sposoby ich optymalizacji w celu zmniejszenia zużycia baterii.

Optymalizacja żądań inicjowanych przez użytkownika

Żądania inicjowane przez użytkownika zazwyczaj mają miejsce w reakcji na pewne zachowania użytkownika. Na przykład aplikacja używana do czytania najnowszych wiadomości może umożliwić użytkownikowi wykonanie gestu „przeciągnij, by odświeżyć” w celu sprawdzenia dostępności nowych artykułów. Możesz wykorzystać te techniki, aby odpowiadać na żądania zainicjowane przez użytkownika i optymalizować wykorzystanie sieci:

Ograniczanie żądań użytkowników

Niektóre żądania inicjowane przez użytkownika możesz zignorować, jeśli nie ma potrzeby ich używać. Na przykład kilka gestów przeciągnięcia w celu odświeżania w krótkim czasie w celu sprawdzenia, czy są dostępne nowe dane, gdy aktualne dane są nadal aktualne. Wykonywanie każdego żądania może spowodować marnowanie dużej mocy, ponieważ nie pozwalałoby na wybudzenie radia. Skuteczniejszym podejściem jest ograniczenie liczby żądań inicjowanych przez użytkownika, tak aby w danym okresie można było wysłać tylko jedno żądanie, co ogranicza częstotliwość korzystania z danego radia.

Używaj pamięci podręcznej

Buforowanie danych aplikacji powoduje utworzenie lokalnej kopii informacji, do których aplikacja musi się odwoływać. Aplikacja może wielokrotnie korzystać z tej samej lokalnej kopii tych informacji bez konieczności nawiązywania połączenia sieciowego, by wysyłać nowe żądania.

Dane, w tym zasoby statyczne i pliki do pobrania na żądanie, takie jak obrazy w pełnym rozmiarze, powinny być jak najbardziej agresywne buforowane. Aby mieć pewność, że strategia buforowania nie powoduje wyświetlania przez aplikację nieaktualnych danych, możesz użyć nagłówków pamięci podręcznej HTTP. Więcej informacji o przechowywaniu odpowiedzi sieciowych w pamięci podręcznej znajdziesz w artykule Unikanie pobierania zbędnych plików.

Na Androidzie 11 i nowszym aplikacja może korzystać z tych samych dużych zbiorów danych, których używają inne aplikacje, takich jak systemy uczące się czy odtwarzanie multimediów. Gdy aplikacja potrzebuje dostępu do udostępnionego zbioru danych, przed próbą pobrania nowej kopii może najpierw sprawdzić wersję w pamięci podręcznej. Więcej informacji o udostępnionych zbiorach danych znajdziesz w artykule Dostęp do udostępnionych zbiorów danych.

Użyj większej przepustowości, aby rzadziej pobierać więcej danych

Przy połączeniu przez bezprzewodowe połączenie radiowe większa przepustowość wiąże się zwykle z wyższym kosztem baterii. Oznacza to, że 5G zużywa zwykle więcej energii niż LTE, co z kolei jest droższe od 3G.

Oznacza to, że chociaż podstawowy stan nadajnika różni się w zależności od technologii radiowej, zasadniczo względny wpływ na baterię zmiany stanu ogona jest większy w przypadku stacji radiowych o większej przepustowości. Więcej informacji o czasie ogonania znajduje się w sekcji Urządzenie z stanami radiowymi.

Jednocześnie większa przepustowość oznacza bardziej agresywne pobieranie danych z wyprzedzeniem, w tym samym czasie. Być może mniej intuicyjnie jest to spowodowane tym, że koszt baterii takiej baterii jest stosunkowo wyższy, więc lepiej wydajniej pozostawać aktywne radio przez dłuższy czas podczas każdej sesji transferu, by zmniejszyć częstotliwość aktualizacji.

Jeśli na przykład radio LTE ma 2 razy większą przepustowość i podwójne koszty energii 3G, podczas każdej sesji pobierz 4 razy więcej danych, a potencjalnie nawet 10 MB. Przy pobieraniu tak dużej ilości danych należy wziąć pod uwagę wpływ pobierania z wyprzedzeniem na dostępną pamięć lokalną i regularnie czyścić pamięć podręczną pobierania z wyprzedzeniem.

Za pomocą ConnectivityManager możesz zarejestrować odbiornik w sieci domyślnej, a TelephonyManager do zarejestrowania PhoneStateListener, aby określić bieżący typ połączenia urządzenia. Gdy typ połączenia będzie już znany, 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, np. aplikacja wysyła logi lub analizy do usługi backendu. Gdy zajmujesz się żądaniami inicjowanymi przez aplikację, zastanów się nad ich priorytetem, zastanów się, czy mogą być one grupowane i czy można je odroczyć do czasu ładowania urządzenia lub połączenia z siecią bez pomiaru. Żądania te można optymalizować, postępując zgodnie ze szczegółowym harmonogramem i korzystając z bibliotek takich jak WorkManager.

Zbiorcze żądania sieciowe

W przypadku urządzenia mobilnego proces włączania radia, nawiązywania połączenia i utrzymywania aktywności radia wymaga dużej mocy obliczeniowej. Dlatego przetwarzanie poszczególnych żądań w losowych momentach może znacznie zużywać energię i skracać czas pracy na baterii. Skuteczniejszym sposobem jest umieszczenie w kolejce żądań sieciowych i przetwarzanie ich razem. Dzięki temu system pokryje koszt energii, który wystarczy jednorazowo włączyć radio, nie rezygnując z pobierania wszystkich danych żądanych przez aplikację.

Używanie WorkManagera

Korzystając z biblioteki WorkManager, możesz wykonywać prace zgodnie z wydajnym harmonogramem, który uwzględnia określone warunki, takie jak dostępność sieci czy stan zasilania. Załóżmy np., że masz podklasę Worker o nazwie DownloadHeadlinesWorker, która pobiera najnowsze nagłówki wiadomości. Działanie tej instancji roboczej można zaplanować co godzinę, o ile urządzenie jest podłączone do sieci bez pomiaru, a bateria urządzenia nie jest słaba. Jeśli wystąpią problemy z pobraniem danych, użyjemy niestandardowej strategii ponawiania prób:

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 Androida zapewnia też kilka innych narzędzi, które pomagają utworzyć wydajny harmonogram wykonywania zadań związanych z siecią, np. ankiet. Więcej informacji o korzystaniu z tych narzędzi znajdziesz w przewodniku po przetwarzaniu w tle.

Optymalizuj żądania inicjowane przez serwer

Żądania inicjowane przez serwer zazwyczaj występują w reakcji na powiadomienie od serwera. Na przykład aplikacja używana do czytania najnowszych wiadomości może otrzymać powiadomienie o nowej grupie artykułów zgodnych z ustawieniami personalizacji, które następnie może pobrać.

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

Komunikacja w chmurze Firebase (FCM) to uproszczony mechanizm służący do przesyłania danych z serwera do określonej instancji aplikacji. Dzięki FCM serwer może powiadomić Twoją aplikację działającą na danym urządzeniu, że są dostępne nowe dane.

W przeciwieństwie do ankiet, w których aplikacja musi regularnie wysyłać pingi do serwera w celu uzyskania nowych danych, ten model oparty na zdarzeniach umożliwia aplikacji utworzenie nowego połączenia tylko wtedy, gdy wie, że są dane do pobrania. Model minimalizuje niepotrzebne połączenia i zmniejsza czas oczekiwania 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żliwić platformie optymalizację przepustowości oraz zminimalizowanie powiązanego wpływu na czas pracy na baterii.