Düzenli güncellemelerin etkisini en aza indirme

Uygulamanızın ağa gönderdiği istekler, güç tüketen hücresel veya kablosuz radyoları açtığı için pil tüketiminin önemli bir nedenidir. Bu radyolar, paket göndermek ve almak için gereken gücün yanı sıra yalnızca açılmak ve uyanık kalmak için de ekstra güç harcar. Her 15 saniyede bir ağ isteği göndermek gibi basit bir işlem, mobil radyonun sürekli açık kalmasına ve pil gücünün hızla tükenmesine neden olabilir.

Genel olarak üç tür düzenli güncelleme vardır:

  • Kullanıcı tarafından başlatılır. Yenilemek için kaydırma hareketi gibi bir kullanıcı davranışına göre güncelleme yapma
  • Uygulama tarafından başlatılan. Güncellemeyi düzenli olarak yapma
  • Sunucu tarafından başlatılan. Sunucudan gelen bir bildirime yanıt olarak güncelleme yapma.

Bu konuda, bunların her biri incelenmekte ve pil tüketimini azaltmak için optimize edilebilecekleri diğer yöntemler ele alınmaktadır.

Kullanıcı tarafından başlatılan istekleri optimize etme

Kullanıcı tarafından başlatılan istekler genellikle bir kullanıcı davranışına yanıt olarak gerçekleşir. Örneğin, en son haber makalelerini okumak için kullanılan bir uygulama, kullanıcının yeni makaleleri kontrol etmek için yenilemek için kaydırma hareketini yapmasına izin verebilir. Ağ kullanımını optimize ederken kullanıcı tarafından başlatılan isteklere yanıt vermek için aşağıdaki teknikleri kullanabilirsiniz.

Kullanıcı isteklerini kısıtlama

Gerekli olmayan bazı kullanıcı tarafından başlatılan istekleri yok saymak isteyebilirsiniz. Örneğin, mevcut veriler hâlâ güncelken yeni verileri kontrol etmek için kısa bir süre içinde birden fazla yenilemek için kaydırma hareketi yapılması gibi. Her isteğe yanıt vermek, radyonun uyanık kalmasına neden olarak önemli miktarda güç tüketebilir. Daha verimli bir yaklaşım, kullanıcı tarafından başlatılan istekleri kısıtlayarak belirli bir süre içinde yalnızca bir istek yapılabilmesini sağlamak ve radyonun kullanım sıklığını azaltmaktır.

Önbelleği kullanma

Uygulamanızın verilerini önbelleğe alarak, uygulamanızın başvurması gereken bilgilerin yerel bir kopyasını oluşturursunuz. Uygulamanız, yeni istekler göndermek için ağ bağlantısı açmak zorunda kalmadan bilgilerin aynı yerel kopyasına birden çok kez erişebilir.

Statik kaynaklar ve tam boyutlu resimler gibi isteğe bağlı indirmeler de dahil olmak üzere verileri mümkün olduğunca agresif bir şekilde önbelleğe almanız gerekir. Önbelleğe alma stratejinizin, uygulamanızın eski verileri göstermesine neden olmaması için HTTP önbellek üstbilgilerini kullanabilirsiniz. Ağ yanıtlarını önbelleğe alma hakkında daha fazla bilgi için Gereksiz indirmelerden kaçınma başlıklı makaleyi inceleyin.

Android 11 ve sonraki sürümlerde uygulamanız, diğer uygulamaların makine öğrenimi ve medya oynatma gibi kullanım alanları için kullandığı büyük veri kümelerini kullanabilir. Uygulamanızın paylaşılan bir veri kümesine erişmesi gerektiğinde, yeni bir kopya indirmeye çalışmadan önce önbelleğe alınmış bir sürüm olup olmadığını kontrol edebilir. Paylaşılan veri kümeleri hakkında daha fazla bilgi edinmek için Paylaşılan veri kümelerine erişme başlıklı makaleyi inceleyin.

Daha fazla veriyi daha seyrek indirmek için daha fazla bant genişliği kullanma

Kablosuz radyo üzerinden bağlanıldığında daha yüksek bant genişliği genellikle daha yüksek pil maliyeti anlamına gelir. Yani 5G genellikle LTE'den daha fazla enerji tüketir. LTE de 3G'den daha fazla enerji tüketir.

Bu, temel radyo durumunun radyo teknolojisine göre değiştiği anlamına gelir. Genel olarak, durum değişikliği kuyruk süresinin göreceli pil etkisi, daha yüksek bant genişliğine sahip radyolar için daha fazladır. Kuyruk süresi hakkında daha fazla bilgi için Radyo durum makinesi bölümüne bakın.

Aynı zamanda, daha yüksek bant genişliği, daha agresif bir şekilde önceden getirme yapabileceğiniz ve aynı süre içinde daha fazla veri indirebileceğiniz anlamına gelir. Kuyruk süresi pil maliyeti nispeten daha yüksek olduğundan, güncellemelerin sıklığını azaltmak için her aktarım oturumu sırasında radyoyu daha uzun süre aktif tutmak da daha verimlidir.

