Uygulama performansı serisine Battery’yle devam ediyorum. Battery aslında bu seride anlatacağım son adım. Çünkü bu serinin genel olarak kapsamı android performans iyileştirmesi hakkında ve performans analizi yapabileceğimiz tool’lar hakkında genel bir bilgi sahibi olmak olarak başlanmıştı.

Mobil cihazınızın donanımı, görevleri çalıştırırken ya da kedinizin fotoğraflarını yüklerken, bu işleri gerçekleştirebilmek için bataryadan enerji çeker. Tabi buna bağlı olarak da bataryadan ne kadar güç çekileceği ve bataryanın ne kadar süreyle dayanacağı değişmektedir. Nasıl daha az batarya tüketen uygulamalar yazacağımız konusu ise arkada çalışan process’i ne kadar iyi anladığımızla ilgilidir.

Şimdi mesela bir Nexus 5 cihaz alıp airplane moda çekelim. Neredeyse hiç batarya harcamayacaktır. Cihazınız aktif olduğu sürece batarya tüketir. Aktifi tanımlamak gerekirse, CPU’nun çalıştığı işler, radio’nun data transfer etmesi, ekranın kendini uyandırması diyebiliriz.

Peki hangi görevler, daha çok batarya tüketiyor olabilir?

Bu aslında cevaplaması kolay bir soru değildir. Batarya tüketimini hardware seviyesinde monitor etmek, işin içinden çıkılmaz bir durumdur, çünkü monitoring’in kendisi de ne kadar batarya tüketildiğini ölçerken batarya tüketmektedir.  Bu tarz bilgileri kesin olarak öğrenmenin yolu cihaza third party bir donanım entegre etmek olabilir. Ancak böylece, cihazın bataryasını yemeden ne kadar pil tüketimi olduğu ölçülebilir. Bunu yaptığımızda değişik bilgiler elde ederiz.

Screen Shot 2015-11-17 at 23.11.38

Mesela yandaki resim, Nexus 5’in airplane moddaki halini göstermektedir.

 

 

 

Screen Shot 2015-11-17 at 23.13.40

 

Cihazı uyandırdığımızda ya da ekranı açtığımızda vs. ise görüntü yandaki gibi oıur.

 

 

Screen Shot 2015-11-18 at 21.54.02

 

Eğer cihaz, dışardan API’lar tarafından uyarılırsa ise yandaki gibi olur.(Alarm, zamanlanmış görevler) Burada bataryanın cihaz ilk uyanırken bir sıçrama yaptığını görüyorsunuz, çalıştırıldıktan sonra da bir dizi iş onu takip eder.

Burada vurgulanması gereken önemli nokta ise, yapılması gereken iş tamamlandıktan sonra cihaz uyku moduna geri döner. Çok az bir iş yapıp cihazı uzun süre uyanık tutmak, bataryanızı çok kolayca tüketir.

Eğer Android L kullanıyorsanız, bataryayla ilgili sorunları analiz etmek için çok sayıda tool aslında var.

Batterystats & Battery Historian

Batterystats cihazınızdan bataryayla ilgili data toplar. Battery Historian ise bu datayı Browser’ınızda görüntüleyebileceğiniz HTML formatına çevirir. Batterystats Android framework’unun bir parçasıdır ve Battery Historian ise Github’da açık kaynak olarak bulunmaktadır. https://github.com/google/battery-historian

Ne işe yarar?

  • O an çalışan processlerin nasıl ve nerede bataryayı tükettiklerini gösterir.
  • Batarya ömrünü uzatmak için uygulamanızdaki ertelenebilir ya da kaldırılabilir taskları tanımlar.

5.0+’da çalışmaktadır.

