Evolusi Penjadwal Tugas



Aplikasi iFunny yang kami kerjakan telah tersedia di toko selama lebih dari lima tahun. Selama masa ini, tim seluler harus melalui berbagai pendekatan dan migrasi antar alat, dan setahun yang lalu ada waktu untuk beralih dari solusi yang ditulis sendiri dan melihat ke arah sesuatu yang lebih "modis" dan tersebar luas. Artikel ini membahas sedikit tentang apa yang telah dipelajari, solusi apa yang telah dilihat, dan apa yang akhirnya mereka temukan.

Mengapa kita membutuhkan semua ini?

Mari kita segera memutuskan untuk menghormati apa artikel ini dan mengapa topik ini ternyata penting bagi tim pengembangan Android:

  1. Ada banyak skenario ketika Anda perlu menjalankan tugas di luar kerangka antarmuka pengguna yang aktif;
  2. sistem memberlakukan sejumlah besar pembatasan pada peluncuran tugas-tugas tersebut;
  3. Ternyata cukup sulit untuk memilih antara solusi yang ada, karena masing-masing alat memiliki pro dan kontra.

Kronologi perkembangan acara


Android 0

AlarmManager, Handler, Layanan


Awalnya, solusi mereka diimplementasikan untuk meluncurkan tugas berbasis latar belakang berdasarkan layanan. Ada juga mekanisme yang menghubungkan tugas dengan siklus hidup dan dapat membatalkan dan mengembalikannya. Ini cocok untuk tim untuk waktu yang lama, karena platform tidak memberlakukan batasan pada tugas-tugas tersebut.
Google menyarankan untuk melakukan ini berdasarkan diagram berikut:



Pada akhir 2018, tidak ada gunanya memahami hal ini, cukup untuk menilai skala bencana.
Bahkan, tidak ada yang peduli berapa banyak pekerjaan yang terjadi di latar belakang. Aplikasi melakukan apa yang mereka inginkan dan kapan mereka mau.

Pro :
tersedia di mana-mana;
dapat diakses oleh semua.

Cons :
sistem membatasi pekerjaan dengan segala cara;
tidak ada peluncuran berdasarkan kondisi;
API minimal dan Anda perlu menulis banyak kode.

Android 5. Lollipop

Penjadwal Pekerjaan


Setelah 5 (!) Tahun, mendekati tahun 2015, Google memperhatikan bahwa tugas-tugas diluncurkan dengan tidak efisien. Pengguna mulai mengeluh secara teratur bahwa ponsel mereka hampir habis hanya dengan berbaring di meja atau di saku mereka.

Dengan rilis Android 5, alat seperti JobScheduler telah muncul. Ini adalah mekanisme yang dengan bantuannya dimungkinkan untuk melakukan berbagai pekerjaan di latar belakang, yang awalnya dioptimalkan dan disederhanakan karena sistem terpusat untuk meluncurkan tugas-tugas ini dan kemampuan untuk menetapkan kondisi untuk peluncuran ini.

Dalam kode, semua ini terlihat cukup sederhana: sebuah layanan diumumkan di mana peristiwa awal dan akhir datang.
Dari nuansa: jika Anda ingin melakukan pekerjaan secara serempak, maka dari onStartJob Anda harus memulai streaming; yang utama adalah jangan lupa memanggil metode jobFinished di akhir pekerjaan, jika tidak sistem tidak akan melepaskan WakeLock, tugas Anda tidak akan dianggap selesai dan akan hilang.

public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } } 

Dari mana saja di aplikasi, Anda dapat memulai pekerjaan ini. Tugas dilakukan dalam proses kami, tetapi dimulai pada tingkat IPC. Ada mekanisme terpusat yang mengontrol eksekusi mereka dan membangun aplikasi hanya pada saat-saat yang diperlukan untuk ini. Anda juga dapat mengatur berbagai kondisi pemicu dan mentransfer data melalui Bundle.

 JobInfo task = new JobInfo.Builder(JOB_ID, serviceName) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .build(); JobScheduler scheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE); scheduler.schedule(task); 

