Эволюция планировщиков задач

916b2b50d1c958f9ed1c008623065b8a?s=47 MOSDROID
November 10, 2018

Эволюция планировщиков задач

Алексей Макаров, Funcorp – MOSDROID #13 Aluminium


Мы проследим за развитием инструментов для выполнения фоновых задач. Рассмотрим проблемы, которые решали разработчики Google при создании официального инструментария. И какие способы были предложены Android сообществом.

916b2b50d1c958f9ed1c008623065b8a?s=128

MOSDROID

November 10, 2018
Tweet

Transcript

  1. 2.

    FunCorp Не являемся стартапом Приложению iFunny 5+ лет
 Входим в

    Топ-50 развлекательных приложений в США 50kk установок 
 4,5k DAO
  2. 7.

    AlarmManager, Handler, Service Плюсы:
 Доступно изначально Доступно на всех девайсах


    Минусы:
 Система всячески ограничивает работу
 Нет запусков по условию API минимальное и нужно писать много кода
  3. 9.

    JobScheduler public class JobSchedulerService extends JobService { @Override public boolean

    onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
  4. 10.

    JobScheduler 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);
  5. 13.

    JobScheduler Плюсы:
 API довольно простое Можно задавать условия запуска
 Минусы:


    Доступно с API 21 Фактически только с API 23 Легко ошибиться
  6. 15.

    GCM Network Manager public class GcmNetworkManagerService extends GcmTaskService { @Override

    public int onRunTask(TaskParams taskParams) { doWork(taskParams); return 0; } }
  7. 16.

    GCM Network Manager 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);
  8. 17.

    GCM Network Manager Плюсы:
 API аналогично JobScheduler Доступно с API

    9
 Минусы:
 Необходимо иметь Google Play Services Легко ошибиться
  9. 19.

    Что такое WakeLock Механизм, который позволяет держать ваше устройство в

    активном состоянии PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE) PowerManager.WakeLock wl = pm.newWakeLock(PARTIAL_WAKE_LOCK, «name») wl.acquire(timeout) - держать блокировку wl.release() - отпустить
  10. 20.

    WakefulBroadcastReceiver public class SimpleWakefulReceiver extends WakefulBroadcastReceiver { @Override public void

    onReceive(Context context, Intent intent) { Intent service = new Intent(context, SimpleWakefulService.class); startWakefulService(context, service); } }
  11. 21.
  12. 26.

    App Standby Имеет процесс на переднем плане
 Имеет активную нотификацию


    Добавлено в список исключений Приложение отправляется в изоляцию если не подходит под условия:
  13. 30.

    FirebaseJobDispatcher public class JobSchedulerService extends JobService { @Override public boolean

    onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
  14. 32.

    FirebaseJobDispatcher 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);
  15. 33.

    FirebaseJobDispatcher Плюсы:
 API аналогично JobScheduler Доступно с API 9
 Минусы:


    Необходимо иметь Google Play Services Легко ошибиться
  16. 36.

    Android Job by Evernote class SendLogsJobCreator : JobCreator { override

    fun create(tag: String): Job? { when (tag) { SendLogsJobTAG -> { return SendLogsJob() } } return null } }
  17. 37.

    Android Job by Evernote class SendLogsJob : Job() { override

    fun onRunJob(params: Params): Result { return doWork(params) } }
  18. 39.

    Android Job by Evernote Плюсы:
 Удобное API Поддерживается на всех

    версиях
 Не нужны Google Play Services
 Минусы:
 Стороннее решение
  19. 44.

    Как запускать задачи По системному событию Без системного события Откладываемые

    Сейчас Откладываемые Сейчас Требуется только в рамках UI ThreadPool + BroadcastReceiver ThreadPool Требуется вне UI WorkManager Foreground Service WorkManager Foreground Service
  20. 45.

    WorkManager. Внутри Если API >= 23 — JobScheduler Затем
 Пытается

    найти FirebaseScheduler
 Затем
 AlarmScheduler (AlarmManager + BroadcastReceiver)
  21. 46.

    createBestAvailableBackgroundScheduler() 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); } }
  22. 47.

    WorkManager. Концепт Worker - логика работы WorkRequest - логика запуска

    задачи WorkRequest.Builder - параметры Constraints - условия WorkManager - менеджер, который управляет задачами WorkStatus - статус задачи
  23. 53.

    JobRunner fun likePost(content: IFunnyContent) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED)

    .build() val input = Data.Builder() .putString(LikeContentJob.ID, content.id) .build() }
  24. 54.

    JobRunner 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() }
  25. 55.

    JobRunner 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) }
  26. 56.

    Worker class LikeContentJob : BaseJob() { @Inject lateInit var iFunnyContentDao:

    IFunnyContentDao override fun performJob(params: Data): Worker.Result { val contentId = params.getString(ID) val result = RestRequest.Content.like(contentId) if (result.isSuccessful()) { iFunnyContentDao.like(contentId) return Worker.Result.SUCCESS } else { iFunnyContentDao.unlike(contentId) return Worker.Result.FAILURE } } }
  27. 57.

    Interactor class LikePostInteractor @Inject constructor( val iFunnyContentDao: IFunnyContentDao, val jobRunner:

    JobRunner) : Interactor { fun execute() { iFunnyContentDao.like(getContent().id) jobRunner.likePost(getContent()) } }
  28. 58.

    ViewModel 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) } } }
  29. 59.

    ViewController 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() } }
  30. 60.

    WorkManager. Миграция с Android Job by Evernote @Singleton public class

    JobRunner { public void runPushRegisterJob(int maxRetries) { PersistableBundleCompat bundle = PushRegisterJob.getPersistableBundle(maxRetries); .setUpdateCurrent(true) .setExtras(bundle) .build() .scheduleAsync(); } }
  31. 61.

    WorkManager. Миграция с Android Job by Evernote @Singleton public class

    JobRunner { public UUID runPushRegisterJob(int maxRetries) { OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(PushRegisterJob.class) .setInputData(PushRegisterJob.getPersistableBundle(maxRetries)) .addTag(PushRegisterJob.TAG) .build(); WorkManager.getInstance().enqueue(request); return request.getId(); } }
  32. 62.

    WorkManager. Миграция с Android Job by Evernote 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 }
  33. 63.

    WorkManager. Миграция с Android Job by Evernote @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); } } }
  34. 64.

    WorkManager. Миграция с Android Job by Evernote Ограничение на размер

    входных данных (setInputData) - 10KB Ограничение на размер input - 10kb
  35. 66.

    WorkManager. Как тестировать fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver

    = WorkManagerTestInitHelper.getTestDriver() } implementation "android.arch.work:work-testing:$arch_version"
  36. 67.

    WorkManager. Как тестировать fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver

    = WorkManagerTestInitHelper.getTestDriver() WorkerInjectorProvider.setInjector(TestInjector()) // mock dependencies } implementation "android.arch.work:work-testing:$arch_version"
  37. 68.

    WorkManager. Как тестировать fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver

    = WorkManagerTestInitHelper.getTestDriver() WorkerInjectorProvider.setInjector(TestInjector()) // mock dependencies val id = jobRunner.runPushRegisterJob() testDriver.setAllConstraintsMet(id) } implementation "android.arch.work:work-testing:$arch_version"
  38. 69.

    WorkManager. Как тестировать fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver

    = WorkManagerTestInitHelper.getTestDriver() WorkerInjectorProvider.setInjector(TestInjector()) // mock dependencies val id = jobRunner.runPushRegisterJob() testDriver.setAllConstraintsMet(id) Assert.assertTrue(…) } implementation "android.arch.work:work-testing:$arch_version"
  39. 73.

    WorkManager. Подводя итог JobScheduler, GCM Network Manager, FirebaseJobDispatcher Android Job

    by Evernote Внутри переходит на WorkManager Критичные баги размываются между решениями
  40. 74.

    WorkManager. Подводя итог JobScheduler, GCM Network Manager, FirebaseJobDispatcher Android Job

    by Evernote Внутри переходит на WorkManager Критичные баги размываются между решениями WorkManager API LEVEL 9+ Не зависит от Google Play Services Chaining/InputMergers Реактивный подход Поддержка от Google (я надеюсь)