Turkcell | Geleceği Yazanlar

Android 301Başlangıç seviyesi derslerde Android uygulama geliştirme ortamını detaylı olarak inceliyoruz.

Tüm Eğitimleri Aldın mı? Kendini sınamanın tam zamanı

Haydi Sınava Gir

Tekrarlayan Uyarıların Zamanlanması

Kategori : Mobil | Lisans : Creative Commons Attribution 2.5 Generic (CC BY 2.5) | En son 12.09.2019 tarihinde güncellendi
Eğitmen : Geleceği Yazanlar Ekibi
Etiketler : android-tekrarlayan-uygulamalar android

Uyarılar (alarm) uygulamanızın yaşam döngüsü dışında da zamana bağlı işler yapma fırsatı verir. Uyarı mekanizması, dinamik, zamana bağlı tetiklenebilen görevleri belirlediğiniz şartlarda başlatmanıza yardımcı olur. Örneğin bir uyarı, her gün hava durumu bilgisini indirecek bir servisi başlatmak için kullanılabilir.

Uyarıların şöyle özellikleri vardır:

  • Intent'leri belli zamanlarda ya da aralıklarda çalıştırmanızı sağlar.
  • Broadcast Receiver ile birlikte servisleri başlatmak ve diğer işlemleri gerçekleştirmek amacıyla kullanılabilir.
  • Uygulamanızın dışında çalışırlar. Uyarılar sisteme yüklendiği için sorumlusu sistemdir. Dolayısıyla uyarıları, belli olayları ya da eylemleri tetiklemek için kullanabilirsiniz. Uygulamanız çalışmıyorken veya cihazın kendisi uyuyorken bile bunu yapmanız olası.
  • Uygulamanızın kaynak gereksinimini en aza indirmenize yardım ederler. Sayaçlara veya sürekli çalışan arka plan servislerine mecbur kalmadan yapmak istediğiniz işleri bu yolla zamanlayabilirsiniz.

NOT: Zamana bağlı işler için Timer ve Thread sınıflarıyla bir arada Handler sınıfını kullanma çözümü yerine, burada anlatılan zamanlanmış uyarı yaklaşımları, uygulamanızın yaşam döngüsü sıradasında çalışmayı çok daha iyi garantiliyorlar. Zamana bağlı işlem yaklaşımı, Andoid'e sistem kaynakları üzerinde daha iyi bir denetim de sağlıyor.

 

Doğru yönteme karar vermek

Tekrarlayan uyarı, sınırlı esnekliğine rağmen görece basit bir mekanizmadır, muhtemelen uygulamanız için en iyi seçim olmayabilir. Özellikle ağ işlerini tetiklemeniz gerekiyorsa zaten bu şekilde kötü tasarlanmış bir uyarı, bataryayı tüketebilir ve sunuculara fazladan yük bindirebilir.

Uygulama dışında bir işlemi tetikleme konusundaki en yaygın senaryo, sunucuyla istemci arasında veri eşleme (syncing) işlemidir. Tam da bu gibi senaryolarda tekrarlayan uyarıları kullanmak aklınıza yatabilir ancak daha iyi çözümler de var. Eğer uygulamanızın verilerini kendi sunucunuzda tutuyorsanız, Sync Adapter ile birlikte Google Cloud Messaging (GCM)'i kullanmak AlarmManager kullanmaktan çok daha iyi bir çözümdür. Sync Adapter, AlarmManager gibi aynı zamanlama seçeneklerini size sunar ancak hissedilir derecede esnek özellikleri vardır. Örneğin bir veri eşleme işlemi, sunucudan/cihazdan gelen, kullanıcının etkin olması (ya da etkin olmaması), günün belli bir saati veya bunun gibi "yeni veri" mesajlarıyla tetiklenip gerçekleştirilebilir. Bu konuda daha fazla ayrıntı için Sync Adapter'ı Çalıştırmak (ENG) belgesine bakabilirsiniz. GCM'nin nasıl ve ne zaman Sync Adapter ile kullanılacağıyla ilişkin ayrıntılı açıklamaların geçtiği The App Clinic: Cricket (ENG) videosunu izleyebilirsiniz.

 

