当前位置:   article > 正文

startForegroundService与startService 使用浅析

startforegroundservice

一. 了解服务(Service)的概念

service是安卓开发中一个很重要组件,意为“服务”。与我们常见的activity不同,“服务”是默默的在背后进行工作的,通常,它用于在后台为我们执行一些耗时,或者需要长时间执行的一些操作的。通常的,我们使用 Intent来启动一个服务(需要在manifest文件中注册,也可以像注册activity一样,给它分配进程)。Service可以不需要UI就在后台运行,不用管开启它的页面是否被销毁,只要进程还在就可以在后台运行。

 - Service生命周期:

  1. public class MyService extends Service {
  2. @Override
  3. public void onCreate() {
  4. super.onCreate();
  5. }
  6. @Override
  7. public int onStartCommand(Intent intent, int flags, int startId) {
  8. return startCommandReturnId;
  9. }
  10. @Override
  11. public void onDestroy() {
  12. super.onDestroy();
  13. }
  14. @Nullable
  15. @Override
  16. public IBinder onBind(Intent intent) {
  17. return null;
  18. }
  19. }

 二. 问题背景

产品需要在我们的业务启动之后,在状态栏展示一个“xx应用正在进行中”中的一个通知,同时,要求app退后台后也不能被结束,即需要保活。于是,我们将目光投向了service

  1. ....// 业务启动流程结束
  2. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (guildInfo != null)) {
  3. getApp().applicationContext.startForegroundService(serviceIntent)
  4. } else {
  5. getApp().applicationContext.startService(serviceIntent)
  6. }
  1. // Service类中
  2. public int onStartCommand(Intent intent, int flags, int startId) {
  3. ....
  4. mAudioNotification = createAudioNotification(); //创建通知栏展示内容
  5. startForeground(NID, mAudioNotification); // 展示服务通知
  6. ....
  7. }

 于是我们的业务中出现了这样的代码,乍看之下 好像也没什么问题。可是外发之后,收到的crash反馈却只增不减。

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord

 这类堆栈引起了我们的注意。原来我们使用了新提供的 startForegroundService,而这个API比较特殊,它要求我们在调用之后,收到 onStartCommand 回调后 5s内必须调用 startForeground, 否则会有ANR,而如果在调用 startForeground 之前,调用了 stopService 或者 stopSelf ,则会直接抛出 crash 我们这里问题的原因就是,在startForeground之前,调用了 stopService。问题找到了,是业务自己调用导致的。

但,似乎这个API没有提供给我们一个可以合适取消service的时机呢。既然这个API 限制这么多,我们又为什么要选择它呢?它和之前常用的startService又有什么区别呢?

三. 源代码分析

  1. @Override
  2. public ComponentName startService(Intent service) {
  3. warnIfCallingFromSystemProcess();
  4. return startServiceCommon(service, false, mUser);
  5. }
  6. @Override
  7. public ComponentName startForegroundService(Intent service) {
  8. warnIfCallingFromSystemProcess();
  9. return startServiceCommon(service, true, mUser);
  10. }
  11. private ComponentName startServiceCommon(Intent service, boolean requireForeground,
  12. UserHandle user)

 这两个API最终的都是调用的 startServiceCommon,区别只在于 其中的参数 requireForeground 字段赋值不同。那么这个字段究竟做了哪些处理呢?

Android8.0的行为变更说明中,我们看到,不在允许随意创建 后台服务,所以改为 调用 startForegroundService的形式。

 

  1.  不满足条件(如:O以上版本后台启动服务)调用startService会抛异常
  1. private ComponentName startServiceCommon(Intent service, boolean requireForeground,
  2. UserHandle user) {
  3. try {
  4. validateServiceIntent(service);
  5. service.prepareToLeaveProcess(this);
  6. ComponentName cn = ActivityManager.getService().startService(
  7. mMainThread.getApplicationThread(), service,
  8. service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
  9. getOpPackageName(), getAttributionTag(), user.getIdentifier());
  10. if (cn != null) {
  11. // 异常匹配
  12. if (cn.getPackageName().equals("!")) {
  13. throw new SecurityException(
  14. "Not allowed to start service " + service
  15. + " without permission " + cn.getClassName());
  16. } else if (cn.getPackageName().equals("!!")) {
  17. throw new SecurityException(
  18. "Unable to start service " + service
  19. + ": " + cn.getClassName());
  20. } else if (cn.getPackageName().equals("?")) {
  21. throw ServiceStartNotAllowedException.newInstance(requireForeground,
  22. "Not allowed to start service " + service + ": " + cn.getClassName());
  23. }
  24. }
  25. ....
  26. return cn;
  27. } catch (RemoteException e) {
  28. throw e.rethrowFromSystemServer();
  29. }
  30. }

  1. if (forcedStandby || (!r.startRequested && !fgRequired)) {
  2. // 调用startService,(!r.startRequested && !fgRequired) 条件为true
  3. final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
  4. r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
  5. if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
  6. if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
  7. return null;
  8. }
  9. if (forcedStandby) {
  10. if (fgRequired) {
  11. return null;
  12. }
  13. }
  14. UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(r.appInfo.uid);
  15. return new ComponentName("?", "app is in background uid " + uidRec);
  16. }
  17. }

  1. // Unified app-op and target sdk check
  2. @GuardedBy(anyOf = {"this", "mProcLock"})
  3. int appRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
  4. // 安卓8限制
  5. if (packageTargetSdk >= Build.VERSION_CODES.O) {
  6. if (DEBUG_BACKGROUND_CHECK) {
  7. Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
  8. }
  9. return ActivityManager.APP_START_MODE_DELAYED_RIGID;
  10. }
  11. .....
  12. }

2.  提高服务优先级

我们知道,在安卓系统内存紧张时,前台应用是有高优先级的,不会被清理掉。所以,我们需要将“不可见”的服务 升级为前台服务,前台服务是被认为用于已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程。

所以 我们在启动service后,收到onStartCommand时,调用 startForeground,同时传入需要展示在前台的notification

3.  为什么调用startForgroundService后,再调用stop或者没有及时调用startForeground会crash/ANR呢?

startServiceCommon

-> AMS.startService

-> ActiveServices.startServiceLocked

-> startServiceInnerLocked

-> bringUpServiceLocked

->realStartServiceLocked

-> sendServiceArgsLocked

  1. private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
  2. boolean oomAdjusted) throws TransactionTooLargeException {
  3. ...
  4. ArrayList<ServiceStartArgs> args = new ArrayList<>();
  5. while (r.pendingStarts.size() > 0) {
  6. ServiceRecord.StartItem si = r.pendingStarts.remove(0);
  7. ...
  8. if (r.fgRequired && !r.fgWaiting) {
  9. if (!r.isForeground) {
  10. <!--监听是否5S内startForeground-->
  11. scheduleServiceForegroundTransitionTimeoutLocked(r);
  12. } ...
  13. try {
  14. r.app.thread.scheduleServiceArgs(r, slice);
  15. }
  1. void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
  2. if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
  3. return;
  4. }
  5. Message msg = mAm.mHandler.obtainMessage(
  6. ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
  7. msg.obj = r;
  8. r.fgWaiting = true;
  9. mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);
  10. }
  11. /**
  12. * How long the Context.startForegroundService() grace period is to get around to
  13. * calling Service.startForeground() before we generate ANR.
  14. */
  15. volatile int mServiceStartForegroundTimeoutMs = DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS;

 修改 r.fgWaiting = true启动任务延迟TimeoutMs后发送 SERVICE_FOREGROUND_TIMEOUT_MSG

  1. // handler处理 SERVICE_FOREGROUND_TIMEOUT_MSG
  2. void serviceForegroundTimeout(ServiceRecord r) {
  3. ProcessRecord app;
  4. synchronized (mAm) {
  5. if (!r.fgRequired || !r.fgWaiting || r.destroying) {
  6. return;
  7. }
  8. app = r.app;
  9. if (app != null && app.isDebugging()) {
  10. // The app's being debugged; let it ride
  11. return;
  12. }
  13. if (DEBUG_BACKGROUND_CHECK) {
  14. Slog.i(TAG, "Service foreground-required timeout for " + r);
  15. }
  16. r.fgWaiting = false;
  17. stopServiceLocked(r, false);
  18. }
  19. if (app != null) {
  20. // 就是我们之前遇到的异常
  21. final String annotation = "Context.startForegroundService() did not then call "+ "Service.startForeground(): " + r;
  22. Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_ANR_MSG);
  23. SomeArgs args = SomeArgs.obtain();
  24. args.arg1 = app;
  25. args.arg2 = annotation;
  26. msg.obj = args;
  27. mAm.mHandler.sendMessageDelayed(msg,
  28. mAm.mConstants.mServiceStartForegroundAnrDelayMs);
  29. }
  30. }

 如果在没有调用startForegroun前调用了stop,则会抛出 SERVICE_FOREGROUND_CRASH_MSG 的msg

  1. private final void bringDownServiceLocked(ServiceRecord r) {
  2. ...
  3. if (r.fgRequired) {
  4. r.fgRequired = false;
  5. r.fgWaiting = false;
  6. mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
  7. AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
  8. mAm.mHandler.removeMessages(
  9. ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
  10. if (r.app != null) {
  11. Message msg = mAm.mHandler.obtainMessage(
  12. ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
  13. msg.obj = r.app;
  14. msg.getData().putCharSequence(
  15. ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
  16. mAm.mHandler.sendMessage(msg);
  17. }
  18. }

解决方案 -  startForegroundService后 必须按照要求调用 startForground

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/464530
推荐阅读
相关标签