Secara umum, dibandingkan dengan tidak sama sekali, ini sudah menjadi sesuatu. Tetapi mekanisme ini hanya tersedia dengan API 21, dan pada saat rilis Android 5.0 akan aneh untuk berhenti mendukung semua perangkat lama (3 tahun telah berlalu, dan kami masih mendukung merangkak).

Pro :
APInya sederhana;
ketentuan untuk peluncuran.

Cons :
Tersedia mulai dengan API 21
pada kenyataannya, hanya dengan API 23;
mudah melakukan kesalahan.

Android 5. Lollipop

Manajer jaringan Gcm


Analog dari JobScheduler - GCM Network Manager juga disajikan. Ini adalah pustaka yang menyediakan fungsionalitas serupa, tetapi sudah bekerja dengan API 9. Benar, sebagai gantinya diperlukan Layanan Google Play. Rupanya, fungsionalitas yang diperlukan untuk JobScheduler untuk bekerja, mulai memberikan tidak hanya melalui versi Android, tetapi juga di tingkat GPS. Perlu dicatat bahwa pengembang kerangka kerja berubah pikiran dengan sangat cepat dan memutuskan untuk tidak menghubungkan masa depan mereka dengan GPS. Terima kasih kepada mereka untuk itu.

Semuanya terlihat sangat identik. Layanan yang sama:

 public class GcmNetworkManagerService extends GcmTaskService { @Override public int onRunTask(TaskParams taskParams) { doWork(taskParams); return 0; } } 

Peluncuran tugas yang sama:

 OneoffTask task = new OneoffTask.Builder() .setService(GcmNetworkManagerService.class) .setTag(TAG) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build(); GcmNetworkManager mGcmNetworkManager = GcmNetworkManager.getInstance(this); mGcmNetworkManager.schedule(task); 

Kesamaan arsitektur ini ditentukan oleh fungsionalitas yang diwarisi dan keinginan untuk mendapatkan migrasi sederhana antar alat.

Pro :
API mirip dengan JobScheduler;
Tersedia mulai dengan API 9.

Cons :
Anda harus memiliki Layanan Google Play
mudah melakukan kesalahan.

Android 5. Lollipop

WakefulBroadcastReceiver


Selanjutnya, saya akan menulis beberapa kata tentang salah satu mekanisme dasar yang digunakan dalam JobScheduler dan tersedia langsung untuk pengembang. Ini adalah WakeLock dan WakefulBroadcastReceiver berbasisnya.

Menggunakan WakeLock, Anda dapat mencegah sistem agar tidak ditangguhkan, yakni menjaga perangkat dalam keadaan aktif. Ini perlu jika kita ingin melakukan beberapa pekerjaan penting.
Saat membuat WakeLock, Anda dapat menentukan pengaturannya: pegang CPU, layar, atau keyboard.

 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE) PowerManager.WakeLock wl = pm.newWakeLock(PARTIAL_WAKE_LOCK, "name") wl.acquire(timeout); 

Berdasarkan mekanisme ini, WakefulBroadcastReceiver berfungsi. Kami memulai layanan dan menahan WakeLock.

 public class SimpleWakefulReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent service = new Intent(context, SimpleWakefulService.class); startWakefulService(context, service); } } 

Setelah layanan menyelesaikan pekerjaan yang diperlukan, kami merilisnya melalui metode serupa.

Melalui 4 versi, BroadcastReceiver ini akan menjadi usang, dan alternatif berikut akan dijelaskan pada developer.android.com:

  • Penjadwal Pekerjaan;
  • Syncadapter
  • UnduhManager
  • FLAG_KEEP_SCREEN_ON untuk Jendela.

Android 6. Marshmallow

DozeMode: Tidur saat bepergian


Kemudian Google mulai menerapkan berbagai optimasi untuk aplikasi yang berjalan di perangkat. Tetapi apa yang dimaksud optimasi untuk pengguna adalah batasan untuk pengembang.