En iyisini yapmanız için tavsiyeler

Tekrarlayan uyarılarınızı tasarlarken verdiğiniz her karar, uygulamanızın sistem kaynaklarını nasıl kullanacağına (veya nasıl kötüye kullanacağına) dair belli sonuçlar doğurur. Örneğin, çok kullanıla bir uygulama hayal edin ve bir sunucuyla veri eşlemesi (data syncing) yapıyor olsun. Veri eşleme işlemi, uygulamanın kurulu olduğu her cihazda belli bir saatte, örneğin gece 11'de başlıyor olsun. Böylesine bir işlemin büyük bir kitleye aynı anda yapılması, sunucunun yanıt verme süresini yukarılara çeker ve hatta "sunucuya erişilemiyor" hataları çıkartabilir. Böyle sorunlarla karşılaşmak istemiyorsanız aşağıdaki önerilerimize bir göz atın:

  • Ağ isteklerini tekrarlayan bir uyarıyla tetikliyorsanız, uyarıya "rastlantısallık" (sapma) payı ekleyin.
    • Farklı zamanlarda rastgele çalışacak ağ istekleri olan uyarılar zamanlayın/programlayın.
    • Yerel işleri, uyarı tetiklendiğinde yapın. "Yerel iş"in anlamı, sunucudan veri alması veya sunucuya veri göndermesi gerekmeyen, gerçekten yerelde yapılan işler.
  • Uyarı sıklığını en aza indirin - uyarılarınız çok sık çalışmasın.
  • Cihazı gereksiz yere uyandırmayın (Cihazı uyandırıp uyandırmayacağınızı uyarıyı oluştururken tip seçimi kısmında belirleyebiliyorsunuz. Bkz: Uyarı Tipi Seçmek)
  • Uyarılarınızın tetiklenme zamanlarını, aslında çok da gerekmiyorsa kesin-belli saatler şeklinde belirlemeyin.
    Bu amaçla setRepeating() yerine setInexactRepeating() metodunu kullanın. setInexactRepeating() metodunu kullandığınızda Android, uyarınızı diğer birçok uygulamanın tekrarlayan uyarılarılarıyla aynı zamanda çalıştırır. Bu sayede sistemin cihazı uyanık tutması gereken zamanların sayısı düşer ve böylece batarya tüketimi de azalır. Batarya tüketimi mobil cihazlarda her şeydir, unutmayın. Bu amaçla Android 4.4 (API 19) sürümünden itibaren artık tüm tekrarlayan uyarılar kesin zaman bilgisi içeremiyor.
    setRepeating() yerine setInexactRepeating() metodunu bir iyileştirmeymiş gibi kullanabilirsiniz ancak bu metod, eğer uygulamanızı çalıştıran tüm cihazlar, sunucunuza yakın zamanlarda istek yaparsa (örneğin öğle aralarında ya da akşam saatlerindeki yoğunluk gibi) yine aşırı trafiğe neden olabilir. Bu yüzden yukarıda anlatıldığı gibi ağ istekleri için uyarılarınızın zamanlama ayarlarına sapma payı eklemeyi unutmayın.
  • Mümkünse uyarıları belli saatlere ayarlamaktan kaçının.
    Kesin tetiklenme zamanları olan tekrarlayan uyarılar, kötü ölçeklemeyi beraberinde getirir. Yapabiliyorsanız yine ELAPSED_REALTIME sabitini kullanarak uyarınızı yapılandırın.

Sıradaki başlıkta farklı uyarı tipleri detaylı biçimde açıklanıyor.

 

Tekrarlayan bir uyarı oluşturmak

