Slide 1

Slide 1 text

Android Dev Summit 2019 extended Seoul WorkManager: the Beginning Sungyong An • SOUP NAVER Webtoon and more…

Slide 2

Slide 2 text

Android Dev Summit 2019 extended Seoul Before WorkManager History ⏰

Slide 3

Slide 3 text

Before WorkManager Service AlarmManager JobScheduler Foreground Background

Slide 4

Slide 4 text

Before WorkManager AlarmManager API 1 • ౠ੿ द੼ী জ੉ प೯غب۾ ৘ডೞח ӝמ • ߈ࠂ੸ੋ ੘স प೯ оמ • জ੉ प೯غ૑ ঋওਸ ٸب ࢎਊ оמ • API 19+ ੿ഛೠ दрী प೯غח Ѫਸ ࠁ੢ೞ૑ ঋ਺ (ߓఠܻ ࢎਊ ୭ࣗച ݾ੸)

Slide 5

Slide 5 text

Before WorkManager AlarmManager API 1 JobScheduler API 21 • ౠ੿ ઑѤী प೯غب۾ ੘সਸ ৘ডೞח ӝמ (֎౟ਕ௼ ࢚క, ؀ۚ੸ੋ प೯दр ١) • Doze ݽ٘ ؀਽ (ߓఠܻ ࢎਊ ѐࢶ) • API 21+ ࢎਊ оמ

Slide 6

Slide 6 text

Before WorkManager AlarmManager API 1 JobScheduler API 21 Doze mode API 23

Slide 7

Slide 7 text

Before WorkManager GCM 2015 AlarmManager API 1 JobScheduler API 21 GCMNetworkManager Doze mode API 23 • API 14+ ࢎਊ оמ • GCM Service ӝ߈ (Google Playо ࢸ஖غযঠ ೣ)

Slide 8

Slide 8 text

Before WorkManager GCM Evernote 2015 2015 AlarmManager API 1 JobScheduler API 21 GCMNetworkManager Doze mode API 23 android-job • API 14+ ࢎਊ оמ • JobScheduler (21+) • GCM Service (21-) • Not Official

Slide 9

Slide 9 text

Before WorkManager Firebase GCM Evernote 2015 2016 2015 AlarmManager API 1 JobScheduler API 21 GCMNetworkManager Doze mode API 23 JobDispatcher android-job • API 14+ ࢎਊ оמ • JobScheduler (21+) • GCM Service (21-)

Slide 10

Slide 10 text

Before WorkManager Firebase GCM Evernote 2015 2016 2015 AlarmManager API 1 JobScheduler API 21 GCMNetworkManager Doze mode API 23 JobDispatcher android-job Deprecated Now WorkManager Jetpack

Slide 11

Slide 11 text

Android Dev Summit 2019 extended Seoul WorkManager 1.0 to 2.2 Recap

Slide 12

Slide 12 text

1.0 • Scheduling background work • execution of guaranteed • constraint-aware • Backwards compatible up to API 14 • 23+: JobScheduler • 14~22: BroadcastReceiver + AlarmManager • Supports Task Chainining • Supports Rx, Coroutine • Depends on Room

Slide 13

Slide 13 text