Langkah pertama adalah DozeMode, yang menempatkan perangkat ke mode tidur jika tidak digunakan selama waktu tertentu. Dalam versi pertama, itu berlangsung satu jam, dalam versi berikutnya durasi tidur dikurangi menjadi 30 menit. Secara berkala, telepon bangun, melakukan semua tugas yang tertunda dan tertidur lagi. Jendela DozeMode mengembang secara eksponensial. Semua transisi antar mode dapat dilacak melalui adb.

Ketika DozeMode terjadi, pembatasan berikut diberlakukan pada aplikasi:

  • sistem mengabaikan semua WakeLock;
  • AlarmManager tertunda;
  • JobScheduler tidak berfungsi;
  • SyncAdapter tidak berfungsi;
  • akses jaringan terbatas.

Anda juga dapat menambahkan aplikasi Anda ke daftar putih sehingga tidak termasuk dalam batasan DozeMode, tetapi setidaknya Samsung sepenuhnya mengabaikan daftar ini.

Android 6. Marshmallow

AppStandby: Aplikasi Tidak Aktif


Sistem mengidentifikasi aplikasi yang tidak aktif, dan memberlakukan semua pembatasan yang sama pada mereka seperti di DozeMode.
Aplikasi dikirim ke isolasi jika:

  • tidak memiliki proses di latar depan;
  • tidak memiliki notifikasi aktif;
  • tidak ditambahkan ke daftar pengecualian.

Android 7. Nougat

Optimalisasi Latar Belakang. Langsing


Svelte adalah proyek di mana Google berusaha mengoptimalkan konsumsi RAM oleh aplikasi dan sistem itu sendiri.
Di Android 7, dalam kerangka proyek ini, diputuskan bahwa Siaran tersirat tidak terlalu efektif, karena disimak oleh sejumlah besar aplikasi dan sistem menghabiskan banyak sumber daya saat peristiwa ini terjadi. Oleh karena itu, jenis peristiwa berikut ini dilarang untuk dideklarasikan dalam manifes:

  • CONNECTIVITY_ACTION;
  • ACTION_NEW_PICTURE;
  • ACTION_NEW_VIDEO.

Android 7. Nougat

FirebaseJobDispatcher


Pada saat yang sama, versi baru kerangka peluncuran tugas diterbitkan - FirebaseJobDispatcher. Bahkan, itu adalah NetworkManager GCM yang telah selesai, yang dirapikan sedikit dan dibuat sedikit lebih fleksibel.

Secara visual, semuanya tampak persis sama. Layanan yang sama:

 public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } } 

Satu-satunya perbedaan di antara dia adalah kemampuan untuk menginstal drivernya. Pengemudi adalah kelas yang bertanggung jawab atas strategi peluncuran tugas.

Peluncuran tugas itu sendiri tidak berubah dari waktu ke waktu.

 FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); Job task = dispatcher.newJobBuilder() .setService(FirebaseJobDispatcherService.class) .setTag(TAG) .setConstraints(Constraint.ON_UNMETERED_NETWORK, Constraint.DEVICE_IDLE) .build(); dispatcher.mustSchedule(task); 

Pro :
API mirip dengan JobScheduler;
Tersedia mulai dengan API 9.

Cons :
Anda harus memiliki Layanan Google Play
mudah melakukan kesalahan.

Sangat menganjurkan untuk menginstal driver saya untuk menyingkirkan GPS. Kami bahkan mencari, tetapi akhirnya menemukan yang berikut:





Google mengetahui hal ini, tetapi tugas ini tetap terbuka selama beberapa tahun.

Android 7. Nougat

Android Job oleh Evernote


Akibatnya, masyarakat tidak tahan, dan solusi buatan sendiri muncul dalam bentuk perpustakaan dari Evernote. Itu bukan satu-satunya, tetapi itu adalah solusi dari Evernote yang mampu membangun dirinya sendiri dan “masuk ke masyarakat”.