Yukarıda anlatıldığı gibi tekrarlayan uyarılar kullanmak, zamanlanmış, düzenli olaylar ve veri kontrolleri için iyi bir seçimdir. Bir tekrarlayan uyarının şuradaki gibi karakteristik özellikleri vardır:

  • Uyarı tipi. Ayrıntılarını hemen alttaki Uyarı Tipini Seçmek başlığında öğrenebilirsiniz.
  • Tetiklenme zamanı. Eğer tanımladığınız tetiklenme zamanı cihaz uykuda olduğundan vb. sebeplerle geçmişte kalmışsa o uyarı cihaz uyandığı an tetiklenir.
  • Uyarının aralığı. Örneğin günde bir, her saat, her 5 saniyede bir vs.
  • Uyarı tetiklendiğinde çalışacak "sıra bekleyen" (pending) Intent. Eğer aynı PendingIntent'i kullanan ikinci bir uyarı başlatırsanız, bu uyarı orijinaliyle yer değiştirecektir.

 

Uyarı tipini seçmek

Tekrarlayan uyarıları kullanırken karşınıza çıkacak ilk önemli nokta, uyarı tipinin ne olması gerektiği hususudur.

Uyarılar için farklı zaman dilimlerinde tiktaklayan iki genel saat tipi var: "geçen zaman saati" (elapsed real time) ve "gerçek zamanlı saat" (real time clock - RTC).

Geçen zaman saati "sistemin boot olduğundan beri geçen zaman"ı referans alır. Gerçek zamanlı saat de UTC zamanını yani o anki duvar saati gibi gerçek saati gösterir. Bunun anlamı, geçirilen zamana dayalı bir uyarı (örneğin her 30 saniyede bir çalışan uyarı) ayarlamak istiyorsanız, "geçen zaman saati" kullanışlıdır çünkü kullanıcının bölgesindeki zaman dilimiyle bir alakası yoktur. Kullanıcı cihazını açar ve bu saat o andan itibaren tiktaklamaya başlar. Gerçek zamanlı saat, daha çok o anki bölgenin saatine bağlı uyarılar için uygundur.

Her iki uyarı tipinin de bir "wakeup"lı yani "uyandıran" sürümü mevcut. Bu uyandıran sürümler sayesinde eğer ekran kapalıysa cihazın işlemcisine uyanmasını söylemek mümkün. Bu özellik, uyarının tam ayarlandığı saatte çalıştırılmasını sağlar. Eğer uygulamanız zaman bağlı bir uygulamaysa, örneğin belli bir işlem için sınırlı bir aralıkta iş yapıyorsa, uyandırma özelliği olan uyarılar gayet kullanışlıdır. Eğer uyarılarınızda "wakeup" sürümlü olan uyarıları kullanmak istemezseniz, tüm tekrarlayan uyarılar cihazın bir sonraki uyanışında çalıştırılacaktır, unutmamanız gerekir.

Şimdi uyandırma ihtiyacından bağımsız olarak uyarıların zamanlama durumları üzerinde duralım.

Eğer basitçe belli aralıklarda (örneğin her yarım saatte) çalışacak bir uyarıya ihtiyacınız varsa, geçen zaman saatine dayalı tiplerden birini kullanın. Bu genellikle en iyi çözüm olacaktır.

