Meminimalkan efek update reguler

Permintaan yang dibuat oleh aplikasi Anda ke jaringan merupakan penyebab utama baterai cepat habis karena aplikasi mengaktifkan radio seluler atau Wi-Fi yang memakan daya. Selain daya yang diperlukan untuk mengirim dan menerima paket, radio ini mengeluarkan daya tambahan untuk terus menyala dan tetap aktif. Permintaan jaringan yang sederhana setiap 15 detik dapat membuat radio seluler tetap aktif secara terus-menerus dan menghabiskan daya baterai dengan cepat.

Ada tiga jenis umum update rutin:

  • Dimulai oleh pengguna. Melakukan update berdasarkan beberapa perilaku pengguna, seperti gestur tarik untuk refresh.
  • Dimulai oleh aplikasi. Melakukan pembaruan secara berulang.
  • Dimulai oleh server. Melakukan update sebagai respons terhadap notifikasi dari server.

Topik ini membahas ketiga aspek tersebut dan membahas cara tambahan untuk mengoptimalkannya guna mengurangi konsumsi baterai.

Mengoptimalkan permintaan yang dimulai pengguna

Permintaan yang dimulai pengguna biasanya terjadi sebagai respons terhadap beberapa perilaku pengguna. Misalnya, aplikasi yang digunakan untuk membaca artikel berita terbaru dapat memungkinkan pengguna melakukan gestur tarik untuk refresh guna memeriksa artikel baru. Anda dapat menggunakan teknik berikut untuk merespons permintaan yang dimulai pengguna sekaligus mengoptimalkan penggunaan jaringan.

Membatasi permintaan pengguna

Sebaiknya abaikan beberapa permintaan yang dimulai pengguna jika tidak diperlukan, seperti beberapa gestur tarik untuk refresh selama beberapa waktu singkat untuk memeriksa data baru saat data saat ini masih baru. Bertindak berdasarkan setiap permintaan dapat menyia-nyiakan sejumlah besar daya dengan membuat radio tetap aktif. Pendekatan yang lebih efisien adalah men-throttle permintaan yang dimulai pengguna sehingga hanya satu permintaan yang dapat dibuat selama jangka waktu tertentu, sehingga mengurangi frekuensi penggunaan radio.

Menggunakan cache

Dengan meng-cache data aplikasi, Anda membuat salinan lokal informasi yang perlu dirujuk oleh aplikasi. Kemudian, aplikasi Anda dapat mengakses salinan lokal informasi yang sama beberapa kali tanpa harus membuka koneksi jaringan untuk membuat permintaan baru.

Anda harus menyimpan data dalam cache se agresif mungkin, termasuk resource statis dan download on demand seperti gambar ukuran penuh. Anda dapat menggunakan header cache HTTP untuk memastikan strategi penyimpanan cache tidak mengakibatkan aplikasi menampilkan data yang tidak berlaku. Untuk informasi selengkapnya tentang cara menyimpan respons jaringan dalam cache, lihat Menghindari download redundan.

Di Android 11 dan yang lebih tinggi, aplikasi Anda dapat menggunakan set data besar yang sama dengan yang digunakan aplikasi lain untuk kasus penggunaan seperti machine learning dan pemutaran media. Saat aplikasi Anda perlu mengakses set data bersama, aplikasi dapat memeriksa versi yang di-cache terlebih dahulu sebelum mencoba mendownload salinan baru. Untuk mempelajari lebih lanjut set data bersama, lihat Mengakses set data bersama.

Mengurangi frekuensi download data lebih banyak dengan menggunakan bandwidth lebih besar

Saat terhubung melalui radio nirkabel, bandwidth yang lebih tinggi umumnya menyebabkan biaya baterai yang lebih tinggi. Artinya, 5G biasanya menggunakan lebih banyak energi daripada LTE, yang nantinya lebih mahal daripada 3G.

Ini berarti bahwa meskipun status radio yang mendasarinya bervariasi berdasarkan teknologi radio, secara umum dampak baterai relatif dari waktu akhir perubahan status lebih besar untuk radio dengan bandwidth yang lebih tinggi. Untuk mengetahui informasi selengkapnya tentang tail-time, lihat Mesin status radio.

Pada saat yang sama, bandwidth yang lebih tinggi berarti Anda dapat mengambil data secara lebih agresif, dengan mendownload lebih banyak data secara bersamaan. Mungkin kurang intuisinya, karena biaya baterai tail-time relatif lebih tinggi, juga lebih efisien untuk membuat radio tetap aktif selama jangka waktu yang lebih lama selama setiap sesi transfer untuk mengurangi frekuensi update.