Dalam hal arsitektur, perpustakaan ini lebih nyaman daripada pendahulunya.
Entitas yang bertanggung jawab untuk membuat tugas telah muncul. Dalam kasus JobScheduler, mereka diciptakan melalui refleksi.

 class SendLogsJobCreator : JobCreator { override fun create(tag: String): Job? { when (tag) { SendLogsJob.TAG -> return SendLogsJob() } return null } } 

Ada kelas yang terpisah, yang merupakan tugas itu sendiri. Di JobScheduler, ini semua dibuang ke switch di dalam StartJob.

 class SendLogsJob : Job() { override fun onRunJob(params: Params): Result { return doWork(params) } } 

Peluncuran tugas identik, tetapi selain acara yang diwariskan, Evernote juga menambahkan sendiri, seperti meluncurkan tugas harian, tugas unik, dan meluncurkan di dalam jendela.

 new JobRequest.Builder(JOB_ID) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED) .build() .scheduleAsync(); 

Pro :
API yang nyaman;
didukung di semua versi;
Tidak perlu Layanan Google Play.

Cons :
solusi pihak ketiga.

Orang-orang secara aktif mendukung perpustakaan mereka. Meskipun ada beberapa masalah kritis, ini bekerja pada semua versi dan pada semua perangkat. Akibatnya, tahun lalu tim Android kami memilih solusi dari Evernote, karena perpustakaan dari Google memotong lapisan besar perangkat yang tidak dapat mereka dukung.
Di dalam, ia mengerjakan solusi dari Google, dalam kasus-kasus ekstrem - dengan AlarmManager.

Android 8. Oreo

Batas Eksekusi Latar Belakang


Mari kita kembali ke batasan kita. Dengan munculnya Android baru, optimisasi baru telah datang. Orang-orang dari Google menemukan masalah lain. Kali ini semuanya berubah menjadi layanan dan siaran (ya, bukan hal baru).

  • startService jika aplikasi di latar belakang
  • siaran tersirat dalam manifes

Pertama, dilarang memulai layanan dari latar belakang. Dalam "kerangka hukum" tetap hanya layanan foreground. Layanan sekarang dapat dikatakan sudah usang.
Batasan kedua adalah Siaran yang sama. Kali ini menjadi terlarang untuk mendaftarkan SEMUA Siaran tersirat dalam manifes. Siaran Tersirat adalah Siaran, yang dimaksudkan tidak hanya untuk aplikasi kita. Misalnya, ada Tindakan ACTION_PACKAGE_REPLACED, dan ada ACTION_MY_PACKAGE_REPLACED. Jadi, yang pertama adalah implisit.

Tetapi semua Siaran masih dapat didaftarkan melalui Context.registerBroadcast.

Android 9. Pai

Pekerja


Pada optimasi ini sudah berhenti. Mungkin perangkat mulai bekerja dengan cepat dan hati-hati dalam hal konsumsi energi; mungkin pengguna kurang mengeluh tentang hal itu.
Di Android 9, para pengembang kerangka kerja secara menyeluruh mendekati alat untuk meluncurkan tugas. Dalam upaya untuk menyelesaikan semua masalah yang mendesak, perpustakaan diperkenalkan di Google I / O untuk meluncurkan tugas latar belakang WorkManager.

Google baru-baru ini berusaha untuk membentuk visinya tentang arsitektur aplikasi Android dan memberi pengembang alat yang diperlukan untuk ini. Jadi ada komponen arsitektur dengan LiveData, ViewModel dan Room. WorkManager terlihat seperti pelengkap yang masuk akal untuk pendekatan dan paradigma mereka.

Jika kita berbicara tentang bagaimana WorkManager diatur di dalam, maka tidak ada terobosan teknologi di dalamnya. Bahkan, ini adalah pembungkus solusi yang ada: JobScheduler, FirebaseJobDispatcher dan AlarmManager.