Eğer günün belli zamanlarında (örneğin akşam saat 20'de) çalışacak uyarıya ihtiyacınız varsa, o zaman duvar saati temelli "gerçek zamanlı saat" tiplerinden birini kullanın. Hatırlatmakta fayda var: Bu tipte uyarıların ayakbağı olma olasılığı var. Uygulamanız kullanıcının yerel saatine uygun şekilde yapılandırılmamış olabilir veya kullanıcı cihazın zaman ayarlarında değişiklik yapmış olabilir. Dolayısıyla uygulamanızın beklenmedik davranışları olabilir. Gerçek zamanlı saat tipi olan uyarılar kullanmanın kötü ölçeklemeyle sonuçlanan olaylara sebep olabileceğine yukarıda değinmiştik. Bu yüzden eğer mümkünse "geçen zaman" tipli uyarıları kullanmanızı öneriyoruz.

Şimdi sözünü ettiğimiz tüm uyarı tiplerine bakalım:

  • ELAPSED_REALTIME - Cihazın boot olduğu zamandan itibaren geçen zaman miktarını referans alarak, belirttiğiniz "sıra bekleyen" (pending) Intent'i çalıştırır fakat cihaz uykudaysa uyandırmaz.
    NOT: Geçen zamana cihazın uykuda olduğu zaman da dâhildir.
  • ELAPSED_REALTIME_WAKEUP - Cihazın boot olduğu zamandan itibaren geçen zamanı miktarını referans alarak, belirttiğiniz "sıra bekleyen" (pending) Intent'i çalıştırır ve cihaz uykudaysa bile uyandırır.
  • RTC - Belli bir zamanda, sıra bekleyen (pending) Intent'i çalıştırır fakat cihaz uykudaysa uyandırmaz.
  • RTC_WAKEUP - Belli bir zamanda, sıra bekleyen (pending) Intent'i çalıştırmak için cihazı uyandırır.

 

ELAPSED_REALTIME_WAKEUP örnekleri

Aşağıda ELAPSED_REALTIME_WAKEUP tipli uyarılarla ilgili örneklere bakalım.

30 dakika içinde uyarıyı çalıştırmak için cihazı uyandırır ve sonraki her 30 dakikada yine çalışır:

// Muhtemelen uyarınız, bundan daha az sıklıkta olacaktır
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        AlarmManager.INTERVAL_HALF_HOUR,
        AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);

1 dakika içinde uyarıyı bir kereliğine (tekrarlama yok) çalıştırmak için cihazı uyandırır:

private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() +
        60 * 1000, alarmIntent);

 

RTC örnekleri

Aşağıda RTC_WAKEUP tipli uyarılarla ilgili örneklere bakalım.

Yaklaşık olarak saat 14'te çalışacak uyarıyı başlatmak için cihazı uyandırır. Bu uyarı günde bir kere aynı saatte çalışır.

// Uyarıyı yaklaşık saat 14'te başlamaya ayarlayalım
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 14);

// setInexactRepeating() metoduyla birlikte AlarmManager'ın sunduğu aralık sabitlerinden birini kullanabiliyorsunuz. Buradaki örnekte bu AlarmManager.INTERVAL_DAY
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        AlarmManager.INTERVAL_DAY, alarmIntent);

Saat tam 8:30'da başlayacak uyarıyı çalıştırmak için cihazı uyandırır ve bundan sonraki her 20 dakikada uyarı çalışır:

private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

// Uyarıyı 8:30'a ayarlayalım.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);

// setRepeating() metodu size özel kesin zaman aralıkları tanımlama olanağı verir. Bu örnekte 20 dakika
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        1000 * 60 * 20, alarmIntent);

 

Uyarının kesin zamana ihtiyacı olup olmadığına karar vermek

Yukarıda sözünü ettiğimiz gibi uyarı tipine karar vermek uyarı oluşturmanın ilk adımı. Bir sonraki adım, uyarının ne kadar kesinliğe ihtiyacı olduğudur. Çoğu uygulama için setInexactRepeating() doğru seçimdir. Bu metodu kullandığınızda Android, tüm kesin zaman içermeyen tekrarlı uyarıları bir arada aynı zamanda çalışmak için eşleştirir. Bu da batarya tüketiminin azaltılmasını sağlar.

Kesin, değişmez zaman gereksinimleri olan çok az uygulama örneğin, tam saat 8:30'da ve ardından her 1 saatte çalışması gereken uyarılar için setRepeating() metodunu kullanmalısınız. Eğer mümkünse kesin zaman içeren uyarılar kullanmaktan kaçının.

setRepeating() metoduyla yapabildiğiniz özel zaman aralığı belirleme olanağını setInexactRepeating() metoduyla yapamazsınız. setInexactRepeating() metodunda zaman aralıkları için tanımlanmış INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY gibi sabitleri kullanabiliyorsunuz. AlarmManager referans belgesine bakarak tüm sabitlerin olduğu listeyi görebilirsiniz.

 