Basic class MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { // do something! return Result.success() } } val constraints = Constraints.Builder().build() val request = OneTimeWorkRequest.Builder(MyWorker::class.java) .setConstraints(constraints) .build() WorkManager.getInstance().enqueue(request)

Slide 14

Slide 14 text

Basic - Internal WorkManager.getInstance().enqueue(request) -> WorkManagerImpl.enqueue(...) -> WorkContinuationImpl(...).enqueue() -> EnqueueRunnable(...).run() -> // Store in WorkManager DB workDatabase.workSpecDao().insertWorkSpec(...) workDatabase.dependencyDao().insertDependency(...) workDatabase.workTagDao().insert(...) workDatabase.workNameDao().insert(...) -> scheduleWorkInBackground() After force-stop, reschedule!!

Slide 15

Slide 15 text

Constraints val constraints = Constraints.Builder() .setRequiresCharging(true) .setRequiresDeviceIdle(true) // API 23+ .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .setRequiresStorageNotLow(true) .build()

Slide 16

Slide 16 text

WorkRequests val uploadWorkRequest = OneTimeWorkRequestBuilder() .setInitialDelay(10, TimeUnit.MINUTES) .setBackoffCriteria( BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .setInputData(workDataOf(KEY_IMAGE_URI to imageUriString)) .setConstraints(constraints) .addTag("cleanup") .build() val uploadWorkRequest = PeriodicWorkRequestBuilder(1, TimeUnit.HOURS) .setConstraints(constraints) .build()

Slide 17

Slide 17 text

Unique Work WorkManager.getInstance(this).enqueueUniqueWork( "uniqueWorkName", ExistingWorkPolicy.REPLACE, OneTimeWorkRequestBuilder().build() ) WorkManager.getInstance(this).enqueueUniquePeriodicWork( "uniqueWorkName", ExistingWorkPolicy.REPLACE, PeriodicWorkRequestBuilder(1, TimeUnit.HOURS).build() )

Slide 18

Slide 18 text

Task Chaining val request: OneTimeWorkRequest = ... val nextRequest: OneTimeWorkRequest = … // W/o Chaining WorkManager.getInstance().enqueue(request) // With Chaining WorkManager.getInstance() .beginWith(request) .then(nextRequest) .then(listOf(request, request, request)) .enqueue()

Slide 19

Slide 19 text

Cancel Work WorkManager.getInstance(this).cancelAllWork() val request = OneTimeWorkRequestBuilder<...>().addTag("tag_name").build() WorkManager.getInstance(this).cancelAllWorkByTag("tag_name") val request = OneTimeWorkRequestBuilder<...>().build() WorkManager.getInstance(this).cancelWorkById(request.id) WorkManager.getInstance(this).enqueueUniqueWork("uniqueWorkName", ...) WorkManager.getInstance(this).enqueueUniquePeriodicWork("uniqueWorkName", ...) WorkManager.getInstance(this).cancelUniqueWork("uniqueWorkName")

Slide 20

Slide 20 text

Worker RxWorker Worker CoroutineWorker ListenableWorker App Context WorkerParameters

Slide 21

Slide 21 text

Worker public abstract class ListenableWorker { @MainThread public abstract @NonNull ListenableFuture startWork(); public abstract static class Result { ... } } public abstract class Worker extends ListenableWorker { @WorkerThread public abstract @NonNull Result doWork(); } public abstract class RxWorker extends ListenableWorker { @MainThread public abstract Single createWork(); } abstract class CoroutineWorker(...) : ListenableWorker(...) { abstract suspend fun doWork(): Result } work-runtime work-rxjava2 work-runtime-ktx

Slide 22

Slide 22 text

2.0 • AndroidX Migration • Before: android.arch.work.* • After: androidx.work.*

Slide 23

Slide 23 text

2.1 • Supports On-Demand Initialization • Supports more testing • TestWorkerBuilder • TestListenableWorkerBuilder

Slide 24

Slide 24 text

Initialization WorkManager Initializer Application WorkManager initialize() onCreate() onCreate() Auto On Demand WorkManager getInstance(context) initialize() Application getWorkManager Configuration() Application onCreate()

Slide 25

Slide 25 text

Auto On Demand // do nothing class MyApplication() : Application(), Configuration.Provider { override getWorkManagerConfiguration() = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .build() } Initalization

Slide 26

Slide 26 text

Testing CoroutineWorker class TestWorker(...) : CoroutineWorker(context, parameters) { override suspend fun doWork(): Result { return Result.success() } } @Test fun testCoroutineWorkerTest() { runBlocking { val worker = TestListenableWorkerBuilder(context) .setInputData(workDataOf("key" to 10)) .setRunAttempCount(2) .build() val result = worker.startWork().await() assertThat(result).isEqualTo(Result.success()) } }

Slide 27

Slide 27 text

Testing Worker class TestWorker(...) : Worker(context, parameters) { override fun doWork(): Result { return Result.success() } } @Test fun testWorkerTest() { runBlocking { val executor = Executors.newSingleThreadExecutor() val worker = TestWorkerBuilder(context, executor) .setInputData(workDataOf("key" to 10)) .setRunAttempCount(2) .build() val result = worker.startWork().await() assertThat(result).isEqualTo(Result.success()) } }

Slide 28

Slide 28 text

2.2 • GCMNetworkManager ૑ਗ (API 14 ~ 22) def work_version = "2.2.0" implementation "androidx.work:work-runtime:$version" implementation "androidx.work:work-gcm:$version" Link: https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmNetworkManager ❓

Slide 29

Slide 29 text

// in 'work’ // androidx.work.impl.Schedulers public static final String GCM_SCHEDULER = "androidx.work.impl.background.gcm.GcmScheduler"; // in 'work-gcm' @Nullable private static Scheduler tryCreateGcmBasedScheduler(@NonNull Context context) { try { Class> klass = Class.forName(GCM_SCHEDULER); Scheduler scheduler = (Scheduler) klass.getConstructor(Context.class).newInstance(context); Logger.get().debug(TAG, String.format("Created %s", GCM_SCHEDULER)); return scheduler; } catch (Throwable throwable) { Logger.get().debug(TAG, "Unable to create GCM Scheduler", throwable); return null; } } Java Reflection!!

Slide 30

Slide 30 text

Dependency work-runtime work-runtime-ktx work-gcm work-lint work-rxjava2 work-testing room lifecycle android platform JobScheduler (API 23+) AlarmManager + BroadcastReceiver (API 14 ~ 22) GCM NetworkManager (API 14 ~ 22) Google Play

Slide 31

Slide 31 text

New in 2.3

Slide 32

Slide 32 text

Worker Progress Foreground Background Worker Progress Progress Result

Slide 33

Slide 33 text

Worker Progress class ProgressWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) { override suspend fun doWork(): Result { setProgress(workDataOf(PROGRESS to 25)) ... setProgress(workDataOf(PROGRESS to 50)) ... return Result.success() } companion object { const val PROGRESS = "Progress" } }

Slide 34

Slide 34 text

Observing val request = OneTimeWorkRequestBuilder().build() WorkManager.getInstance(this) .getWorkInfoByIdLiveData(request.id) .observe(this, Observer { workInfo: WorkInfo? -> if (workInfo != null) { val progress = workInfo.progress val value = progress.getInt(PROGRESS, 0) // Do something with progress information } }) Worker Progress

Slide 35

Slide 35 text

SystemFgService Foreground Worker Background Worker long-running task Foreground

Slide 36

Slide 36 text

class ForegroundWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) { private val notificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) override suspend fun doWork(): Result { setForeground(createForegroundInfo()) // do long-running tasks return Result.success() } private fun createForegroundInfo(): ForegroundInfo { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Create a Notification channel } val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) Foreground Worker

Slide 37

Slide 37 text

override suspend fun doWork(): Result { setForeground(createForegroundInfo()) // do long-running tasks return Result.success() } private fun createForegroundInfo(): ForegroundInfo { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Create a Notification channel } val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("ForegroundWorker") .setContentText("In Progress ...") .setOngoing(true) .build() return ForegroundInfo(0, notification) } } Foreground Worker Notification ID

Slide 38

Slide 38 text

return Result.success() } private fun createForegroundInfo(): ForegroundInfo { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Create a Notification channel } val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("ForegroundWorker") .setContentText("In Progress ...") .setOngoing(true) .addAction(android.R.drawable.ic_delete, "Cancel", WorkManager.getInstance(applicationContext) .createCancelPendingIntent(id)) .build() return ForegroundInfo(0, notification) } } Foreground Worker with Cancel action Worker ID

Slide 39

Slide 39 text

2.3 (βeta) • Supports Progress • Supports long-running Worker • Added Lint Rules

Slide 40

Slide 40 text

Summary • WorkManager = ⏰ AlarmManager + JobScheduler (+ GCM) • WorkManagerח force-stop غ؊ۄب, জਸ ੤प೯ೞݶ ੘সਸ ׮द ৘ডೠ׮. • GCM, Evernote, Firebase ١੄ Job ӝמਸ ࢎਊೞҊ ੓঻׮ݶ, ੉ઁח WorkManager۽ ߸҃೧ঠ ೡ ٸ! • 2.3ীࢲח Workerী Progress, Foreground ӝמب ઁҕೠ׮.

Slide 41

Slide 41 text

Android Dev Summit 2019 extended Seoul Happy New Year!