buatBestTersediaBackgroundScheduler
 static Scheduler createBestAvailableBackgroundScheduler(Context, WorkManager) { if (Build.VERSION.SDK_INT >= MIN_JOB_SCHEDULER_API_LEVEL) { return new SystemJobScheduler(context, workManager); } try { return tryCreateFirebaseJobScheduler(context); } catch (Exception e) { return new SystemAlarmScheduler(context); } } 


Kode seleksi sangat sederhana. Tetapi harus dicatat bahwa JobScheduler tersedia mulai dengan API 21, tetapi mereka hanya menggunakannya dengan API 23, karena versi pertama agak tidak stabil.

Jika versi lebih rendah dari 23, maka melalui refleksi kami mencoba menemukan FirebaseJobDispatcher, jika tidak kami menggunakan AlarmManager.

Perlu dicatat bahwa bungkusnya keluar cukup fleksibel. Kali ini, pengembang memecah segalanya menjadi entitas yang terpisah, dan secara arsitektur terlihat nyaman:

  • Pekerja - logika kerja;
  • WorkRequest - logika peluncuran tugas;
  • WorkRequest.Builder - parameter;
  • Batasan - kondisi;
  • WorkManager - seorang manajer yang mengelola tugas;
  • WorkStatus - status tugas.




Kondisi peluncuran diwarisi dari JobScheduler.
Dapat dicatat bahwa pemicu untuk mengubah URI hanya muncul dengan API 23. Selain itu, Anda dapat berlangganan perubahan tidak hanya URI tertentu, tetapi juga semua bersarang di dalamnya menggunakan bendera dalam metode.

Jika kita berbicara tentang kita, maka pada tahap alfa, diputuskan untuk beralih ke WorkManager.
Ada beberapa alasan untuk ini. Evernote memiliki beberapa bug penting yang dijanjikan pengembang untuk diperbaiki dengan transisi ke versi dengan WorkManager terintegrasi. Dan mereka sendiri setuju bahwa keputusan dari Google meniadakan kelebihan Evernote. Selain itu, solusi ini sangat sesuai dengan arsitektur kami, karena kami menggunakan Komponen Arsitektur.

Lebih lanjut, saya ingin menunjukkan dengan contoh sederhana bagaimana kami mencoba menggunakan pendekatan ini. Pada saat yang sama, tidak terlalu penting apakah Anda memiliki WorkManager atau JobScheduler.



Mari kita lihat contoh dengan kasus yang sangat sederhana: mengklik publikasi ulang atau sejenisnya.

Sekarang semua aplikasi berusaha untuk tidak memblokir permintaan ke jaringan, karena hal ini membuat pengguna gugup dan membuatnya menunggu, meskipun saat ini ia dapat melakukan pembelian di dalam aplikasi atau menonton iklan.

Dalam kasus seperti itu, data lokal pertama kali berubah - pengguna segera melihat hasil tindakannya. Kemudian di latar belakang ada permintaan ke server, jika gagal, data diatur ulang ke kondisi semula.

Selanjutnya, saya akan menunjukkan contoh tampilannya bersama kami.

JobRunner berisi logika untuk meluncurkan tugas. Metodenya menggambarkan konfigurasi tugas dan lulus parameter.

JobRunner.java
 fun likePost(content: IFunnyContent) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() val input = Data.Builder() .putString(LikeContentJob.ID, content.id) .build() val request = OneTimeWorkRequest.Builder(LikeContentJob::class.java) .setInputData(input) .setConstraints(constraints) .build() WorkManager.getInstance().enqueue(request) } 


Tugas itu sendiri dalam WorkManager adalah sebagai berikut: kami mengambil id dari parameter dan memanggil metode di server untuk menyukai konten ini.

Kami memiliki kelas dasar yang berisi logika berikut:

 abstract class BaseJob : Worker() { final override fun doWork(): Result { val workerInjector = WorkerInjectorProvider.injector() workerInjector.inject(this) return performJob(inputData) } abstract fun performJob(params: Data): Result } 