Uyarıyı iptal etmek

Uygulamanıza bağlı olarak, uyarı iptal etmeyi de istebilirsiniz. Bir uyarıyı iptal etmek için AlarmManager'daki cancel() metodunu kullanabilirsiniz. Bu metoda parametre olarak artık başlatmak istemediğiniz PendingIntent'i vermelisiniz. Bir örnek:

// uyarı ayarlanmışsa iptal eder
if (alarmMgr!= null) {
    alarmMgr.cancel(alarmIntent);
}

 

Bir uyarıyı cihaz önyüklendiğinde (Boot) başlatmak

Varsayılan olarak cihaz kapandığında tüm uyarılar iptal edilir. Böyle bir şeyin uyarınız için olmasının önüne geçmek isterseniz, kullanıcı cihazını yeniden başlattığında otomatik olarak siz de uygulamanızın tekrarlayan uyarısını yeniden başlatabilirsiniz. Bu sayede kullanıcı uyarıyı elle yeniden başlatmak zorunda kalmadan, AlarmManager tarafından görevler yerine getirilmeye devam eder.

Adımlar şöyle:

  1. Manifest dosyasında RECEIVE_BOOT_COMPLETED iznini tanımlayın. Bu sayede uygulamanız, sistem önyüklemesi bittiğinde ACTION_BOOT_COMPLETED isimli yayını (broadcast) alacaktır. (Uygulamanızın bu yayını alabilmesi için daha önce kullanıcı tarafından en az bir kere çalıştırılması gerekir)
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. Yayını alacak bir BroadcastReceiver hazırlayalım:
    public class SampleBootReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
                // uyarıyı burada başlatabilirsiniz
            }
        }
    }
  3. Oluşturduğunuz bu yayın alıcının manifest dosyasında beyan edildiği yere ACTION_BOOT_COMPLETED yayınını alabilmesi için bir intent filter ile ekleyin:
     
    <receiver android:name=".SampleBootReceiver"
    	        android:enabled="false">
    	    <intent-filter>
    	        <action android:name="android.intent.action.BOOT_COMPLETED"></action>
    	    </intent-filter>
    	</receiver>

    Dikkat ederseniz bu alıcının android:enabled="false" gibi bir özelliği var. Bunun anlamı uygulamanız açıkça etkinleştirmedikçe bu boot yayın alıcısı çağırılamayacak. Bu sayede bu boot yayın alıcısının sistem tarafından gereksiz yere "dürtülmesinin" önüne geçiyorsunuz. Buradaki boot yayın alıcısını (örneğin kullanıcı için bir şekilde bir uyarı başlatmak gerekince) şu şekilde etkinleştirebilirsiniz:
    ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
    PackageManager pm = context.getPackageManager();
    
    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP);

    Bu yolla yayın alıcınızı etkin hale getirdiğinizde, kullanıcı cihazını yeniden başlatsa bile etkin olarak kalacaktır. Bir başka deyişle, yayın alıcıyı Java kodu tarafında etkinleştirmek manifest dosyasındaki ayarların üstüne yazacaktır - cihazın yeniden başlaması durumunda bile. Ayrıca bu yayın alıcı, uygulamanız onu etkisizleştirene kadar etkin olmaya devam edecektir. Etkisizleştirmek isterseniz (örneğin kullanıcı için bir şekilde uyarıyı iptal etmek gerektiğinde) şu yolu takip edebilirsiniz:
    ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
    PackageManager pm = context.getPackageManager();
    
    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP);

 

Bu sayfadaki parçalar Android Open Source Project kapsamında oluşturulmuş ve paylaşılmış içeriğin küçük değişiklikler yapılmış hâlidir ve Creative Commons 2.5 Attribution License'ta belirlenen koşullara göre kullanılmıştır.

Bu eğitim içeriğinin orijinal hâline buradan ulaşabilirsiniz: Scheduling Repeating Alamrs