Misalnya, jika radio LTE memiliki bandwidth dua kali lipat dan dua kali lipat biaya energi 3G, Anda harus mendownload data empat kali lebih banyak selama setiap sesi, atau berpotensi sebanyak 10 MB. Saat mendownload data sebanyak ini, sebaiknya pertimbangkan efek pengambilan data pada penyimpanan lokal yang tersedia dan hapus cache pengambilan data secara teratur.

Anda dapat menggunakan ConnectivityManager untuk mendaftarkan pemroses untuk jaringan default, dan TelephonyManager untuk mendaftarkan PhoneStateListener guna menentukan jenis koneksi perangkat saat ini. Setelah jenis koneksi diketahui, Anda dapat mengubah rutinitas pengambilan data:

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

Optimalkan permintaan yang dimulai aplikasi

Permintaan yang dimulai oleh aplikasi biasanya terjadi sesuai jadwal, seperti aplikasi yang mengirim log atau analisis ke layanan backend. Saat menangani permintaan yang dimulai oleh aplikasi, pertimbangkan prioritas permintaan tersebut, apakah dapat ditumpuk bersama, dan apakah permintaan dapat ditunda hingga perangkat mengisi daya atau terhubung ke jaringan yang tidak berbayar. Permintaan ini dapat dioptimalkan dengan penjadwalan yang cermat dan dengan menggunakan library seperti WorkManager.

Membuat batch permintaan jaringan

Pada perangkat seluler, proses menyalakan radio, membuat koneksi, dan menjaga radio tetap aktif menggunakan daya dalam jumlah besar. Karena alasan ini, memproses permintaan satu per satu secara acak dapat menghabiskan daya yang signifikan dan mengurangi masa pakai baterai. Pendekatan yang lebih efisien adalah mengantrekan sekumpulan permintaan jaringan dan memprosesnya bersama-sama. Hal ini memungkinkan sistem membayar biaya daya untuk menyalakan radio sekali saja, dan tetap mendapatkan semua data yang diminta oleh aplikasi.

Menggunakan WorkManager

Anda dapat menggunakan library WorkManager untuk melakukan pekerjaan sesuai jadwal efisien yang mempertimbangkan apakah kondisi tertentu terpenuhi, seperti ketersediaan jaringan dan status daya. Misalnya, Anda memiliki subclass Worker bernama DownloadHeadlinesWorker yang mengambil judul berita terbaru. Pekerja ini dapat dijadwalkan untuk berjalan setiap jam, asalkan perangkat terhubung ke jaringan tidak berbayar dan baterai perangkat tidak rendah, dengan strategi percobaan ulang kustom jika ada masalah saat mengambil data, seperti yang ditunjukkan di bawah ini:

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

Selain WorkManager, platform Android menyediakan beberapa alat lain untuk membantu Anda membuat jadwal yang efisien dalam menyelesaikan tugas jaringan, seperti polling. Untuk mempelajari penggunaan alat ini lebih lanjut, lihat Panduan pemrosesan latar belakang.

Mengoptimalkan permintaan yang dimulai server

Permintaan yang dimulai server biasanya terjadi sebagai respons terhadap notifikasi dari server. Misalnya, aplikasi yang digunakan untuk membaca artikel berita terbaru dapat menerima notifikasi tentang batch artikel baru yang sesuai dengan preferensi personalisasi pengguna, yang kemudian akan didownload.

Mengirim update server dengan Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) adalah mekanisme ringan yang digunakan untuk mengirimkan data dari server ke instance aplikasi tertentu. Dengan FCM, server dapat memberi tahu aplikasi yang berjalan di perangkat tertentu bahwa ada data baru yang tersedia untuknya.

Dibandingkan dengan polling, ketika aplikasi Anda harus secara teratur melakukan ping ke server untuk mengkueri data baru, model berbasis peristiwa ini memungkinkan aplikasi Anda membuat koneksi baru hanya jika mengetahui ada data untuk didownload. Model ini meminimalkan koneksi yang tidak perlu dan mengurangi latensi saat memperbarui informasi dalam aplikasi Anda.

FCM diimplementasikan menggunakan koneksi TCP/IP persisten. Hal ini meminimalkan jumlah koneksi persisten dan memungkinkan platform mengoptimalkan bandwidth serta meminimalkan dampak terkait pada masa pakai baterai.