美文网首页Android源码阅读
Android8.0的后台Service优化源码解析

Android8.0的后台Service优化源码解析

作者: 十蛋stan | 来源:发表于2019-01-15 16:56 被阅读23次

    今天在用户的错误列表上看到这么个bug

    java.lang.RuntimeException: Unable to start receiver com.anysoft.tyyd.appwidget.PlayAppWidgetProvider: 
    java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.anysoft.tyyd/.play.PlayerService }: 
    app is in background uid UidRecord{607ef50 u0a127 RCVR idle change:idle|uncached procs:1 seq(0,0,0)}
    

    这个bug是在适配Android8.0后出现的,解释下就是,app在后台uid的进程下面不允许启动Service.

    重现情景:

    由于我们的桌面小控件在onUpdate()方法里用Context.startService()启动了Service.当app的进程没有启动时,把桌面部件拉到Launcher桌面上就会报这个错误.

    先来看看Android官网在8.0时的后台服务启动优化的一些措施:

    后台服务限制:处于空闲状态时,应用可以使用的后台服务存在限制。 这些限制不适用于前台服务,因为前台服务更容易引起用户注意。
    在 Android 8.0 之前,创建前台服务的方式通常是先创建一个后台服务,然后将该服务推到前台。
    Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。
    在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground()方法以显示新服务的用户可见通知。
    如果应用在此时间限制内调用 startForeground(),则系统将停止服务并声明此应用为 ANR

    我总结一下就是8.0后,如果一个处于后台的应用想要启动Service就必须调用Context.startForegroundService()并且5秒内在该Service内调用startForeground()

    下面看看源码的变动情况

    源码解析:

    首先是后台应用调用Context.startService()启动Service为什么会报错

    启动Service的入口ContextImpl.startService()

    ContextImpl:
    
        @Override
        public ComponentName startService(Intent service) {
            warnIfCallingFromSystemProcess();
            return startServiceCommon(service, false, mUser);
        }
        //进入startServiceCommon()
        private ComponentName startServiceCommon(Intent service, boolean requireForeground,
                UserHandle user) {
            try {
                validateServiceIntent(service);
                service.prepareToLeaveProcess(this);
                ComponentName cn = ActivityManager.getService().startService(
                    mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                                getContentResolver()), requireForeground,
                                getOpPackageName(), user.getIdentifier());
                if (cn != null) {
                    if (cn.getPackageName().equals("!")) {
                        throw new SecurityException(
                                "Not allowed to start service " + service
                                + " without permission " + cn.getClassName());
                    } else if (cn.getPackageName().equals("!!")) {
                        throw new SecurityException(
                                "Unable to start service " + service
                                + ": " + cn.getClassName());
                    } else if (cn.getPackageName().equals("?")) {//1
                        throw new IllegalStateException(
                                "Not allowed to start service " + service + ": " + cn.getClassName());
                    }
                }
                return cn;
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    

    1处就是我们bug抛出异常的地方Not allowed to start service Intent...
    我们先看看ActivityManager.getService().startService()的返回逻辑

    ActivityManagerService:
    
        @Override
        public ComponentName startService(
            ...
            try {
                  res = mServices.startServiceLocked(caller, service resolvedType, callingPid, callingUid, requireForeground, callingPackage, userId);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return res;
        }
    

    启动Service会调用ActiveServices.startServiceLocked()

    ActiveServices:
    
      ComponentName startServiceLocked(...){
            ...
            // If this isn't a direct-to-foreground start, check our ability to kick off an
            // arbitrary service
            if (!r.startRequested && !fgRequired) {
                // Before going further -- if this app is not allowed to start services in the
                // background, then at this point we aren't going to let it period.
                final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                        r.appInfo.targetSdkVersion, callingPid, false, false);
                if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                    Slog.w(TAG, "Background start not allowed: service "
                            + service + " to " + r.name.flattenToShortString()
                            + " from pid=" + callingPid + " uid=" + callingUid
                            + " pkg=" + callingPackage);
                    if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
                        return null;
                    }
                    UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
                    //2.
                    return new ComponentName("?", "app is in background uid " + uidRec);
                }
            }
      }    
    

    这里的fgRequired是从ContextImpl.startServiceCommon(fgRequired:false)传进来的,为false.
    2标记处是不是又看到相关bug信息了 "app is in background uid...",于是我们看看allowed返回值mAm.getAppStartModeLocked()

    ActivityManagerService:
    
      int getAppStartModeLocked(){
          UidRecord uidRec = mActiveUids.get(uid);
          ...
          if (uidRec == null || alwaysRestrict || uidRec.idle) {
              final int startMode = (alwaysRestrict) ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk) : 
              appServicesRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
          }
          return startMode;
          ...
      }
      
    

    allowed的返回值就是startMode.这里alwaysRestrict是传入的参数false,这里的uidRec由于应用进程都未启动,于是uidRec.idle为true表示空闲进程,所以我们直接看appServicesRestrictedInBackgroundLocked()

    ActivityManagerService:
    
      int appServicesRestrictedInBackgroundLocked(){
          ...
          // Persistent app?
          if (mPackageManagerInt.isPackagePersistent(packageName)) {
                if (DEBUG_BACKGROUND_CHECK) {
                    Slog.i(TAG, "App " + uid + "/" + packageName
                            + " is persistent; not restricted in background");
                }
                return ActivityManager.APP_START_MODE_NORMAL;
          }
    
          // Non-persistent but background whitelisted?
          if (uidOnBackgroundWhitelist(uid)) {
                if (DEBUG_BACKGROUND_CHECK) {
                    Slog.i(TAG, "App " + uid + "/" + packageName
                            + " on background whitelist; not restricted in background");
                }
                return ActivityManager.APP_START_MODE_NORMAL;
          }
    
          // Is this app on the battery whitelist?
          if (isOnDeviceIdleWhitelistLocked(uid)) {
                if (DEBUG_BACKGROUND_CHECK) {
                    Slog.i(TAG, "App " + uid + "/" + packageName
                            + " on idle whitelist; not restricted in background");
                }
                return ActivityManager.APP_START_MODE_NORMAL;
          }
          return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
      }
    

    这个方法会判断是否是Persistent app,白名单,电量白名单应用,很显然普通app都不是,于是进入appRestrictedInBackgroundLocked()看看

    ActivityManagerService:
    
            // Apps that target O+ are always subject to background check
            if (packageTargetSdk >= Build.VERSION_CODES.O) {
                if (DEBUG_BACKGROUND_CHECK) {
                    Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
                }
                return ActivityManager.APP_START_MODE_DELAYED_RIGID;
            }
            // ...and legacy apps get an AppOp check
            int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
                    uid, packageName);
            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
            }
            switch (appop) {
                case AppOpsManager.MODE_ALLOWED:
                    return ActivityManager.APP_START_MODE_NORMAL;
                case AppOpsManager.MODE_IGNORED:
                    return ActivityManager.APP_START_MODE_DELAYED;
                default:
                    return ActivityManager.APP_START_MODE_DELAYED_RIGID;
            }
    

    这里的packageTargetSdk刚好是O,所以返回ActivityManager.APP_START_MODE_DELAYED_RIGID了.由于返回值不是ActivityManager.APP_START_MODE_NORMAL.于是就return new ComponentName("?", "app is in background uid " + uidRec);然后就出现了开头的异常.

    下面看下Context.startForegroundService启动Service的逻辑

    入口依旧为ContextImpl.startForegroundService()

        @Override
        public ComponentName startForegroundService(Intent service) {
            warnIfCallingFromSystemProcess();
            return startServiceCommon(service, true, mUser);
        }
    

    这里与startService的区别就在于传入的fgRequired为true.于是一路
    ContextImpl.startServiceCommon()-->ActivityManagerService.startService()-->ActiveServices.startServiceLocked(),由于fgRequired为true,就跳过刚才那段逻辑下面就是正常的Service启动流程了.
    那么还有一个问题,为什么还需要在5秒内调用Service.startForeground()呢?
    在启动Service的过程中会调用到ActiveServices.bringUpServiceLocked()方法,然后会调用ActiveServices.sendServiceArgsLocked()

    ActiveServices:
        ...
        while (r.pendingStarts.size() > 0) {
        ...
        if (r.fgRequired && !r.fgWaiting) {
            if (!r.isForeground) {
                //3
                scheduleServiceForegroundTransitionTimeoutLocked(r);
            } else {
                r.fgRequired = false;
            }
        }
        ...
        }
    

    在3处会调用scheduleServiceForegroundTransitionTimeoutLocked()作用就是发送一个延时5秒的message

    ActiveServices:
    
        void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
            if (r.app.executingServices.size() == 0 || r.app.thread == null) {
                return;
            }
            Message msg = mAm.mHandler.obtainMessage(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
            msg.obj = r;
            r.fgWaiting = true;
            mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);//这个值是5*1000
        }
    

    看下这个消息的处理

    ActivityManagerService:
      
      class MainHandler extends Handler{
          @Override
          public void handleMessage(Message msg) {
                switch (msg.what) {
                    ...
                    case SERVICE_FOREGROUND_TIMEOUT_MSG: {
                    mServices.serviceForegroundTimeout((ServiceRecord)msg.obj);
                }
          }
      }
    

    又来到ActiveServices

    ActiveServices:
      
        void serviceForegroundTimeout(ServiceRecord r) {
            ProcessRecord app;
            synchronized (mAm) {
                if (!r.fgRequired || r.destroying) {
                    return;
                }
                app = r.app;
                r.fgWaiting = false;
                stopServiceLocked(r);
            }
        }
    

    这里就是调用stopServiceLocked(r)把service关掉了.那么Service.startForeground()一定会有代码取消这个消息,来看:

    Service:
      
      public final void startForeground(int id, Notification notification) {
            try {
                mActivityManager.setServiceForeground(
                        new ComponentName(this, mClassName), mToken, id,
                        notification, 0);
            } catch (RemoteException ex) {
            }
      }
    

    mActivityManager就是调用AMS

    ActivityManagerService:
    
      @Override
      public void setServiceForeground(ComponentName className, IBinder token,
                int id, Notification notification, int flags) {
            synchronized(this) {
                mServices.setServiceForegroundLocked(className, token, id, notification, flags);
            }
      }
    
    ActiveServices:
      public void setServiceForegroundLocked(ComponentName className, IBinder token,
                int id, Notification notification, int flags) {
            final int userId = UserHandle.getCallingUserId();
            final long origId = Binder.clearCallingIdentity();
            try {
                ServiceRecord r = findServiceLocked(className, token, userId);
                if (r != null) {
                    setServiceForegroundInnerLocked(r, id, notification, flags);
                }
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
      }
    

    来看setServiceForegroundInnerLocked()

    ActiveServices:
      private void setServiceForegroundInnerLocked(){
        ...
        if (r.fgRequired) {
            if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "Service called startForeground() as required: " + r);}
                    r.fgRequired = false;
                    r.fgWaiting = false;
                    mAm.mHandler.removeMessages(
                            ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        }
        ...
      }
    

    这里就removeMessages(SERVICE_FOREGROUND_TIMEOUT_MSG)取消这个message了.

    相关文章

      网友评论

        本文标题:Android8.0的后台Service优化源码解析

        本文链接:https://www.haomeiwen.com/subject/utlydqtx.html