Minimalizuj wpływ regularnych aktualizacji

Główną przyczyną rozładowywania baterii przez aplikację są żądania wysyłane przez aplikację do sieci, ponieważ włączają one energooszczędne sieci komórkowe lub Wi-Fi. Poza mocą potrzebną do wysyłania i odbierania pakietów zużywają one dodatkową moc, jednocześnie włączając i podnosząc stan aktywności. Coś tak prostego jak żądanie sieciowe co 15 sekund może utrzymywać ciągłość działania radiowej w trybie ciągłym i szybko zużywać baterię.

Istnieją trzy ogólne typy regularnych aktualizacji:

  • Inicjowane przez użytkownika. Przeprowadzanie aktualizacji na podstawie określonych działań użytkownika, np. gestu przesunięcia, aby odświeżyć.
  • Inicjowana przez aplikację. Regularne przeprowadzanie aktualizacji.
  • Inicjowane przez serwer. aktualizacja w odpowiedzi na powiadomienie z serwera,

W tym temacie przyjrzymy się każdemu z nich i omówiono dodatkowe sposoby ich optymalizacji w celu zmniejszenia obciążenia baterii.

Optymalizacja żądań inicjowanych przez użytkowników

Żądania zainicjowane przez użytkownika zwykle występują w odpowiedzi na pewne zachowania użytkownika. Na przykład aplikacja służąca do czytania najnowszych artykułów może umożliwiać użytkownikowi wykonanie gestu przesunięcia, by odświeżyć stronę, aby sprawdzić, czy są nowe. Aby odpowiadać na żądania inicjowane przez użytkownika przy jednoczesnej optymalizacji korzystania z sieci, możesz stosować poniższe metody.

Ograniczanie żądań użytkowników

Niektóre żądania zainicjowane przez użytkownika możesz zignorować, na przykład wiele gestów przesunięcia w krótkim czasie w celu sprawdzenia, czy są dostępne nowe dane, podczas gdy aktualne dane są nadal aktualne. Realizacja każdego żądania mogłaby zużywać dużo energii, nie wyłączając radia. Lepszym sposobem jest ograniczenie żądań inicjowanych przez użytkownika, tak aby można było wysłać tylko jedno żądanie w określonym czasie. Zmniejsza to częstotliwość używania radia.

Użyj pamięci podręcznej

Zapisując dane aplikacji w pamięci podręcznej, tworzysz lokalną kopię informacji, do których aplikacja ma się odwoływać. Aplikacja może dzięki temu wielokrotnie uzyskiwać dostęp do tej samej lokalnej kopii informacji bez konieczności otwierania połączenia sieciowego w celu wysyłania nowych żądań.

Staraj się jak najbardziej agresywnie zapisywać w pamięci podręcznej dane, w tym zasoby statyczne i pliki do pobrania na żądanie, np. obrazy w pełnym rozmiarze. Możesz użyć nagłówków HTTP, aby mieć pewność, że strategia buforowania nie powoduje wyświetlania w aplikacji nieaktualnych danych. Więcej informacji o buforowaniu odpowiedzi sieciowych znajdziesz w artykule Unikanie nadmiarowego pobierania.

Na Androidzie 11 i nowszych aplikacja może korzystać z tych samych dużych zbiorów danych, których używają inne aplikacje w takich zastosowaniach jak systemy uczące się czy odtwarzanie multimediów. Gdy aplikacja chce uzyskać dostęp do udostępnionego zbioru danych, może najpierw sprawdzić wersję z pamięci podręcznej, zanim spróbujesz pobrać nową kopię. Więcej informacji o udostępnianych zbiorach danych znajdziesz w artykule Uzyskiwanie dostępu do udostępnionych zbiorów danych.

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

W przypadku połączenia przez radio bezprzewodowe więcej przepustowości wiąże się zwykle z większym kosztem baterii. Oznacza to, że 5G zużywa zwykle więcej energii niż LTE, co z kolei jest droższe niż 3G.

Oznacza to, że chociaż bazowy stan radiowy różni się w zależności od technologii radiowej, względny wpływ zmian stanu na baterię jest większy w przypadku urządzeń radiowych o większej przepustowości. Więcej informacji o czasie reakcji znajdziesz w artykule na temat systemu radiowego.

