Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Mobile Fest 2018. Yonatan Levin. WTF with Android Background Restrictions

386 views

Published on

With recent Android releases the wild west of endless running threads is over.
No more smoking barrels of "last 5% battery".
You are required to act and seriously consider how you became the excellent citizen of Android device and play side by side with a marshall law of background tasks. Or die trying.
It's a talk we will explore how memory affect a background process, what solutions we have today and how new WorkManager can save our lives and galaxy :)

Published in: Education
  • Be the first to comment

  • Be the first to like this

Mobile Fest 2018. Yonatan Levin. WTF with Android Background Restrictions

  1. 1. Hi !
  2. 2. Yonatan V.Levin levin.yonatan parahall Google Developer Expert CTO & Co Founder
  3. 3. Why app is crashed when got an update from widget?
  4. 4. Background
  5. 5. Android 101
  6. 6. Memory @ Android
  7. 7. != SWAP Space No Swap space
  8. 8. Activity Manager Process Process Process Process Process Process oom_adj: 0 oom_adj: 2 oom_adj: 8 oom_adj: 7 oom_adj: 1 oom_adj: 9
  9. 9. Activity Manager Process Process Process Process Process Process oom_adj: 0 oom_adj: 2 oom_adj: 8 oom_adj: 7 oom_adj: 1 oom_adj: 9 OOM Killer Threshold: 10Kb, 7
  10. 10. Activity Manager Process Process Process oom_adj: 0 oom_adj: 2 oom_adj: 1 Free memory
  11. 11. bash-3.2# logcat -t 1000 | grep "has died" 01-29 15:01:09.577 1278 1283 I ActivityManager: Process com.motorola.calendar (pid 8595) has died. 01-29 15:07:46.957 1278 1278 I ActivityManager: Process com.dropbox.android (pid 8606) has died. bash-3.2# dmesg | grep sigkill <4>[58305.115783] send sigkill to 8595 (torola.calendar), adj 13, size 3198 <4>[58702.255462] send sigkill to 8606 (dropbox.android), adj 14, size 2942
  12. 12. What is a Service A Service is an application component that can perform long-running operations in the background, and it does not provide a user interface.
  13. 13. Why Service One of 4 core components → App entry points Stay alive when users navigate out of your app Can be cached Can be run on separate process
  14. 14. Rowing direction
  15. 15. Changes in O The startService() method now throws an IllegalStateException if an app targeting Android 8.0 tries to use that method in a situation when it isn't permitted to create background services.
  16. 16. More is coming ● August 2018: New apps required to target API level 26 (Android 8.0) or higher. ● November 2018: Updates to existing apps required to target API level 26 or higher. ● 2019 onwards: Each year the targetSdkVersion requirement will advance. Within one year following each Android dessert release, new apps and app updates will need to target the corresponding API level or higher.
  17. 17. The king is dead. Long live the king
  18. 18. "The service as we know it today - is deprecated. It's no longer allowed to fulfill the main purpose - execute the long-running task in the background. Therefore it's no longer usable" Yoni Levin
  19. 19. Let’s explore very simple scenario
  20. 20. App in Foreground - Let’s execute it now! ExecutorService executor = Executors.newFixedThreadPool(threads); executor.submit(myWork);
  21. 21. App in Foreground - Let’s execute it now! ExecutorService executor = Executors.newFixedThreadPool(threads); executor.submit(myWork);
  22. 22. Login succeeded?
  23. 23. OkHttp connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; retry = 3; Worst case scenario: 3 * 30 = 90 seconds.
  24. 24. Build long running task Maybe not so long running task, but it can takes minutes to execute and update UI when it finished Wait, but what if the user offline?
  25. 25. So, we need to switch to use a service.
  26. 26. But you can’t!!!!
  27. 27. Don’t put your hands down
  28. 28. Let’s use JobScheduler JobScheduler mJobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE); int jobInfoNetworkType = convertNetworkType(constraints.getRequiredNetworkType()); PersistableBundle extras = new PersistableBundle(); extras.putBoolean(EXTRA_IS_PERIODIC, isPeriodic); JobInfo.Builder builder = new JobInfo.Builder(jobId, serviceComponent) .setRequiredNetworkType(jobInfoNetworkType) .setRequiresCharging(false) .setRequiresDeviceIdle(false) .setExtras(extras) .setPersisted(true) .build(); mJobScheduler.schedule(jobInfo);
  29. 29. if (Build.VERSION.SDK_INT >= 23) { //use JobService }
  30. 30. But I have minSDK < 21 23
  31. 31. JobDispatcher
  32. 32. Job myJob = firebaseJobDispatcher.newJobBuilder() .setService(SmartService.class) .setTag(SmartService.LOCATION_SMART_JOB) .setRecurring(true) .setLifetime(FOREVER) .setTrigger(Trigger.executionWindow(0, 60 * 5)) .setReplaceCurrent(false) .setConstraints(ON_ANY_NETWORK) .build(); firebaseJobDispatcher.mustSchedule(myJob);
  33. 33. No promise when a job executed
  34. 34. And more…
  35. 35. +--- com.firebase:firebase-jobdispatcher:0.5.2 | +--- com.android.support:support-v4:25.0.0 -> 25.3.0 (*) | --- com.google.android.gms:play-services-gcm:10.0.1 -> 10.2.1 | +--- com.google.android.gms:play-services-base:10.2.1 | | +--- com.google.android.gms:play-services-basement:10.2.1 | | | --- com.android.support:support-v4:24.0.0 -> 25.3.0 (*) | | --- com.google.android.gms:play-services-tasks:10.2.1 | | --- com.google.android.gms:play-services-basement:10.2.1 (*) | +--- com.google.android.gms:play-services-basement:10.2.1 (*) | --- com.google.android.gms:play-services-iid:10.2.1 | +--- com.google.android.gms:play-services-base:10.2.1 (*) | --- com.google.android.gms:play-services-basement:10.2.1 (*)
  36. 36. +--- com.firebase:firebase-jobdispatcher:0.5.2 | +--- com.android.support:support-v4:25.0.0 -> 25.3.0 (*) | --- com.google.android.gms:play-services-gcm:10.0.1 -> 10.2.1 | +--- com.google.android.gms:play-services-base:10.2.1 | | +--- com.google.android.gms:play-services-basement:10.2.1 | | | --- com.android.support:support-v4:24.0.0 -> 25.3.0 (*) | | --- com.google.android.gms:play-services-tasks:10.2.1 | | --- com.google.android.gms:play-services-basement:10.2.1 (*) | +--- com.google.android.gms:play-services-basement:10.2.1 (*) | --- com.google.android.gms:play-services-iid:10.2.1 | +--- com.google.android.gms:play-services-base:10.2.1 (*) | --- com.google.android.gms:play-services-basement:10.2.1 (*)
  37. 37. Tens millions of devices
  38. 38. Let’s use Alarm + Executer? AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent delayMet = CommandHandler.createIntent(context, workSpecId); PendingIntent pendingIntent = PendingIntent.getService( context, alarmId, delayMet, PendingIntent.FLAG_ONE_SHOT); alarmManager.set(RTC_WAKEUP, triggerAtMillis, pendingIntent);
  39. 39. PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); powerManager.newWakeLock(PARTIAL_WAKE_LOCK, "tag").acquire(); WakeLock Pain
  40. 40. What about constraints & rescheduling?
  41. 41. Wait. But I still want to benefit from old services on pre-O devices!
  42. 42. JobIntentService Context.startService() < >= JobScheduler.enqueue() setOverrideDeadline(0)
  43. 43. JobIntentService onStartJob(JobParams) JobService onHandleWork(Intent intent); onStopJob(JobParams ) onStopCurrentWork()
  44. 44. Oh! Someone restarted a phone!
  45. 45. BOOT_COMPLETED <receiver android:name=".AppReceiver" android:permission="android.permission.RECEIVE_BOOT_COMPLETED"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
  46. 46. The fight which hard to win
  47. 47. So let’s start to think about more abstract solution
  48. 48. So here is the summary of what we need - Abstraction - Choose the best work scheduler/execution - Execute when we meet work Criteria - Work Persistency - Handle Failure and Reschedule - Remove finished job
  49. 49. WorkManager
  50. 50. WorkManager aims to simplify the developer experience by providing a first-class API for system-driven background processing. It is intended for background jobs that should run even if the app is no longer in the foreground. Where possible, it uses JobScheduler or Firebase JobDispatcher to do the work; if your app is in the foreground, it will even try to do the work directly in your process.
  51. 51. Data Data enqueue() WorkManager JobScheduler JobDispatcher Executor AlarmManager WorkDatabase WorkRequest saveWork updateWorkState publish Processor Worker WorkResult
  52. 52. Data enqueue() WorkManager JobScheduler JobDispatcher Executer AlarmManager WorkDatabase WorkRequest saveWork updateWorkState publish Processor Worker Data WorkResult
  53. 53. public class LocationUploadWorker extends Worker { ... //Upload last passed location to the server public WorkerResult doWork() { ServerReport serverReport = new ServerReport(getInputData().getDouble(LOCATION_LONG, 0),...); FirebaseDatabase db = FirebaseDatabase.getInstance(); DatabaseReference ref = db.getReference("WorkerReport"); ref.push().setValue(serverReport); return WorkerResult.SUCCESS; } }
  54. 54. public class LocationUploadWorker extends Worker { ... //Upload last passed location to the server public WorkerResult doWork() { ServerReport serverReport = new ServerReport(getInputData().getDouble(LOCATION_LONG, 0),...); FirebaseDatabase db = FirebaseDatabase.getInstance(); DatabaseReference ref = db.getReference("WorkerReport"); ref.push().setValue(serverReport); return WorkerResult.SUCCESS; } }
  55. 55. public class LocationUploadWorker extends Worker { ... //Upload last passed location to the server public WorkerResult doWork() { ServerReport serverReport = new ServerReport(getInputData().getDouble(LOCATION_LONG, 0),...); FirebaseDatabase db = FirebaseDatabase.getInstance(); DatabaseReference ref = db.getReference("WorkerReport"); ref.push().setValue(serverReport); return WorkerResult.SUCCESS; } }
  56. 56. Data enqueue() WorkManager JobScheduler JobDispatcher Executer AlarmManager WorkDatabase WorkRequest saveWork updateWorkState publish Processor Worker Data WorkResult
  57. 57. Constraints constr = new Constraints.Builder(). setRequiredNetworkType(NetworkType.CONNECTED).build(); Data inputData = new Data.Builder() .putDouble(LOCATION_LAT, location.getLatitude()) .putDouble(LOCATION_LONG, location.getLongitude()) .putLong(LOCATION_TIME, location.getTime()).build(); OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(LocationUploadWorker.class) .setConstraints(constr).setInputData(inputData).build();
  58. 58. Constraints constr = new Constraints.Builder(). setRequiredNetworkType(NetworkType.CONNECTED).build(); Data inputData = new Data.Builder() .putDouble(LOCATION_LAT, location.getLatitude()) .putDouble(LOCATION_LONG, location.getLongitude()) .putLong(LOCATION_TIME, location.getTime()).build(); OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(LocationUploadWorker.class) .setConstraints(constr).setInputData(inputData).build();
  59. 59. Constraints constr = new Constraints.Builder(). setRequiredNetworkType(NetworkType.CONNECTED).build(); Data inputData = new Data.Builder() .putDouble(LOCATION_LAT, location.getLatitude()) .putDouble(LOCATION_LONG, location.getLongitude()) .putLong(LOCATION_TIME, location.getTime()).build(); OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(LocationUploadWorker.class) .setConstraints(constr).setInputData(inputData).build();
  60. 60. Constraints constr = new Constraints.Builder(). setRequiredNetworkType(NetworkType.CONNECTED).build(); Data inputData = new Data.Builder() .putDouble(LOCATION_LAT, location.getLatitude()) .putDouble(LOCATION_LONG, location.getLongitude()) .putLong(LOCATION_TIME, location.getTime()).build(); OneTimeWorkRequest uploadWork = new OneTimeWorkRequest.Builder(LocationUploadWorker.class) .setConstraints(constr).setInputData(inputData).build();
  61. 61. Data enqueue() WorkManager JobScheduler JobDispatcher Executer AlarmManager WorkDatabase WorkRequest saveWork updateWorkState publish Processor Worker Data WorkResult
  62. 62. WorkManager.getInstance().enqueue(uploadWork);
  63. 63. Data Data enqueue() WorkManager JobScheduler JobDispatcher Executer AlarmManager WorkDatabase WorkRequest saveWork updateWorkState publish Processor Worker WorkResult
  64. 64. JobScheduler JobDispatcher Executer AlarmManager WorkDatabase updateWorkState publish Processor Data enqueue() WorkManager WorkRequest saveWork Worker publish Data WorkResult
  65. 65. Data Data enqueue() WorkManager JobScheduler JobDispatcher Executer AlarmManager WorkDatabase WorkRequest saveWork updateWorkState publish Processor Worker WorkResult
  66. 66. workManager.getStatusById(locationWork.getId()).observe( this, workStatus -> { if(workStatus!=null && workStatus.getState().isFinished()){ ... workStatus.getOutputData()... } }
  67. 67. workManager.getStatusById(locationWork.getId()).observe( this, workStatus -> { if(workStatus!=null && workStatus.getState().isFinished()){ ... workStatus.getOutputData()... } }
  68. 68. workManager.getStatusById(locationWork.getId()).observe( this, workStatus -> { if(workStatus!=null && workStatus.getState().isFinished()){ ... workStatus.getOutputData()... } }
  69. 69. PeriodicWorkRequest locationWork = new PeriodicWorkRequest.Builder(LocationWork.class, 15,TimeUnit.MINUTES) .addTag(LocationWork.TAG) .build(); WorkManager.getInstance().enqueue(locationWork); Location Every 15 min
  70. 70. PeriodicWorkRequest locationWork = new PeriodicWorkRequest.Builder(LocationWork.class, 15,TimeUnit.MINUTES) .addTag(LocationWork.TAG) .build(); WorkManager.getInstance().enqueue(locationWork); Location Every 15 min
  71. 71. WorkManager.getInstance(this) .beginWith(Work.from(LocationWork.class)) .then(Work.from(LocationUploadWork.class)) .enqueue(); Upload Location
  72. 72. WorkManager.getInstance(this) .enqueue(Work.from(LocationWork.class,LocationUploadWork.class)); Upload Location
  73. 73. Note: You can’t enqueue chain of periodic and one-time work together.
  74. 74. PeriodicWorkRequest locationWork = new PeriodicWorkRequest.Builder(LocationWork.class, 15,TimeUnit.MINUTES) .addTag(LocationWork.TAG) .build(); workManager.enqueue(locationWork); workManager.cancelWorkById(locationWork.getId()); Cancel
  75. 75. PeriodicWorkRequest locationWork = new PeriodicWorkRequest.Builder(LocationWork.class, 15,TimeUnit.MINUTES) .addTag(LocationWork.TAG) .build(); workManager.enqueue(locationWork); workManager.cancelWorkById(locationWork.getId()); Cancel
  76. 76. PeriodicWorkRequest locationWork = new PeriodicWorkRequest.Builder(LocationWork.class, 15,TimeUnit.MINUTES) .addTag(LocationWork.TAG) .build(); Location Every 15 min
  77. 77. Location Every 15 min PeriodicWorkRequest locationWork = new PeriodicWorkRequest.Builder(LocationWork.class, 15,TimeUnit.MINUTES) .addTag(LocationWork.TAG) .build();
  78. 78. workManager.cancelAllWorkByTag(LocationWork.TAG);
  79. 79. Real Life
  80. 80. public class MyActivity extends AppCompatActivity { protected void onCreate(...) { Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType .CONNECTED).build(); OneTimeWorkRequest profileFetchWork = new OneTimeWorkRequest.Builder(ProfileFetchWorker.class) .setConstraints(constraints).build(); WorkManager workManager = WorkManager.getInstance(); workManager.enqueue(profileFetchWork); … } }
  81. 81. public class MyActivity extends AppCompatActivity { protected void onCreate(...) { Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType .CONNECTED).build(); OneTimeWorkRequest profileFetchWork = new OneTimeWorkRequest.Builder(ProfileFetchWorker.class) .setConstraints(constraints).build(); WorkManager workManager = WorkManager.getInstance(); workManager.enqueue(profileFetchWork); … } }
  82. 82. public class MyActivity extends AppCompatActivity { protected void onCreate(...) { Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType .CONNECTED).build(); OneTimeWorkRequest profileFetchWork = new OneTimeWorkRequest.Builder(ProfileFetchWorker.class) .setConstraints(constraints).build(); WorkManager workManager = WorkManager.getInstance(); workManager.enqueue(profileFetchWork); … } }
  83. 83. public class MyActivity extends AppCompatActivity { protected void onCreate(...) { Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType .CONNECTED).build(); OneTimeWorkRequest profileFetchWork = new OneTimeWorkRequest.Builder(ProfileFetchWorker.class) .setConstraints(constraints).build(); WorkManager workManager = WorkManager.getInstance(); workManager.enqueue(profileFetchWork); … } }
  84. 84. public class MyActivity extends AppCompatActivity { protected void onCreate(...) { Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType .CONNECTED).build(); OneTimeWorkRequest profileFetchWork = new OneTimeWorkReques .Builder(ProfileFetchWorker.class) .setConstraints(constraints).build(); WorkManager workManager = WorkManager.getInstance(); workManager.enqueue(profileFetchWork); … } }
  85. 85. public class ProfileFetchWorker extends Worker { public WorkerResult doWork() { List<Profile> profiles = loadProfiles(getAppContext()); Data profileData = ResUtil.profileToMap(profiles); setOutputData(profileData); return WorkerResult.SUCCESS; } }
  86. 86. public class ProfileFetchWorker extends Worker { public WorkerResult doWork() { List<Profile> profiles = loadProfiles(getAppContext()); Data profileData = ResUtil.profileToMap(profiles); setOutputData(profileData); return WorkerResult.SUCCESS; } }
  87. 87. public class ProfileFetchWorker extends Worker { public WorkerResult doWork() { List<Profile> profiles = loadProfiles(getAppContext()); Data profileData = ResUtil.profileToMap(profiles); setOutputData(profileData); return WorkerResult.SUCCESS; } }
  88. 88. public class ProfileFetchWorker extends Worker { public WorkerResult doWork() { List<Profile> profiles = loadProfiles(getAppContext()); Data profileData = ResUtil.profileToMap(profiles); setOutputData(profileData); return WorkerResult.SUCCESS; } }
  89. 89. What about status update?
  90. 90. public class MyActivity extends AppCompatActivity { public void startFetchProfiles() { ... UUID workId = profileFetchWork.getId(); workManager.getStatusById(workId).observe(this,workStatus-> { if (workStatus.getState().isFinished()) { updateUI(workStatus.getOutputData()); } }); } }
  91. 91. public class MyActivity extends AppCompatActivity { public void startFetchProfiles() { ... UUID workId = profileFetchWork.getId(); workManager.getStatusById(workId).observe(this,workStatus-> { if (workStatus.getState().isFinished()) { updateUI(workStatus.getOutputData()); } }); } }
  92. 92. public class MyActivity extends AppCompatActivity { public void startFetchProfiles() { ... UUID workId = profileFetchWork.getId(); workManager.getStatusById(workId).observe(this,workStatus-> { if (workStatus.getState().isFinished()) { updateUI(workStatus.getOutputData()); } }); } }
  93. 93. public class MyActivity extends AppCompatActivity { public void startFetchProfiles() { ... UUID workId = profileFetchWork.getId(); workManager.getStatusById(workId).observe(this,workStatus-> { if (workStatus.getState().isFinished()) { updateUI(workStatus.getOutputData()); } }); } }
  94. 94. MV{x} pattern
  95. 95. View ViewModel Very fast API List<Profile> UpdateUI ProfileFetchWorker onChange
  96. 96. Get Profiles ProfileDao View ViewModel Profile Repository ProfileNetworkDataSource SQLite Very fast API Room LiveData<List<Profile> LiveData<Profile[]> Observe Observe LifecycleOwner Load Profiles ProfileFetchWorker
  97. 97. Get Profiles ProfileDao View ViewModel Profile Repository ProfileNetworkDataSource SQLite Very fast API Room LiveData<List<Profile> MutableLiveData<Profile[]> Observe Observe LifecycleOwner Load Profiles ProfileFetchWorker LiveData<WorkStatus>
  98. 98. insert ProfileDao View ViewModel Profile Repository ProfileNetworkDataSource SQLite Very fast API Room LiveData<List<Profile> MutableLiveData<Profile[]> onChange LifecycleOwner ProfileFetchWorker LiveData<WorkStatus> onChange onChange onChange success
  99. 99. Get Location
  100. 100. Main Activity Service HandlerThread Looper Location Handler Network Handler Location Tracker BOOT_COMPLETE D Receiver AlarmManager Every 15 min
  101. 101. View WorkManager UploadWork Location Tracker Every 15 min LocationWork
  102. 102. public class LocationWork extends Worker { public WorkerResult doWork() { ... return WorkerResult.SUCCESS; } } Synchronous execution
  103. 103. mLocationManager. requestSingleUpdate(LocationManager.GPS_PROVIDER, mGpsLocationListener, looper); Asynchronous execution
  104. 104. public class GpsLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { ... } }
  105. 105. public class LocationWork extends Worker { public WorkerResult doWork() { ... return WorkerResult.SUCCESS; } }
  106. 106. public class LocationWork extends Worker { public WorkerResult doWork() { handlerThread = new HandlerThread("MyHandlerThread"); handlerThread.start(); looper = handlerThread.getLooper(); locationTracker = new LocationTracker(getAppContext(), looper); locationTracker.start(); ... return WorkerResult.SUCCESS; } }
  107. 107. public class LocationWork extends Worker { public WorkerResult doWork() { handlerThread = new HandlerThread("MyHandlerThread"); handlerThread.start(); looper = handlerThread.getLooper(); locationTracker = new LocationTracker(getAppContext(), looper); locationTracker.start(); ... return WorkerResult.SUCCESS; } }
  108. 108. public class LocationWork extends Worker { public WorkerResult doWork() { handlerThread = new HandlerThread("MyHandlerThread"); handlerThread.start(); looper = handlerThread.getLooper(); locationTracker = new LocationTracker(getAppContext(), looper); locationTracker.start(); ... return WorkerResult.SUCCESS; } }
  109. 109. public class LocationWork extends Worker { public WorkerResult doWork() { handlerThread = new HandlerThread("MyHandlerThread"); handlerThread.start(); looper = handlerThread.getLooper(); locationTracker = new LocationTracker(getAppContext(), looper); locationTracker.start(); ... return WorkerResult.SUCCESS; } }
  110. 110. public class LocationWork extends Worker { public WorkerResult doWork() { ... try { locationWait = new CountDownLatch(1); locationWait.await(); } catch (InterruptedException e) { e.printStackTrace(); } cleanUp(); return WorkerResult.SUCCESS; }
  111. 111. public class LocationWork extends Worker { public WorkerResult doWork() { ... try { locationWait = new CountDownLatch(1); locationWait.await(); } catch (InterruptedException e) { e.printStackTrace(); } cleanUp(); return WorkerResult.SUCCESS; }
  112. 112. public class LocationWork extends Worker { public WorkerResult doWork() { ... try { locationWait = new CountDownLatch(1); locationWait.await(); } catch (InterruptedException e) { e.printStackTrace(); } cleanUp(); return WorkerResult.SUCCESS; }
  113. 113. PeriodicWorkRequest locationWork = new PeriodicWorkRequest .Builder(LocationWork.class, 15, TimeUnit.MINUTES) .addTag(LocationWork.TAG) .build(); WorkManager.getInstance().enqueue(locationWork);
  114. 114. What’s next?
  115. 115. UploadWorker
  116. 116. Naive: Foreground service Periodic 15 min 44 locations 6% battery Smart: JobDispatcher Periodic 15 min 37 locations 1% battery Worker: Mix Periodic 15 min 38 locations 2% battery
  117. 117. What about getting update from system? e.g. Alarm, GeoFence?
  118. 118. geofencingClient. addGeofences(getGeofencingRequest(), getGeofencePendingIntent());
  119. 119. private PendingIntent getGeofencePendingIntent() { Intent intent = new Intent(this, GeoFenceReceiver.class); return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); }
  120. 120. private PendingIntent getGeofencePendingIntent() { Intent intent = new Intent(this, GeoFenceReceiver.class); return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } WHY?
  121. 121. sendBroadcast(new Intent("this.is.an.implicit.broadcast")); 04-11 14:12:36.340 753-763/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.PACKAGE_REMOVED dat=package:com.commonsware.cwac.cam2.demo flg=0x4000010 (has extras) } to com.commonsware.android.sysevents.pkg/.OnPackageChangeReceiver
  122. 122. <receiver android:name=".GeoFenceReceiver" android:enabled="true" android:exported="true"/>
  123. 123. public class GeoFenceReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { ... } }
  124. 124. public class GeoFenceReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { GeofencingEvent event = GeofencingEvent.fromIntent(intent); Location location = event.getTriggeringLocation(); Data inputData = ResourceUtil.locToData(location); OneTimeWorkRequest workRequest = new OneTimeWorkRequest .Builder(LocationUploadWorker.class) .setInputData(inputData).build(); WorkManager.getInstance().enqueue(workRequest); } }
  125. 125. public class GeoFenceReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { GeofencingEvent event = GeofencingEvent.fromIntent(intent); Location location = event.getTriggeringLocation(); Data inputData = ResourceUtil.locToData(location); OneTimeWorkRequest workRequest = new OneTimeWorkRequest .Builder(LocationUploadWorker.class) .setInputData(inputData).build(); WorkManager.getInstance().enqueue(workRequest); } }
  126. 126. public class GeoFenceReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { GeofencingEvent event = GeofencingEvent.fromIntent(intent); Location location = event.getTriggeringLocation(); Data inputData = ResourceUtil.locToData(location); OneTimeWorkRequest workRequest = new OneTimeWorkRequest .Builder(LocationUploadWorker.class) .setInputData(inputData).build(); WorkManager.getInstance().enqueue(workRequest); } }
  127. 127. public class GeoFenceReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { GeofencingEvent event = GeofencingEvent.fromIntent(intent); Location location = event.getTriggeringLocation(); Data inputData = ResourceUtil.locToData(location); OneTimeWorkRequest workRequest = new OneTimeWorkRequest .Builder(LocationUploadWorker.class) .setInputData(inputData).build(); WorkManager.getInstance().enqueue(workRequest); } }
  128. 128. Alpha Version
  129. 129. “...But android-job will soon reach it’s end of life…”
  130. 130. parahall

×