Jetpack: WorkManager Temel Kullanımı




Herkese selamlar,

Bugün Android Jetpack'le birlikte gelen WorkManager hakkında konuşup, nerelerde ve nasıl kullanılmalıdır ve bu kullanım senaryolarına bağlı olarak küçük örnekler gerçekleştireceğiz. O zaman vakit kaybetmeden bu WorkManager nedir, ne değildir öğrenelim.


WorkManager Nedir?


Aramıza Jetpack ile katılmış olan WorkManager en basit hali ile background/arka plan işlemlerimizin yürütülmesinden sorumlu diyebiliriz. Evet bu cümle biraz kafa karıştırıcı olabilir çünkü hali hazırda bizler yıllardan beri arka planda yapılması gereken işlemler için belirli yöntemler zaten kullanmaktaydık. Neden bildiğimiz yöntemleri kullanmak varken bunu da öğrenmek zorunda kalalım ki diyebilirsiniz. Fakat işin ucu pek de öyle değil, aslında her birimizin, belki de WorkManager'ın basit bir şekilde sunduğu özellikleri bin bir takla atarak yapıyoruz. Aslında avantajlarını duyunca kesinlikle benim gibi kendisini seveceksiniz.


Sunduğu Avantajlar

  • API 14'e kadar geriye uyumluluk sağlamakta. Bu sayede API level'i düşük Android cihazlarda da kullanıldığı takdirde görevini gerçekleştirebilmektedir. Bu uyumluluklar ile birlikte farklı API level'leri ile çalışabilme kabiliyetine sahiptir. API 23 ve üstü ile JobScheduler, API 14-22 arasında ise Service'lerin kullanılıp kullanılmayacağına göre çalışabilme kabiliyetleri değişebilmektedir. Bu bahsettiklerimizi aşağıdaki grafikte de görebilirsiniz.

Not: Yapacağınız uygulama API 29 veya üzerinde çalışıyor ise FirebaseJobDispatcher ve GcmNetworkManager 1 Kasım 2020 tarihi itibari ile çalışmayacaktır. Daha fazla bilgi Android developers blog'unun linkini sizlere bırakıyorum:

https://android-developers.googleblog.com/2019/11/unifying-background-task-scheduling-on.html


  • Pil dostudur. Yayınlanma amaçlarından biri de Android cihazların arka planda çalışması sırasındaki aşırı pil tüketimine yöneliktir.

  • Belirlenmiş spesifik bir zamanda (saat 13:48'de çalışsın gibi) çalışma gibi bir iddiası yoktur. Yani WorkManager'lar tercihe göre belirli bir zaman aralığında çalışabilir veya görevini yerine getirilmesi için gerekli koşulları sağladığında aktif hale gelebilir. (3 saat de bir çalışsın gibi)

  • Yürütülmesi ertelenebilir.

  • Telefon yeniden başlatılsa veya uygulama kapatılsa bile çalışmaya devam eder. (Fakat uygulama öldürülürse çalışmaz. )

  • Zincir şeklinde kullanılabilme özelliği vardır. Bunu açmak gerekirse, arka arkaya paralel veya asenkron işlemler yapabilmektedir ve zincirler arası birinin output'u diğerinin input'u olabilmektedir.

  • Kısıtlamalara sahiptir. Kısıtlamadan kasıt, WorkRequest'lerimizin gerçekleşmesi için sahip olduğu ön koşullardır. Koşulumuz gerçekleşmediği sürece request'imiz kuyrukta bekleme durumunda kalır ve koşul sağlandığı anda request'imiz işlemlerini gerçekleştirmeye devam eder.

  • İşlemlerimizi periyodik veya tek seferde çalışması şeklinde gerçekleştirebiliriz. Periyodik işlemler için örnek vermek gerekirse, sunucuya belirli bir zaman aralığında (varsa koşullar sağlandığı sürece) veri gönderilebilir veya o sunucudan veri alınabilir. Tek sefer/zaman da çalışan işlemlerimiz ise görevini yapar ve bir daha çalışmaz.

Avantajlarını söylediğimize göre şimdi de biraz işin teorik kısmına girelim ve devamında yavaştan pratik kısmında kodlarımızı yapalım.


WorkManager Temel Kavramları


Worker: Yapılan işin gerçekleştiği kısımdır. Girdilerin ve çıktıların ve bunları takip eden tetiklenecek diğer durumların gerçekleşeceği sınıfa extends işlemi ile dahil edilen yapıdır.


WorkRequest: Gerçekleşmesini istediğimiz işlemlerin gerçekleşmesi için kullandığımız istek yapısıdır. WorkRequest kısmında input'lar verilebilir, kısıtların veya başka işlemler bu işe dahil edilebilir. OneTimeWorkRequest ve PeriodicWorkRequest olmak üzere ikiye ayrılmaktadır.


WorkManager: Yapılacak işler için oluşturduğumuz talep ve taleplerin yürütüldüğü, gerçekleştirildiği yapıdır.


WorkInfo: Yapılan işler ile ilgili durumların takip edildiği ve durumlar sırasındaki output'larımızı gözlemlediğimiz yapıdır. Bu takip işlemleri LiveData ile birlikte kullanılmaktadır.


WorkManager Kullanımı


Yazacağımız kodlara geçmeden önce kısaca neler yapacağımızdan bahsedelim. Örneğimizde WorkManager'lara giriş değerini baştan inputData ile vereceğiz. Devamında start butonumuza bastıktan sonra gerekli koşullar sağlandıkça bu input değerleri bir sonraki Worker'da ufak bir modifikasyona uğrayıp, koşulu sağlandıktan sonra bir diğer Worker'a geçecektir. Uygulamamızda bu işlemler gerek senkron gerek asenkron şekilde çalışma mantığını barındıracaktır . Bu işlemler yapılırken cancel butonuna basıldığında da o an işlem hangi Worker'da ise o Worker'dan itibaren çalışmanın durdurulacağını göreceğizdir. Açıklamamızı yaptığımıza göre hızlıca örneğimizi yapmaya başlayabiliriz.


İlk olarak build.gradle(:app) kısmımıza WorkManager'ımızı çalışması için tanıtalım.



Şimdi ilk Worker'ımızı oluşturalım.



doWork fonksiyonu Worker içinde yapacağımız işlemlerin bulanacağı kısımdır. Class'ımızı oluştururken otomatik olarak gelen TODO kısmını silip return Result.success() yazıyoruz.

Bu bizim Worker'ımızın output'unu data tipinde return edecektir.


Peki Result.success() nedir veya başka tipleri var mı öğrenelim.


Result.success(): Başarı ile sonuçlanan işlemler için kullanacağımız yoldur.

Result.failure(): Tek bir istek veya zincirin işlemlerini tamamlayamadan sonlanması durumunda return edilen yoldur.

Result.retry(): İstek veya zincirimizin yeniden çalışmayı denenmesi için kullanılan return yoludur. Tekrarlama işleminin nasıl yapılması gerektiği ile ilgili bilgileri Request'imizi yapılandırırken ekleriz.



AWorker'ın yukarıda tamamlanmış halini görüyorsunuz, şimdi bu kodları inceleyelim. Data tipindeki outputData fonksiyonumuz Return.success() içinde bir output döndürerek ilk Worker işlemimizi tamamlamamızı sağlayacaktır. Yukarıda da göründüğü üzere input ve output'lar için key/value tipinde işlemler yapmaktayız. Bu sebeple iki adet const kullandık. Fakat input'un aldığı const MainActivity'de bulunduğu için o şu anda size bir hata veriyor olabilir. Hiç merak etmeyin birazdan MainActivity'e geçince gidecektir.



MainActivity'de ilk başta göndereceğimiz input değerin bir key'i olması için const oluşturuyoruz. Sonrasında WorkManager, Constraints ve Data tipindeki variable'larımızı tanımlıyoruz.


Worker'ımıza input değerini gösteren key'in kendisiyle bir putString oluşturuyoruz. Bu sayede Worker içinde input'umuz, bu key'in karşılığındaki değerini alıyor olacaktır. Daha sonra kısıtlamalarımız olan Constraints'imizi initialize ediyoruz ve bu sırada aslında bir kısıtlama tanımlamış oluyoruz. Buradaki kısıtlamamız, eğer telefonumuz şarj oluyor ise bu request'i gerçekleştir anlamındadır. Aksi durumda telefon şarj olana kadar bu request enqueue/kuyrukta bekleyecektir. Kısıtlamalarımız sadece bundan ibaret değildir. Bazıları şunlardır:


setRequiresStorageNotLow(): true olduğu sürece depolama alanınız düşük ise kısıtlama aktif olur ve request'iniz kısıtlama koşulunun kalkmasına kadar kuyrukta bekler.

requiresBatteryNotLow(): true olduğu sürece şarj seviyeniz düşük ise kısıtlama aktif olur ve request'iniz kısıtlama koşulunun kalkmasına kadar kuyrukta bekler.

requiresDeviceIdle(): true olduğu sürece cihazınız aktif olarak kullanımdaysa kısıtlama aktif olur, boşta olduğu durumda kısıtlama ortadan kalkar.

getRequiredNetworkType():

  • CONNECTED : Herhangi bir ağ bağlantısı var ise kısıtlama ortadan kalkar.

  • METERED : Hücresel veriniz aktif olarak açık ise kısıtlama ortadan kalkar.

  • NOT_REQUIRED : Request için ağ bağlantısı gerekli değildir.

  • UNMETERED : Wifi ağına bağlı iseniz kısıtlama ortadan kalkar.

Constraints'imizi hallettikten sonra WorkManager'ımızı da oluşturduğumuza göre şimdi ilk request'imizi oluşturalım. Yapılan işin tekrarlı olmasını istemediğimiz için OneTimeWorkRequestBuilder kullanacağız. Sonrasında içinde hangi Worker sınıfını temsil edeceğini belirtmek için o sınıfın ismini yazacağız. .setInputData() ile Worker'ımıza önceden oluşturduğumuz input değerini göndereceğiz ve en sonunda bu request'in, oluşturduğumuz constraints'i kullanabilmesi için .setConstraints()'i kullanıp build edeceğiz. Oluşturduğumuz butona tıkladığımızda bu işlemlerin gerçekleşmesi için WorkManager yardımı ile enqueue oluşturup bu request'imizi onun içine atacağız. Tek bir request'imiz olduğu için şu anda böyle kullanıyoruz ama ileride göreceğimiz üzere zincirlenmiş request'lerde biraz daha farklı bir yazım şekli kullanacağız. Burada bizim işlem durumlarımız ve output'umuz da textView'da gözükecektir. Bunun içinde WorkInfo ve LiveData'yı kullanacağımızdan bahsetmiştik. workManager.getWorkInfoByIdLiveData() yöntemi ile request'imizin id'sini hedef alıp LiveData'nın sağladığı Observer ile de output'u ve işlem durumlarını takip edeceğiz ve her işlemi textView'ımıza ekleyeceğiz. Şimdi butona basalım ve sonucu görelim. Cihazımız çok büyük ihtimal default olarak şarj oluyor olarak gözükecektir.


Şimdi ilk olarak şarj olma işlemini iptal edelim ve tuşa basalım sonrasında tekrardan şarj oluyor olarak ayarlayalım. Gördüğünüz üzere ilk başta kuyrukta kendini beklemeye alan request, tekrardan şarj olmaya başladığı anda işlemi başarılı olarak gerçekleşti.


Not: Şarj işlemini tekrardan aktif ettikten sonra şarj yüzdesini en az %1 olarak arttırmanız lazım yoksa kısıtlama hala ortadan kalkmamış olacaktır.


Start komutu çıktısı

WorkRequest'imizi illa id yardımı ile takip etmemize gerek yoktur. Bir tag yardımı ile de bu işlemi yapabiliriz. İlk oluşturduğumuz request'e bir tag ekler ve sonrasında da bu tag yardımıyla isteğimizi gerçekleştiririz.



Peki biz OneTimeWorkRequest'i kullandık fakat PeriodicWorkRequest nasıl kullanıyor dersek biraz da ondan bahselim.


PeriodicWorkRequest: Öncelikle PeriodicWorkRequest'imiz bize tam olarak asla saniyesi saniyesine işlem yapabilme iddiasında bulunmaz. Kendisi varsayılan minumum 15 dakikalık bir tekrarlama işlemi uygular. Ama biz istersek çalışma periyodu zamanını 1 saat veya 1 güne çıkartabiliriz. Burası tamamen size kalmış bir durumdur.

Bu request türünü işleme sokmanın bir önceki örnekten pek bir farklı yoktur. O yüzden bütün işlemleri tekrardan yapmak yerine sadece kendisini yazacağız.



Şimdi ise request'imizin gerçekleşmesini belli bir gecikme ile nasıl yapılacağını öğrenelim.

bunun için setInitialDelay() kullanıyoruz. Request'imizi oluştururken input ve constraints'i eklediğimiz gibi aynı şekilde bunu da ekliyoruz ve içine gecikmenin ne kadar olacağı ile ilgili değerleri giriyoruz. Gecikme işlemi request'in kuyruğa girdikten sonrası için geçerlidir.




Worker'ların Zincirlenmesi



Şimdi de ilk zincirimizi oluşturmaya başlayalım.



BWorker'ımızda daha önce anlattıklarımızdan farklı hiçbir şey olmadığı için hemen MainActivity'mize geçiyoruz.



Şimdi ilk olarak yapmamız gereken şey BWorker'ın bir WorkRequest'i olması. Ben ismine hızlıca oneTimeWorkRequestB dedim. setInputData haricinde geri kalan yapının bir önceki request'imizle aynı olmasına dikkat edin. Bunun sebebi BWorker'ı yazarken fark edeceğiniz üzere getInputData ile AWorker'ın son çıktısı olan output'u almamızdan kaynaklı ve bu output BWorker'ın input'udur. BWorker'ın output çıktısı da sonuna B harfi eklenerek oluşturulacaktır. Biz burada input ve output'lar üzerinde basit bir değişiklik yaptık fakat sizler daha komplike, ihtiyaçlarınıza göre işler/örnekler yapabilirsiniz.


MainActivity'deki zincirleme işleminde artık request'imizi direk olarak enqueue'nin içine yazmak yerine, başlangıç request'ini beginWith() içine ve sonrasındaki diğer request veya sayısız request'leri de then() içine yazıyoruz.


Şimdi buraya kadar olanları anladığımıza göre constraints kavramı için basit ama güzel bir örnek uygulayalım.


Request'lerimizi tekrardan düzenleyelim.



Şimdi ilk request'imiz kısıtlama olmadan çalışacaktır. İkinci request'imiz ise kısıtlamaya sahiptir ve bu kısıtlamaya takıldı diyelim. Bunu yapmak için ilk başta kullandığımız cihazın şarj olma durumunu kapatalım ve butona basalım.


İlk olarak göreceğimiz senaryo, AWorker'ın success durumunda sonlandığı ve BWorker'ın kuyrukta(enqueue) beklediğini göreceğiz. Bundan sonra tekrardan cihazı şarj olmaya ayarlarsak BWorker'ımızın da success olacağını göreceğiz. Eğer BWorker sonrasında başka bekleyen adımlarımız olsaydı, ilk olarak kısıtlamaya takılan adımın geçilmesi beklenecekti. Yani arka arkaya sıralanmış request'lerimizde bir adımın kısıtlamaya takılması diğer adıma geçilmesi anlamına gelmez.



Paralel Zincirlenmiş Worker'lar


Buraya kadar örneklerimizi anladıysak artık biraz daha komplike işlemlere geçebiliriz. Şimdi yapmak istediğimiz şey şu olacaktır. Üç adet Worker'lardan ilk ikisi paralel çalışacak ve bu iki Worker'ın başarılı olması durumunda bir sonraki Worker'a geçilecektir.

Yapılacak işlemin diyagramı

O zaman hızlıca kodlarımıza geçelim.




Şimdi CWorker'ımızı oluşturalım.



Son olarak MainActivity kısmını tamamlayalım ve kodlarımızı yorumlayalım.



Buradaki fark ise A ve B'nin birlikte paralel olarak başlayabilmesi için bir liste şeklinde beginWith()'in içine eklenmesidir. Bu sayede başlangıç olarak iki request paralel olarak çalışacak ve ikisinin de başarılı olması durumunda CWorker'ın request'ine geçecektir.


CWorker'ımız zincirin son halkası olduğu için sıra ona geldiğinde A ve B'nin output'ları oluşmuş olacaktır. Bizde bu iki output'u alırız (istersek ikisinden birini alabilirdik veya hiç birini almayabilirdik) ve bunları kullanarak CWorker'ının output'unu oluşturmuş oluruz.



Sizlerle paylaşacağım GitHub reposunda son işlemimizle aynı mantıkta olup iki zincirden oluşmak yerine son zinciri de paralel olan üç zincirli bir WorkManager örneği paylaşacağım. İkinci zincire kadar bütün işlemler aynı olup son zinciri de bir önceki zinciriyle tamamiyle benzer bir mantıkta yapılmıştır. Benim sizlere tavsiyem buraya kadar gelip konuyu anladıysanız benim repomu önce kendiniz o repoya bakmadan yapmanız ve daha sonrasında da kendi yaptığınız ile buraya attığım repoyu karşılaştırmanızdır.


GitHub Repo: https://github.com/mskinik/Kotlin-WorkManager


Yazı için faydalandığım kaynaklar:


#workmanager #android #kotlin #jetpack

Komünite

Platform

Mobiler.dev Anasayfa
  • Twitter
  • Instagram
  • development_düzenlendi_düzenlendi
  • Youtube
  • slack-icon-black_edited_edited_edited
  • Gri LinkedIn Simge
imageedit_2_9667998092.png
JetBrains Hakkında Detaylı Bilgi Alın

© 2020 by mobiler.dev

Kurumsal Yazar Hesapları

adesso.png
mobilerdevLogo.jpg
Yazarlık Başvurusu Hakkında Bilgi Alın, Başvuru Yapın.
Topluluk Yazarlarını Tanıyın