Batterystats’i nasıl kullanacağımızla ilgili adım adım ilerlersek;

  1. Github’dan açık kaynal Battery Historian script’ini indirin. (https://github.com/google/battery-historian).
  2. Battery Historian klasörü altına dosyayı unzip edin. Klasörün içinde, historian.py isimli doyayı bulup, masaüstü ya da başka bir editleyebileceğiniz alana taşıyın.
  3. Cihazınızı bilgisayara bağlayın.
  4. Bilgisayarınızda, terminali açın.
  5. historian.py dosyasını save ettiğiniz klasöre geçin,
    örnek: cd ~/Desktop
  6. adb serverınızı kapatın
    > adb kill-server
  7. adb’yi restart edip, bağlı cihazları kontrol edin
    > adb devices
  8. Batarya datasını resetleyin.
    > adb shell dumpsys batterystats --reset
  9. Cihazınızı bilgisayardan ayırın, böylece sadece cihazın bataryasından o anki kullanımı almış olacaksınız.
  10. Test etmek istediğiniz uygulamanızda biraz gezinin.
  11. Cihazı bilgisayara tekrar bağlayın.
  12. Cihazınızın tanındığından emin olun. > adb devices
  13. Tüm batarya datasını dump edin:
    > adb shell dumpsys batterystats > batterystats.txt
  14. Battery Historian için data dump’ın bir HTML versiyonunu oluşturun:
    > python historian.py batterystats.txt > batterystats.html
  15. Browser'ınızda batterystats.htm dosyasını açın.

Battery Historian Charts

gettingstarted_image02

Yukarıdaki resimde de gördüğünüz gibi her bir satır, bir sistem bileşeni aktif ve bataryadan çekim yapıyorken, renklendirilmiş bir segmenti göstermektedir.

Batarya Kullanım Kategorileri:

  • battery_level: Batarya seviyesinin kaydedilmesi ve loglanması. Yüzde olarak kaydedilir, 093’ün anlamı 93%’dür. Bataryanın ne kadar hızlı tüketildiğine dair yuvarlak bir fikir verir.
  • top: En üstde çalıştırılan uygulama ki bu da genellikle kullanıcıya görünür olan uygulamadır. Eğer uygulamanız aktifken, bateri tüketimini ölçmek istiyorsanız, uygulamanızın top app olduğundan emin olmalısınız. Eğer uygulamnız arka plandayken, bateri tüketimini ölçmek istiyor iseniz ise, uygulamanızın top app olmadığından emin olmalısınız.
  • wifi_running: Wi-fi bağlantısının aktifliğini gösterir.
  • screen: Ekran açık
  • phone_in_call: Telefon aramada olduğunda kaydeder.
  • wake_lock: Uygulama uyanır, bir kilit kapar, küçük bi iş yapar ve uyku moduna geri döner. Cihazı uyandırmak maliyetlidir, bu yüzden eğer çok fazla kısa barlar görüyorsak, bu bir problemin habercisi olabilir.
  • running: CPU’nun ne zaman uyanık olduğunu gösterir. Uyanık ve uyku modunda olması gerektiği zamanlarda mı beklenen state’de kontrol edebilirsiniz.
  • wake_reason: Kernel’in uyanmasına sebep olabilecek son nedendir. Eğer sebep olan sizin uygulamanızda, gerekli olup olmadığına karar vermelisiniz.
  • mobile_radio: Radyonun ne zaman açık olduğunu gösterir. Birbirine çok yakın barlar, batching ya da başka optimizasyonlar yapılması gerektiğini işaret edebilir.
  • gps: GPS’in ne zaman açık olduğunu gösterir. Açık olmasını beklediğiniz zamanlarda mı açık kontrol edebilirsiniz. Mesela map applicationınız varsa gps’de o başladığında başladıysa anlamlıdır.
  • sync: Uygulamanın backend’le olan sync işleminin ne zaman olduğunu gösterir. Bu bar aynı zamanda hangi app’in sync yaptığını da gösterir. Batarya’dan kazanmak için nerelerde sync kapatılabileceği hakkında bilgi verebilir. Yazılımcılar mümkün olduğunca az sadece gerektiğinde sync olmalıdırlar.

Filtering batterystats output:

batterystats.txt dosyasını batterystats command’inden save ederseniz daha fazla bilgiye erişebilirsiniz.

Dosyayı açıp, aşağıdakileri search ederseniz:

  1. Battery History: Güçle ilişkili event’lerin serisi, ör: ekran, wi-fi, uygulamanın gettingstarted_image03launch edilmesi..
  2. Per-PID Stats: Her bir process ne kadar çalıştı.
  3. Statistics since last charge: Sistem genelinde istatistikler, hücre sinyal seviyeleri ve ekran parlaklığı. Cihazda neler olduğuna dair genel bir resim çizer. Bu bilgi daha çok dış event’lerin ortamınızı etkileyip etkilemediğinin kontrolünde etkilidir.
  4. Estimated power use (mAh) by UID and peripheral: Kaba bir tahmin verir, deneme verileri kabul edilmemelidir.
  5. Per-app mobile ms per packet:Radyonun uyanık olduğu zamanlar gönderilen paketler tarafında bölümlenir. Etkin bir uygulama tüm trafiğini batchlerde transfer edecektir, o yüzden bu sayının düşük olması iyidir.
  6. All partial wake locks: App tarafından handle edilen tüm uyanma çağrıları, sayısı ve süresi.

Cihazınız uyku modunda, uygulamalar aktif olmasa bile, çoğu uygulama çalışmayı deneyecektir ve bunu yapabilmek için de cihazı uyandırmak zorunda kalacaktır. Uygulamanızı uyandırmanın en basit yolu ise powermanager.wakelock API’yi kullanmaktır. Bu API, CPU çalışmasını sürdürürken, ekranın kararmasını ya da kapanmasını engeller. Bu uygulamanızın, ileriki bir zamanda uyanmasını, işi yapmasını ve yeniden uyku moduna dönmesini sağlar.

Kullanıcı deneyimine geri dönersek, uygulamanızın başarılı olmasının tek yolu aslında Screen Shot 2015-11-21 at 22.16.12kullanıcıların onu kullanmasıdır. Bazen sorunlar, uygulamanız kullanıcının cihazında uyku modundayken başlar. İşinizi yapmak için cihazı uyandırmak doğru bir fikir gibi gelebilir ancak, yalnız burada gerçek bir bateri problemiyle karşı karşıya kalmak üzere olabilirsiniz. Mesela bir tane uygulama düşünelim. Uygulama sürekli örneği scoreboardları check ediyor, fotoğraf yüklüyor ya da belli bir hastag le yazılmış datayı sürekli çekiyor olsun. Ancak uygulamanız bunları yapıyorsa, cihaz uyku modunda olsa bile, bateri tüketimine devam edecektir. Böyle bir durumda batarya çok hızlı tükenecek ve kullanıcı sürekli cihazını şarj etmek zorunda kalacaktır. Bunun sebebi de aslında cihazın uygulamalar tarafından sürekli uyandırılmasıdır.

Aslında Android, cihazının batarya ömürünü korumak için, hardware’in birçok parçasını kapatır. İlk olarak, ekran kararır ve ardından kendini kapatır. Ve son olarak da CPU uykuya geçer. Tüm bunların hepsi batarya ömrünü korumak için yapılır. Fakat bu inactive modda bile çoğu app aslında çalışmaya devam eder ve cihazı uyandırır.

Uygulamanızın cihazı uyandırmasının en kolay yolu, powermanager.wakelock API’dır. Bu API, CPU çalışmaya devam ederken, ekranın kararmasını ve kapanmasını önler. Bu uygulamanızın ileri bir tarihte cihazı uyandırıp, yapması gereken işleri yaptıktan sonra tekrar uyku moduna dönmesini sağlar. Burada aslında son kısım tricky part’dır. Cihazının bir işlemi yapmak için ne zaman uyandırılacağı aslında karar vermesi basit olan kısımdır ama cihazın tekrar uyku moduna ne zaman geçeceği o kadar da basit bir karar değildir. Mesela imajları yüklemek için 10 sn yeterliyken 60 dk sürerse ne olur? Ya da server crash ederse, cevap alamaz ve sonsuza kadar beklerseniz ? Sonuç olarak, cihaz batarya tamamen tükenene kadar cevap almayı bekleyecektir. Tüm bu sebeplerden dolayı timeout için wakelock.acquire versiyonunu kullanmalısınız. Bu az önce bahsettiğim gibi durumlar yaşandığında aslında wakelock’un release edilmesini ve bu sayede cihazının bataryasının tamamen tükenmesini de önlemiş olacaktır. Fakat bu da problemi tamamen çözmez. Timeout’u bu case’lerde set eder ancak, bir hata alınması durumunda, ne kadar bekledikten sonra tekrar deneyeceğini set etmezseniz ne olur?

Screen Shot 2015-11-21 at 22.47.34

Burada doğru olan çözüm, işi ileriki bir zamanda çalışması için programlamak adına inexact timer kullanmak olabilir. Fakat sistem, batarya ömrünü korumak adına bunun daha önce ya da daha sonra çalışabilir olduğunu fark ederse, işi çalıştırmak için o zamanı bekleyecektir. Örnek olarak, eğer başka bir process cihazı uyandırırsa, sizin process’iniz programlandığı zamanda çalışmak yerine bu process’leri bekleyerek 10 sn sonra da çalışabilir. Bu ve buna benzer bir sürü durum oluşabilir. Ancak eğer uygulamanız cihazı uyandırması gerekiyorsa, batarya ömrünü korumak için ekstra özen göstermeniz gerekmektedir. Bu da olarak job schedular API’nin varolma nedenidir diyebiliriz. Bu API bazı ileri zamanlı işlerin batarya ömrünü en iyi koruyarak yapılabilmesi için idealdir.

Screen Shot 2015-11-21 at 23.10.02

Yukarıda hyper-active uygulamaların battery storing’i. (Bu screenshot Google IO 2014 Project Volta‘dan alınmıştır.)

Bu grafiği yorumlarsak, wakelock activity’ye baktığımızda, çok fazla boşluk cihazın çok fazla sayıda uyandırılması anlamına gelmektedir. Network activity’e baktığımızda ise, tüm bunlar batarya tüketimi anlamına gelmektedir. (Project Volta konuşmasını izleyerek daha detaylı bilgi de edinebilirsiniz.) Peki böyle bir grafiğin oluşmasına nasıl bir kod sebep olabilir?

Github adresindeki uygulama ile devam edeceğim. Traditional olarak eğer cihazın uyanmasını istiyorsak, bir wake lock implement ederiz.

Github’daki kod örneğinden gidersek, aşağıdaki method wakelock kullanmaktadır. İlk olarak cihazı uyandırmak için wakelock’u acquire etmektedir. Böylece network update’i artık yapılabilir. Ardından network’un bağlı olup olmadığı kontrol edilmektedir. Eğer bağlı değilse de bağlı olana kadar denemeye devam edecektir. Eğer bağlıysa, bir AsyncTask’ın içerisinde, UI thread’den bağımsız olarak, serverdan yeni datayı alacaktır. İşlem tamamlandığında ise, AsyncTask’ın postExecute’une düşer ve burada release edilebilir.

private void pollServer() {
    mWakeLockMsg.setText("Polling the server! This day sure went by fast.");
    for (int i=0; i<10; i++) {
        mWakeLock.acquire();
        mWakeLockMsg.append("Connection attempt, take " + i + ":\n");
        mWakeLockMsg.append(getString(R.string.wakelock_acquired));

        // Always check that the network is available before trying to connect. You don't want
        // to break things and embarrass yourself.
        if (isNetworkConnected()) {
            new SimpleDownloadTask().execute();
        } else {
            mWakeLockMsg.append("No connection on job " + i + "; SAD FACE");
        }
    }
}
@Override
protected void onPostExecute(String result) {
    mWakeLockMsg.append("\n" + result + "\n");
    releaseWakeLock();
}

Burada wakelock’lar ilgili temel problem developer tarafından manuel olarak, tutuluyor ve release ediliyor olmasıdır. Yani eğer wakelock’ları çok uzun süreyle tutarsak, kullanıcının bataryasını tüketebiliriz. Aynı cihazda birden çok app’in bunu yaptığını düşünürsek ise batarya ömrü ciddi anlamda kısalmış olur.

Peki Job Scheduler API kullanarak bu durumu nasıl optimize edebiliriz. Udacity Class’ın paylaştığı kaynak kodu ilgili link’den inceleyebilirsiniz.  Burada fark olarak Job Scheduler kullanmak için bir service endpoint yaratmanız gerekmektedir. Bu aslında sistemin işinizi çalıştırmak için uygun olduğunda çağıracağı servistir. Bunu JobService’i extend ederek elde ediyoruz.

mServiceComponent = new ComponentName(this, MyJobService.class);

JobService’de önemli olan onStartJob ve onStopJob callback’lerini implement etmektir.

onStartJob methodunda, background’da çalışacak network’den data çekme mantığımızı implement ederiz.

@Override
public boolean onStartJob(JobParameters params) {
    // This is where you would implement all of the logic for your job. Note that this runs
    // on the main thread, so you will want to use a separate thread for asynchronous work
    // (as we demonstrate below to establish a network connection).
    // If you use a separate thread, return true to indicate that you need a "reschedule" to
    // return to the job at some point in the future to finish processing the work. Otherwise,
    // return false when finished.
    Log.i(LOG_TAG, "Totally and completely working on job " + params.getJobId());
    // First, check the network, and then attempt to connect.
    if (isNetworkConnected()) {
        new SimpleDownloadTask() .execute(params);
        return true;
    } else {
        Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face");
    }
    return false;
}

onStopJob methodunda özellikle yapmamız gereken birşey yoktur. Ancak beklenen önce işiniz durdurulursa bu method çağrılır. Mesela aniden wi-fi bağlantısının kopması gibi. Önemli olan bir nokta daha işiniz tamamlandığında job finish methodunu çağırmanız gerekliliğidir. Böylece wakelock release olur.

@Override
protected void onPostExecute(String result) {
    jobFinished(mJobParam, false);
    Log.i(LOG_TAG, result);
}

Aşağıda pollServer() methodunun yeni halini görüyorsunuz.

public void pollServer() {
    JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
    for (int i=0; i<10; i++) {
        JobInfo jobInfo = new JobInfo.Builder(i, mServiceComponent)
                .setMinimumLatency(5000) // 5 seconds
                .setOverrideDeadline(60000) // 60 seconds (for brevity in the sample)
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // WiFi or data connections
                .build();

        mWakeLockMsg.append("Scheduling job " + i + "!\n");
        scheduler.schedule(jobInfo);
    }
}

Referanslar: