Android 8.0 的开机广播 和 IntentService

原文地址:ACTION_BOOT_COMPLETED, IntentService, and Android 8.0

在 Android 8.0 的后台处理上碰到了一个意料之外的 bug。
Stack Overflow 上有人提了一个关于ACTION_BOOT_COMPLETED的问题。虽然这个隐式广播在 8.0 仍然可用,却受到了很多限制。当收到这个广播时 Android 会判定你的 App 是后台应用,因此你不能像以前一样启动IntentService

所以,这里有几个替代方案可以选择:

方案1:startForegroundService()

在 Android 8.0 以上接收ACTION_BOOT_COMPLETEDBroadcastReceiver 里,通过startForegroundService()替代以前使用的startService()

public class OnBootReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
    Intent i=new Intent(context, TestIntentService.class);

    if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) {
      context.startForegroundService(i);
    } else {
      context.startService(i);
    }
  }
}

注意,如果整个运行过程小于几秒钟,就算你的 Service 内部没有调用startForeground(),这种方法也是行得通的(类似于 ANR 的处理时间)。你可以不用设置Notification和调用startForeground(),但会收到一个报错:

E/AndroidRuntime: FATAL EXCEPTION: main
 Process: com.commonsware.myapplication, PID: 5991
 android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1775)
     at android.os.Handler.dispatchMessage(Handler.java:105)
     at android.os.Looper.loop(Looper.java:164)
     at android.app.ActivityThread.main(ActivityThread.java:6541)
     at java.lang.reflect.Method.invoke(Native Method)
     at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

当然,如果你不介意在通知栏显示一个Notification,Android 希望你按常规调用startForeground(),这样是最符合规范的。

方案2:goAsync()

从 API 11 开始,BroadcastReceiver提供了一个goAsync()方法。这个方法可以让你摆脱主线程的束缚(跟异步任务有一点区别),所以你可以完全放弃IntentService转而使用BroadcastReceiver。你依然会受到 ANR 的时间限制,但并不会阻塞主线程。这种方案比前一种方案要好,因为虽然也受到了时间的限制,但至少不会有烦人的报错。不爽的地方就是,你需要一定程度上重构代码了。

public void onReceive(final Context context, final Intent intent) {  
      final PendingResult result = goAsync();  
      wl.acquire();  
      AsyncHandler.post(new Runnable() {  
         @Override  
         public void run() {  
            handleIntent(context, intent);//耗时操作  
            result.finish();  
          }  
      });  
}  

方案3:JobScheduler

如果你的代码耗时很长,并且你并不想让用户看到Notification,那你可以选择实现JobService搭配JobScheduler一起使用。这样做还有一个额外的好处,可以让你在某种特定情况下才触发操作(比如:当网络可用时)。不过,这种方式不但需要你重构代码,而且最低版本只支持 Android 5.0,所以如果你的minSdkVersion低于 21,你需要争对老设备做一些兼容。

方案4:JobIntentService

根据 Eugen Pechanec 的提醒,还有一个类叫JobIntentService,这是一个JobService/IntentService的混搭模式,并且它在 v4 兼容包下。

相关阅读:
1. BroadcastReceiver 使用 goAsync() 执行异步操作
2. 在 Android 5.0 中使用 JobScheduler
3. JobIntentService 官方文档

你可能感兴趣的:(Android 8.0 的开机广播 和 IntentService)