Jednocześnie większa przepustowość oznacza, że możesz pobierać z wyprzedzeniem więcej danych w tym samym czasie. Być może mniej intuicyjnie, ponieważ koszt baterii w krótkim czasie jest względnie wyższy, można też skuteczniej utrzymywać aktywność radiową podczas każdej sesji transferu, aby zmniejszyć częstotliwość aktualizacji.

Na przykład, jeśli radio LTE ma 2 razy większą przepustowość i 2 razy wyższe koszty energii niż 3G, podczas każdej sesji należy pobierać 4 razy więcej danych, a potencjalnie nawet 10 MB. W przypadku pobierania tak dużej ilości danych ważne jest, aby wziąć pod uwagę wpływ pobierania z wyprzedzeniem na dostępną lokalną pamięć masową i regularnie czyścić pamięć podręczną pobierania z wyprzedzeniem.

Za pomocą ConnectivityManager możesz zarejestrować detektor w sieci domyślnej, a TelephonyManager do zarejestrowania PhoneStateListener, aby określić bieżący typ połączenia urządzenia. Gdy poznasz 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;
}

Optymalizuj żądania inicjowane przez aplikację

Żądania inicjowane przez aplikację zwykle są realizowane zgodnie z harmonogramem, na przykład aplikacja wysyłająca logi lub dane analityczne do usługi backendu. W przypadku żądań zainicjowanych przez aplikację weź pod uwagę priorytet tych żądań, to, czy można je grupować razem oraz czy można je odroczyć do czasu ładowania urządzenia lub połączenia z siecią bez pomiaru użycia danych. Te żądania można optymalizować przy użyciu dokładnego harmonogramu i przy użyciu bibliotek takich jak WorkManager.

Zbiorcze żądania sieciowe

Na urządzeniu mobilnym proces włączania radia, nawiązywania nawiązywania połączenia i utrzymywania aktywności radia pochłania ogromną ilość energii. Z tego powodu przetwarzanie poszczególnych żądań w losowych momentach może zużywać dużo energii i skrócić czas pracy na baterii. Bezpieczniejszym sposobem jest umieszczenie zestawu żądań sieciowych w kolejce i przetworzenie ich razem. Dzięki temu system może pokryć koszty energii związane z włączeniem radia tylko raz, a jednocześnie nadal pobierać wszystkie dane, których żąda aplikacja.

Korzystanie z WorkManagera

Za pomocą biblioteki WorkManager możesz pracować nad wydajnym harmonogramem, który określa, czy są spełnione określone warunki, takie jak dostępność sieci czy stan zasilania. Załóżmy na przykład, że masz podklasę Worker o nazwie DownloadHeadlinesWorker, która pobiera najnowsze nagłówki wiadomości. Ten instancja robocza może być uruchamiana co godzinę, o ile urządzenie jest połączone z siecią bez pomiaru użycia danych, a bateria urządzenia nie jest słaba. W razie problemów z pobieraniem danych użyj niestandardowej strategii ponawiania próby. Jak widać 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 WorkManagera platforma Androida oferuje też inne narzędzia, które pomagają stworzyć wydajny harmonogram wykonywania zadań związanych z siecią, takich jak odpytywanie. 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 zwykle są realizowane w odpowiedzi na powiadomienie z serwera. Na przykład aplikacja służąca do czytania najnowszych wiadomości może otrzymać powiadomienie o nowej grupie artykułów pasujących do preferencji użytkownika, które następnie zostaną pobrane.

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

Komunikacja w chmurze Firebase (FCM) to prosty mechanizm służący do przesyłania danych z serwera do określonej instancji aplikacji. Za pomocą FCM serwer może powiadamiać aplikację działającą na konkretnym urządzeniu, że są dostępne nowe dane na jego temat.

W odróżnieniu od sondowania, w których aplikacja musi regularnie wysyłać pingi do serwera w celu wysłania zapytania o nowe dane, ten model oparty na zdarzeniach umożliwia aplikacji utworzenie nowego połączenia tylko wtedy, gdy ma informacje do pobrania. Model minimalizuje liczbę niepotrzebnych połączeń i zmniejsza czas oczekiwania podczas aktualizowania informacji w aplikacji.

Usługa FCM jest wdrażana za pomocą stałego połączenia TCP/IP. Zmniejsza to liczbę trwałych połączeń i pozwala platformie zoptymalizować przepustowość i zminimalizować jej wpływ na żywotność baterii.