Pertama, ini memungkinkan Anda untuk menjauh dari pengetahuan eksplisit Pekerja. Ini juga berisi logika injeksi ketergantungan melalui WorkerInjector.

WorkerInjectorImpl.java
 @Singleton public class WorkerInjectorImpl implements WorkerInjector { @Inject public WorkerInjectorImpl() {} @Ovierride public void inject(Worker job) { if (worker instanceof AppCrashedEventSendJob) { Injector.getAppComponent().inject((AppCrashedEventSendJob) job); } else if (worker instanceof CheckNativeCrashesJob) { Injector.getAppComponent().inject((CheckNativeCrashesJob) job); } } } 


Ini hanya proksi panggilan ke Dagger, tetapi membantu kami dalam pengujian: kami mengganti implementasi injektor dan mengimplementasikan lingkungan yang diperlukan dalam tugas.

 fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver = WorkManagerTestInitHelper.getTestDriver() WorkerInjectorProvider.setInjector(TestInjector()) // mock dependencies val id = jobRunner.runPushRegisterJob() testDriver.setAllConstraintsMet(id) Assert.assertTrue(…) } 

 class LikePostInteractor @Inject constructor( val iFunnyContentDao: IFunnyContentDao, val jobRunner: JobRunner) : Interactor { fun execute() { iFunnyContentDao.like(getContent().id) jobRunner.likePost(getContent()) } } 

Interactor adalah entitas yang ditarik oleh ViewController untuk memulai bagian skrip (dalam hal ini, seperti itu). Kami menandai konten secara lokal sebagai "diunggah" dan mengirimkan tugas untuk dieksekusi. Jika tugas gagal, sejenisnya dihapus.

 class IFunnyContentViewModel(val iFunnyContentDao: IFunnyContentDao) : ViewModel() { val likeState = MediatorLiveData<Boolean>() var iFunnyContentId = MutableLiveData<String>() private var iFunnyContentState: LiveData<IFunnyContent> = attachLiveDataToContentId(); init { likeState.addSource(iFunnyContentState) { likeState.postValue(it!!.hasLike) } } } 

Kami menggunakan Komponen Arsitektur Google: ViewModel dan LiveData. Seperti inilah tampilan ViewModel kami. Di sini kita menghubungkan pembaruan objek di DAO dengan status suka.

IFunnyContentViewController.java
 class IFunnyContentViewController @Inject constructor( private val likePostInteractor: LikePostInteractor, val viewModel: IFunnyContentViewModel) : ViewController { override fun attach(view: View) { viewModel.likeState.observe(lifecycleOwner, { updateLikeView(it!!) }) } fun onLikePost() { likePostInteractor.setContent(getContent()) likePostInteractor.execute() } } 


ViewController, di satu sisi, berlangganan untuk mengubah status sejenisnya, di sisi lain, memulai bagian naskah yang kita butuhkan.

Dan itu praktis semua kode yang kita butuhkan. Tetap menambahkan perilaku Tampilan itu sendiri dengan suka dan implementasi DAO Anda; jika Anda menggunakan Kamar, maka cukup daftarkan bidang di objek. Itu terlihat sangat sederhana dan efektif.

Untuk meringkas


JobScheduler, Manajer Jaringan GCM, FirebaseJobDispatcher:

  • jangan menggunakannya
  • jangan membaca artikel tentang mereka lagi
  • jangan menonton laporan
  • jangan berpikir yang mana untuk dipilih.

Pekerjaan Android oleh Evernote:

  • Di dalamnya mereka akan menggunakan WorkManager;
  • bug kritis kabur di antara solusi.

WorkManager:

  • API LEVEL 9+;
  • independen dari Layanan Google Play;
  • Rantai / InputMergers;
  • pendekatan reaktif;
  • dukungan dari Google (saya ingin percaya).

Source: https://habr.com/ru/post/id434680/


All Articles