Örneğin, bir LTE radyonun bant genişliği ve enerji maliyeti 3G'nin iki katıysa her oturumda dört kat daha fazla veri (veya potansiyel olarak 10 MB'a kadar) indirmeniz gerekir. Bu kadar çok veri indirirken önceden getirmenin kullanılabilir yerel depolama alanı üzerindeki etkisini göz önünde bulundurmanız ve önceden getirme önbelleğinizi düzenli olarak temizlemeniz önemlidir.

Varsayılan ağ için bir dinleyici kaydetmek üzere ConnectivityManager'yi, mevcut cihaz bağlantı türünü belirlemek amacıyla bir PhoneStateListener kaydetmek için ise TelephonyManager'yi kullanabilirsiniz. Bağlantı türü belirlendikten sonra önceden getirme rutinlerinizi buna göre değiştirebilirsiniz:

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

Uygulama tarafından başlatılan istekleri optimize etme

Uygulama tarafından başlatılan istekler genellikle bir programa göre gerçekleşir. Örneğin, bir uygulama, arka uç hizmetine günlükler veya analizler gönderir. Uygulama tarafından başlatılan isteklerle ilgilenirken bu isteklerin önceliğini, birlikte gruplandırılıp gruplandırılamayacağını ve cihaz şarj olana veya tarifesiz bir ağa bağlanana kadar ertelenip ertelenemeyeceğini göz önünde bulundurun. Bu istekler, dikkatli bir planlama ve WorkManager gibi kitaplıklar kullanılarak optimize edilebilir.

Ağ isteklerini toplu olarak gönderme

Mobil cihazlarda radyonun açılması, bağlantı kurulması ve radyonun uyanık tutulması işlemleri büyük miktarda güç tüketir. Bu nedenle, istekleri rastgele zamanlarda tek tek işlemek önemli miktarda güç tüketebilir ve pil ömrünü kısaltabilir. Daha verimli bir yaklaşım, bir dizi ağ isteğini sıraya koymak ve bunları birlikte işlemektir. Bu sayede sistem, radyonun açılması için gereken güç maliyetini yalnızca bir kez öder ve bir uygulamanın istediği tüm verileri alır.

WorkManager'ı kullanma

Ağ kullanılabilirliği ve güç durumu gibi belirli koşulların karşılanıp karşılanmadığını dikkate alan verimli bir planlama doğrultusunda çalışmak için WorkManager kitaplığını kullanabilirsiniz. Örneğin, en son haber başlıklarını alan DownloadHeadlinesWorker adında bir Worker alt sınıfınız olduğunu varsayalım. Bu çalışan, cihazın sınırsız bir ağa bağlı olması ve pilinin düşük olmaması koşuluyla her saat çalışacak şekilde programlanabilir. Veriler alınırken sorun yaşanırsa aşağıda gösterildiği gibi özel bir yeniden deneme stratejisi uygulanır:

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

Android platformu, WorkManager'ın yanı sıra ağ görevlerini (ör. yoklama) tamamlamak için verimli bir plan oluşturmanıza yardımcı olacak başka araçlar da sunar. Bu araçları kullanma hakkında daha fazla bilgi edinmek için Arka planda işleme kılavuzu'nu inceleyin.

Sunucu tarafından başlatılan istekleri optimize etme

Sunucu tarafından başlatılan istekler genellikle bir sunucudan gelen bildirime yanıt olarak gerçekleşir. Örneğin, en son haber makalelerini okumak için kullanılan bir uygulama, kullanıcının kişiselleştirme tercihlerine uyan yeni bir makale grubu hakkında bildirim alabilir ve ardından bu makaleleri indirebilir.

Firebase Cloud Messaging ile sunucu güncellemeleri gönderme

Firebase Cloud Messaging (FCM), bir sunucudan belirli bir uygulama örneğine veri aktarmak için kullanılan basit bir mekanizmadır. FCM'yi kullanarak sunucunuz, belirli bir cihazda çalışan uygulamanıza yeni veriler olduğunu bildirebilir.

Uygulamanızın yeni verileri sorgulamak için sunucuya düzenli olarak ping göndermesi gereken yoklama yöntemine kıyasla bu etkinliğe dayalı model, uygulamanızın yalnızca indirilecek veri olduğunu bildiğinde yeni bir bağlantı oluşturmasına olanak tanır. Model, gereksiz bağlantıları en aza indirir ve uygulamanızdaki bilgileri güncellerken gecikmeyi azaltır.

FCM, kalıcı bir TCP/IP bağlantısı kullanılarak uygulanır. Bu sayede, kalıcı bağlantı sayısı en aza indirilir ve platformun bant genişliğini optimize etmesi ve pil ömrü üzerindeki ilgili etkiyi en aza indirmesi sağlanır.