Android P版本startService Crash问题,深入了解StartService流程

上个礼拜刘神在微信上跟我探讨人生的时候突然提到,小高你的博客很久没更新了。我心里一惊,是啊已经一年没更新博客了。但是写点什么呢?刘神说跟你讲下我最近遇到一个很有意思的bug,也许你就有想法了

 

BUG现象

当你在activity里面中delay(这里可以用handler或者各种延时方法)启动service时,按home键回到桌面,如果delay的时间为70s,那么程序会报错,但是如果delay的时间为50s,却可以正常运行

 

相信聪明的同学肯定已经心中有数了,google果然对安全性越来越看重了,很明显google这是在限制后台app 只能在一定的时间内startService,那么这个问题我们该怎么去解决呢,我们可以去看下google官方有没有相应解释,以及将startService相应流程看一遍,相信问题自然迎刃而解

 

Google官方解释

https://developer.android.google.cn/about/versions/oreo/background.html#services

 

StartService流程

启动Service的第一步就是Activity.startService()那我们去看看Activity发现里面竟然没有startService()这个方法!why?去父类ContextThemeWrapper找找,还是没有?再去爷爷类ContextWrapper找找,果然在爷爷类这里。这里涉及到java的继承写法,所谓继承,是一个很简单很直观的概念,与显示生活中的继承(例如儿子继承了父亲财产)类似。继承可以理解为一个类从另一个类中获取方法和属性的过程。如果类B继承于类A,那么类B就拥有类A的属性和方法。这也就是为什么Activity中并没有写startService()却可以正常调用

frameworks\base\core\java\android\content\ContextWrapper.java

Context mBase;
@Override
public ComponentName startService(Intent service) {
    return mBase.startService(service);
}

那么我们来看下Activity继承的这个方法,可以看到方法里面还是没有具体的内容,而是继续往下调用了mBase.startService(service),mBase.startService(service) 中的mBase其实是一个Context,熟悉源码的同学应该知道,Context是一个抽象类,所以这里实际上传进来的Context的实现类ContextImpl,也就是说调用的是ContextImpl里面的startService(service)方法,有些同学可能会不理解,为什么明明声明是Context,却说调用的是ContextImpl里面的的方法呢?

其实这几个类设计是典型的装饰模式(Decorator),ContextWrapper的startService函数调用ContextImpl的startService额外职责后,可以继续做自己的事情。而这里之所以不声明为ContextImpl的原因则是为了让代码更灵活一些,比如说以后要继续加个ContextImpl2、ContextImpl3会更方便一些

frameworks\base\core\java\android\app\ContextImpl.java

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

可以看到ContextImpl.startService()继续往下调用了 startServiceCommon(service, false, mUser);

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("?")) {
                    throw new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
}

 

throw new IllegalStateException("Not allowed to start service " + service + ": " + cn.getClassName());

这个startServiceCommon方法我们可以注意一下这行,再对比下我们得到的异常log,可以发现跟异常log是一样的。那么我们可以推断异常就是这里抛出来的,那么为什么cn.getPackageName().equals("?") 这个条件会成立呢 我们继续往下看 

frameworks\base\core\java\android\app\ActivityManager.java

   public static IActivityManager getService() {

        return IActivityManagerSingleton.get();

    }



    private static final Singleton<IActivityManager> IActivityManagerSingleton =

            new Singleton<IActivityManager>() {

                @Override

                protected IActivityManager create() {

                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);

                    final IActivityManager am = IActivityManager.Stub.asInterface(b);

                    return am;

                }

            };

这里用到了一个叫Singleton的类,也就是我们常说的单例,主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 使用Singleton的好处还在于可以节省内存

final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);返回的是ams

final IActivityManager am = IActivityManager.Stub.asInterface(b); 返回一个IActivityManager供我们做一些工作 这里用到Android的特色通信方式aidl 使用Binder驱动走到ams里面,所以其实IActivityManager 真正实现是在ActivityManagerService里面,AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言,这是我们在开发Android时经常要用到的

 

frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

    @Override

    public ComponentName startService(IApplicationThread caller, Intent service,

            String resolvedType, boolean requireForeground, String callingPackage, int userId)

            throws TransactionTooLargeException {

        enforceNotIsolatedCaller("startService");

        // Refuse possible leaked file descriptors

        if (service != null && service.hasFileDescriptors() == true) {

            throw new IllegalArgumentException("File descriptors passed in Intent");

        }



        if (callingPackage == null) {

            throw new IllegalArgumentException("callingPackage cannot be null");

        }



        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,

                "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);

        synchronized(this) {

            final int callingPid = Binder.getCallingPid();

            final int callingUid = Binder.getCallingUid();

            final long origId = Binder.clearCallingIdentity();

            ComponentName res;

            try {

                res = mServices.startServiceLocked(caller, service,

                        resolvedType, callingPid, callingUid,

                        requireForeground, callingPackage, userId);

            } finally {

                Binder.restoreCallingIdentity(origId);

            }

            return res;

        }

}

然后下一步到了ActivityManagerService.startService(),继续往下调用

mServices.startServiceLocked(caller, service,resolvedType, callingPid, callingUid,requireForeground, callingPackage, userId);

这里的mService的类型其实是ActiveServices

 

frameworks\base\services\core\java\com\android\server\am\ActiveServices.java

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
            throws TransactionTooLargeException {
        if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
                + " type=" + resolvedType + " args=" + service.getExtras());

        final boolean callerFg;
        if (caller != null) {
            final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
            if (callerApp == null) {
                throw new SecurityException(
                        "Unable to find app for caller " + caller
                        + " (pid=" + callingPid
                        + ") when starting service " + service);
            }
            callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
        } else {
            callerFg = true;
        }
		android.util.Log.e("gaoshifeng","callerFg == " + callerFg);

        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false, false);
        if (res == null) {
            return null;
        }
        if (res.record == null) {
            return new ComponentName("!", res.permission != null
                    ? res.permission : "private to package");
        }

        ServiceRecord r = res.record;

        if (!mAm.mUserController.exists(r.userId)) {
            Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
            return null;
        }

        // If we're starting indirectly (e.g. from PendingIntent), figure out whether
        // we're launching into an app in a background state.  This keys off of the same
        // idleness state tracking as e.g. O+ background service start policy.
        final boolean bgLaunch = !mAm.isUidActiveLocked(r.appInfo.uid);

        // If the app has strict background restrictions, we treat any bg service
        // start analogously to the legacy-app forced-restrictions case, regardless
        // of its target SDK version.
        boolean forcedStandby = false;
        if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
            if (DEBUG_FOREGROUND_SERVICE) {
                Slog.d(TAG, "Forcing bg-only service start only for " + r.shortName
                        + " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
            }
            forcedStandby = true;
        }

        // If this is a direct-to-foreground start, make sure it is allowed as per the app op.
        boolean forceSilentAbort = false;
        if (fgRequired) {
            final int mode = mAm.mAppOpsService.checkOperation(
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
            switch (mode) {
                case AppOpsManager.MODE_ALLOWED:
                case AppOpsManager.MODE_DEFAULT:
                    // All okay.
                    break;
                case AppOpsManager.MODE_IGNORED:
                    // Not allowed, fall back to normal start service, failing siliently
                    // if background check restricts that.
                    Slog.w(TAG, "startForegroundService not allowed due to app op: service "
                            + service + " to " + r.name.flattenToShortString()
                            + " from pid=" + callingPid + " uid=" + callingUid
                            + " pkg=" + callingPackage);
                    fgRequired = false;
                    forceSilentAbort = true;
                    break;
                default:
                    return new ComponentName("!!", "foreground not allowed as per app op");
            }
        }

        // If this isn't a direct-to-foreground start, check our ability to kick off an
        // arbitrary service
		
		android.util.Log.e("gaoshifeng","forcedStandby = " + forcedStandby);
		android.util.Log.e("gaoshifeng","!r.startRequested = " + !r.startRequested);//问题在这
		android.util.Log.e("gaoshifeng","!fgRequired = " + !fgRequired);
        if (forcedStandby || (!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, forcedStandby);
            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 + " startFg?=" + fgRequired);
                if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
                    // In this case we are silently disabling the app, to disrupt as
                    // little as possible existing apps.
                    return null;
                }
                if (forcedStandby) {
                    // This is an O+ app, but we might be here because the user has placed
                    // it under strict background restrictions.  Don't punish the app if it's
                    // trying to do the right thing but we're denying it for that reason.
                    if (fgRequired) {
                        if (DEBUG_BACKGROUND_CHECK) {
                            Slog.v(TAG, "Silently dropping foreground service launch due to FAS");
                        }
                        return null;
                    }
                }
                // This app knows it is in the new model where this operation is not
                // allowed, so tell it what has happened.
                UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
                return new ComponentName("?", "app is in background uid " + uidRec);
            }
        }

        // At this point we've applied allowed-to-start policy based on whether this was
        // an ordinary startService() or a startForegroundService().  Now, only require that
        // the app follow through on the startForegroundService() -> startForeground()
        // contract if it actually targets O+.
        if (r.appInfo.targetSdkVersion < Build.VERSION_CODES.O && fgRequired) {
            if (DEBUG_BACKGROUND_CHECK || DEBUG_FOREGROUND_SERVICE) {
                Slog.i(TAG, "startForegroundService() but host targets "
                        + r.appInfo.targetSdkVersion + " - not requiring startForeground()");
            }
            fgRequired = false;
        }

        NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
                callingUid, r.packageName, service, service.getFlags(), null, r.userId);

        // If permissions need a review before any of the app components can run,
        // we do not start the service and launch a review activity if the calling app
        // is in the foreground passing it a pending intent to start the service when
        // review is completed.
        if (mAm.mPermissionReviewRequired) {
            // XXX This is not dealing with fgRequired!
            if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage,
                    callingUid, service, callerFg, userId)) {
                return null;
            }
        }

        if (unscheduleServiceRestartLocked(r, callingUid, false)) {
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r);
        }
        r.lastActivity = SystemClock.uptimeMillis();
        r.startRequested = true;
        r.delayedStop = false;
        r.fgRequired = fgRequired;
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                service, neededGrants, callingUid));

        if (fgRequired) {
            // We are now effectively running a foreground service.
            mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, true);
        }

        final ServiceMap smap = getServiceMapLocked(r.userId);
        boolean addToStarting = false;
        if (!callerFg && !fgRequired && r.app == null
                && mAm.mUserController.hasStartedUserState(r.userId)) {
            ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
            if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
                // If this is not coming from a foreground caller, then we may want
                // to delay the start if there are already other background services
                // that are starting.  This is to avoid process start spam when lots
                // of applications are all handling things like connectivity broadcasts.
                // We only do this for cached processes, because otherwise an application
                // can have assumptions about calling startService() for a service to run
                // in its own process, and for that process to not be killed before the
                // service is started.  This is especially the case for receivers, which
                // may start a service in onReceive() to do some additional work and have
                // initialized some global state as part of that.
                if (DEBUG_DELAYED_SERVICE) Slog.v(TAG_SERVICE, "Potential start delay of "
                        + r + " in " + proc);
                if (r.delayed) {
                    // This service is already scheduled for a delayed start; just leave
                    // it still waiting.
                    if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Continuing to delay: " + r);
                    return r.name;
                }
                if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
                    // Something else is starting, delay!
                    Slog.i(TAG_SERVICE, "Delaying start of: " + r);
                    smap.mDelayedStartList.add(r);
                    r.delayed = true;
                    return r.name;
                }
                if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Not delaying: " + r);
                addToStarting = true;
            } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
                // We slightly loosen when we will enqueue this new service as a background
                // starting service we are waiting for, to also include processes that are
                // currently running other services or receivers.
                addToStarting = true;
                if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                        "Not delaying, but counting as bg: " + r);
            } else if (DEBUG_DELAYED_STARTS) {
                StringBuilder sb = new StringBuilder(128);
                sb.append("Not potential delay (state=").append(proc.curProcState)
                        .append(' ').append(proc.adjType);
                String reason = proc.makeAdjReason();
                if (reason != null) {
                    sb.append(' ');
                    sb.append(reason);
                }
                sb.append("): ");
                sb.append(r.toString());
                Slog.v(TAG_SERVICE, sb.toString());
            }
        } else if (DEBUG_DELAYED_STARTS) {
            if (callerFg || fgRequired) {
                Slog.v(TAG_SERVICE, "Not potential delay (callerFg=" + callerFg + " uid="
                        + callingUid + " pid=" + callingPid + " fgRequired=" + fgRequired + "): " + r);
            } else if (r.app != null) {
                Slog.v(TAG_SERVICE, "Not potential delay (cur app=" + r.app + "): " + r);
            } else {
                Slog.v(TAG_SERVICE,
                        "Not potential delay (user " + r.userId + " not started): " + r);
            }
        }

        ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
        return cmp;
    }

可以看到这一行,这就可以解释为什么前面cn.getPackageName().equals("?") 这个条件会成立了,也就是我们抛异常的真正原因

return new ComponentName("?", "app is in background uid " + uidRec);

最后调用startServiceInnerLocked()继续往下走

startServiceInnerLocked()-->bringUpServiceLocked() -->realStartServiceLocked() 

 

app.thread.scheduleCreateService(r, r.serviceInfo,
    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
    app.repProcState);

  realStartServiceLocked() 里面调用app.thread.scheduleCreateService()进入启动流程,app其实是ProcessRecord类型我们找到ProcessRecord.java可以看到thread就是IApplicationThread,而 IApplicationThread就是ActivityThread的内部类,那就好办了,我们找到ActivityThread.java 找到scheduleCreateService方法

\frameworks\base\core\java\android\app\ActivityThread.java

public final void scheduleCreateService(IBinder token,
                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;
            s.compatInfo = compatInfo;

            sendMessage(H.CREATE_SERVICE, s);
        }

 

case CREATE_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                    handleCreateService((CreateServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
private void handleCreateService(CreateServiceData data) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();

        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to instantiate service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }

        try {
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
            service.onCreate();//到这一个service 就被create出来了
            mServices.put(data.token, service);
            try {
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to create service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }
    }

最终会调用到handleCreateService(),看到我们熟悉的service.onCreate();也就意味着整个startService的流程我们就走完了

 

BUG分析

 

那我们回过头来看看我们遇到的bug,刚刚我们已经知道问题产生的位置就是这里,为了方便阅读我就直接把变量名字改成类名好了。我们来仔细看看这一部分代码

ActiveServices.startServiceLocked()中的

android.util.Log.e("gaoshifeng","forcedStandby = " + forcedStandby);
		android.util.Log.e("gaoshifeng","!r.startRequested = " + !r.startRequested);//问题在这
		android.util.Log.e("gaoshifeng","!fgRequired = " + !fgRequired);
       if (forcedStandby || (!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, forcedStandby);
            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 + " startFg?=" + fgRequired);
                if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
                    // In this case we are silently disabling the app, to disrupt as
                    // little as possible existing apps.
                    return null;
                }
                if (forcedStandby) {
                    // This is an O+ app, but we might be here because the user has placed
                    // it under strict background restrictions.  Don't punish the app if it's
                    // trying to do the right thing but we're denying it for that reason.
                    if (fgRequired) {
                        if (DEBUG_BACKGROUND_CHECK) {
                            Slog.v(TAG, "Silently dropping foreground service launch due to FAS");
                        }
                        return null;
                    }
                }
                // This app knows it is in the new model where this operation is not
                // allowed, so tell it what has happened.
                UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
                return new ComponentName("?", "app is in background uid " + uidRec);
            }
        }

大家都知道解决bug,最好是需要配合一些log来看效率会更高,这边跟大家分享一个小技巧,在调试Service这块的时候,其实很多系统log他是默认不打印的,我们可以把它全打印出来这样有助于我们debug,Service这部分的开关就是在ActivityManagerDebugConfig.java里面有个DEBUG_ALL ,我们把它设置为true这样就可以打印所有log了frameworks\base\services\core\java\com\android\server\am\ActivityManagerDebugConfig.java

// Enable all debug log categories.

public static boolean DEBUG_ALL = true;

 

回到代码我们可以看到当allowed != ActivityManager.APP_START_MODE_NORMAL时

return new ComponentName("?", "app is in background uid " + uidRec);

也就是name里面就会包含“?”,那么我们来看看allowed这个变量是如何获取的

final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,

                    r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
            int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
        UidRecord uidRec = mActiveUids.get(uid);
        if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
                + packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
                + (uidRec != null ? uidRec.idle : false));
        if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) {
            boolean ephemeral;
            if (uidRec == null) {
                ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(
                        UserHandle.getUserId(uid), packageName);
            } else {
                ephemeral = uidRec.ephemeral;
            }

            if (ephemeral) {
                // We are hard-core about ephemeral apps not running in the background.
                return ActivityManager.APP_START_MODE_DISABLED;
            } else {
                if (disabledOnly) {
                    // The caller is only interested in whether app starts are completely
                    // disabled for the given package (that is, it is an instant app).  So
                    // we don't need to go further, which is all just seeing if we should
                    // apply a "delayed" mode for a regular app.
                    return ActivityManager.APP_START_MODE_NORMAL;
                }
                final int startMode = (alwaysRestrict)
                        ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
                        : appServicesRestrictedInBackgroundLocked(uid, packageName,
                                packageTargetSdk);
                if (DEBUG_BACKGROUND_CHECK) {
                    Slog.d(TAG, "checkAllowBackground: uid=" + uid
                            + " pkg=" + packageName + " startMode=" + startMode
                            + " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid, false)
                            + " onwhitelist(ei)=" + isOnDeviceIdleWhitelistLocked(uid, true));
                }
                if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
                    // This is an old app that has been forced into a "compatible as possible"
                    // mode of background check.  To increase compatibility, we will allow other
                    // foreground apps to cause its services to start.
                    if (callingPid >= 0) {
                        ProcessRecord proc;
                        synchronized (mPidsSelfLocked) {
                            proc = mPidsSelfLocked.get(callingPid);
                        }
                        if (proc != null &&
                                !ActivityManager.isProcStateBackground(proc.curProcState)) {
                            // Whoever is instigating this is in the foreground, so we will allow it
                            // to go through.
                            return ActivityManager.APP_START_MODE_NORMAL;
                        }
                    }
                }
                return startMode;
            }
        }
        return ActivityManager.APP_START_MODE_NORMAL;
}

可以看到这里就有一些系统log可以协助我们来分析

        if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="

                + packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="

                + (uidRec != null ? uidRec.idle : false));

        if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) {

 

 

 

抛出异常时的log

07-29 22:37:41.155  1128  1147 I ActivityManager: UID idle uid=10091
07-29 22:37:47.567  1128  6869 V ActivityManager: Broadcast sticky: Intent { act=android.intent.action.SIG_STR flg=0x10 (has extras) } ordered=false userid=-1
07-29 22:37:47.568  1128  6869 V ActivityManager: Enqueueing broadcast: android.intent.action.SIG_STR replacePending=false
07-29 22:37:48.488  1128  6869 V ActivityManager: *** startService: Intent { cmp=com.example.illa.testff/.MyService } type=null fg=false
07-29 22:37:48.489  1128  6869 V ActivityManager: startService: Intent { cmp=com.example.illa.testff/.MyService } type=null args=null
07-29 22:37:46.705  4340  4340 I chatty  : uid=0(root) kworker/3:1 identical 2 lines
07-29 22:37:47.705  4340  4340 W kworker/3:1: type=1400 audit(0.0:1042): avc: denied { search } for name="battery" dev="sysfs" ino=7493 scontext=u:r:kernel:s0 tcontext=u:object_r:sysfs_batteryinfo:s0 tclass=dir permissive=0
07-29 22:37:48.489  1128  6869 E gaoshifeng: callerFg == false
07-29 22:37:48.489  1128  6869 V ActivityManager: retrieveServiceLocked: Intent { cmp=com.example.illa.testff/.MyService } type=null callingUid=10091
07-29 22:37:48.490  1128  6869 V ActivityManager: Retrieve created new service: ServiceRecord{7d1fb58 u0 com.example.illa.testff/.MyService}
07-29 22:37:48.491  1128  6869 E gaoshifeng: forcedStandby = false
07-29 22:37:48.491  1128  6869 E gaoshifeng: !r.startRequested = true
07-29 22:37:48.491  1128  6869 E gaoshifeng: !fgRequired = true
07-29 22:37:48.491  1128  6869 D ActivityManager: checkAllowBackground: uid=10091 pkg=com.example.illa.testff rec=UidRecord{5495f47 u0a91 LAST bg:+1m7s354ms idle change:idle procs:1 seq(0,0,0)} always=false idle=true
07-29 22:37:48.492  1128  6869 I ActivityManager: App 10091/com.example.illa.testff targets O+, restricted
07-29 22:37:48.492  1128  6869 D ActivityManager: checkAllowBackground: uid=10091 pkg=com.example.illa.testff startMode=2 onwhitelist=false onwhitelist(ei)=false
07-29 22:37:48.492  1128  6869 W ActivityManager: Background start not allowed: service Intent { cmp=com.example.illa.testff/.MyService } to com.example.illa.testff/.MyService from pid=7287 uid=10091 pkg=com.example.illa.testff startFg?=false
07-29 22:37:48.497  7287  7287 D AndroidRuntime: Shutting down VM
--------- beginning of crash
07-29 22:37:48.511  7287  7287 E AndroidRuntime: FATAL EXCEPTION: main
07-29 22:37:48.511  7287  7287 E AndroidRuntime: Process: com.example.illa.testff, PID: 7287
07-29 22:37:48.511  7287  7287 E AndroidRuntime: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.example.illa.testff/.MyService }: app is in background uid UidRecord{5495f47 u0a91 LAST bg:+1m7s355ms idle change:idle procs:1 seq(0,0,0)}
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1578)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at android.app.ContextImpl.startService(ContextImpl.java:1533)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at android.content.ContextWrapper.startService(ContextWrapper.java:664)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at com.example.illa.testff.MainActivity$1.run(MainActivity.java:39)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:873)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:99)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:193)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6702)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
07-29 22:37:48.511  7287  7287 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
07-29 22:37:48.531  1128  6869 W ActivityManager:   Force finishing activity com.example.illa.testff/.MainActivity

 

正常运行log

07-30 02:00:19.821  1128  1930 V ActivityManager: *** startService: Intent { cmp=com.example.illa.testff/.MyService } type=null fg=false
07-30 02:00:19.821  1128  1930 V ActivityManager: startService: Intent { cmp=com.example.illa.testff/.MyService } type=null args=null
07-30 02:00:19.821  1128  1930 E gaoshifeng: callerFg == false
07-30 02:00:19.821  1128  1930 V ActivityManager: retrieveServiceLocked: Intent { cmp=com.example.illa.testff/.MyService } type=null callingUid=10091
07-30 02:00:19.822  1128  1930 V ActivityManager: Retrieve created new service: ServiceRecord{8666a9a u0 com.example.illa.testff/.MyService}
07-30 02:00:19.822  1128  1930 E gaoshifeng: forcedStandby = false
07-30 02:00:19.822  1128  1930 E gaoshifeng: !r.startRequested = true
07-30 02:00:19.822  1128  1930 E gaoshifeng: !fgRequired = true
07-30 02:00:19.822  1128  1930 D ActivityManager: checkAllowBackground: uid=10091 pkg=com.example.illa.testff rec=UidRecord{5999804 u0a91 LAST bg:+20s513ms change:cached procs:1 seq(0,0,0)} always=false idle=false
07-30 02:00:19.823  1128  1930 V ActivityManager: Checking URI perm to data=null clip=null from Intent { cmp=com.example.illa.testff/.MyService }; flags=0x0
07-30 02:00:19.823  1128  1930 V ActivityManager: Potential start delay of ServiceRecord{8666a9a u0 com.example.illa.testff/.MyService} in ProcessRecord{2b66907 13759:com.example.illa.testff/u0a91}
07-30 02:00:19.823  1128  1930 V ActivityManager: Not delaying: ServiceRecord{8666a9a u0 com.example.illa.testff/.MyService}
07-30 02:00:19.823  1128  1930 V ActivityManager: Bringing up ServiceRecord{8666a9a u0 com.example.illa.testff/.MyService} android.content.Intent$FilterComparison@ff445089 fg=false
07-30 02:00:19.823  1128  1930 V ActivityManager_MU: bringUpServiceLocked: appInfo.uid=10091 app=ProcessRecord{2b66907 13759:com.example.illa.testff/u0a91}
07-30 02:00:19.824  1128  1930 V ActivityManager_MU: realStartServiceLocked, ServiceRecord.uid = 10091, ProcessRecord.uid = 10091
07-30 02:00:19.824  1128  1930 V ActivityManager: >>> EXECUTING create of ServiceRecord{8666a9a u0 com.example.illa.testff/.MyService} in app ProcessRecord{2b66907 13759:com.example.illa.testff/u0a91}

 

07-29 22:37:48.491  1128  6869 D ActivityManager: checkAllowBackground: uid=10091 pkg=com.example.illa.testff rec=UidRecord{5495f47 u0a91 LAST bg:+1m7s354ms idle change:idle procs:1 seq(0,0,0)} always=false idle=true
07-29 22:37:48.492  1128  6869 I ActivityManager: App 10091/com.example.illa.testff targets O+, restricted
07-29 22:37:48.492  1128  6869 D ActivityManager: checkAllowBackground: uid=10091 pkg=com.example.illa.testff startMode=2 onwhitelist=false onwhitelist(ei)=false
 

 

07-30 02:00:19.822  1128  1930 D ActivityManager: checkAllowBackground: uid=10091 pkg=com.example.illa.testff rec=UidRecord{5999804 u0a91 LAST bg:+20s513ms change:cached procs:1 seq(0,0,0)} always=false idle=false

 


我们对比一下log的差异可以看到这个地方比较奇怪,发生异常的log idle=true而且多打印了一行log,找到打印后面这行log的位置

    int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        // 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;
        }

而这个appRestrictedInBackgroundLocked()方法正是getAppStartModeLocked()方法里面当uidRec.idle=true时调用的,正是当uidRec.idle=true 便会成立,从而导致我们log打印的差异

return ActivityManager.APP_START_MODE_DELAYED_RIGID;

看到这一句,我们去查一下APP_START_MODE_DELAYED_RIGID这个值等于2,而错误log里面startMode正是等于2,这也再一次证实我们的推断是正确的。所以问题的关键就在于uidRec.idle为什么会是true,

 

 

 final void idleUids() {
        synchronized (this) {
            final int N = mActiveUids.size();
            if (N <= 0) {
                return;
            }
            final long nowElapsed = SystemClock.elapsedRealtime();
            final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
            long nextTime = 0;
            if (mLocalPowerManager != null) {
                mLocalPowerManager.startUidChanges();
            }
            for (int i=N-1; i>=0; i--) {
                final UidRecord uidRec = mActiveUids.valueAt(i);
                final long bgTime = uidRec.lastBackgroundTime;
                if (bgTime > 0 && !uidRec.idle) {
                    if (bgTime <= maxBgTime) {
                        EventLogTags.writeAmUidIdle(uidRec.uid);
						android.util.Log.e("gaoshifeng","bgTime = " + bgTime);
						android.util.Log.e("gaoshifeng","maxBgTime = " + maxBgTime);
						android.util.Log.e("gaoshifeng","AMS 25781");
                        uidRec.idle = true;
                        uidRec.setIdle = true;
                        doStopUidLocked(uidRec.uid, uidRec);
                    } else {
                        if (nextTime == 0 || nextTime > bgTime) {
                            nextTime = bgTime;
                        }
                    }
                }
            }
            if (mLocalPowerManager != null) {
                mLocalPowerManager.finishUidChanges();
            }
            if (nextTime > 0) {
                mHandler.removeMessages(IDLE_UIDS_MSG);
                mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
                        nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
            }
        }
    }

在ams里面找一下uidRec.idle被赋值的位置发现在idleUids里面,可以看到这里很明显做了一个时间的判断,而且BACKGROUND_SETTLE_TIME正是60s。这里得判断的意思就是系统boot的时间减去60s,lastBackgroundTime这个时间可以理解成按home键的时间,也就是app开始进入background的时间,如果这个时间比系统boot的时间减去60s小或者相等那么就判断这个app进入background的时间已经满了60s或者更久,那就将uidRec.idle 赋值为true; 也就导致了后面的异常

final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME

mConstants.BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;

 

结论

处于前台时,应用可以自由创建和运行前台与后台 Service。 进入后台时,在一个一分钟的时间窗内,应用仍可以创建和使用 Service。 在该时间窗结束后,应用将被视为处于空闲状态。 此时,系统将停止应用的后台 Service

 

解决方案

经过对startService()流程以及log的分析还有google官方文档的阅读我们可以得出以下几个解决方案

 

方案一

可以考虑在

frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

getAppStartModeLocked()方法里面加个特殊判断

if ("com.example.illa.testff".equals(packageName)) {            

    return ActivityManager.APP_START_MODE_NORMAL;

}

 

方案二

这是google官方给出的解决方案

使用startForegroundService,此方法会在状态栏显示通知(可以去掉状态栏方法)

Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service

 

方案三

因为这个限制其实是O版本以后才有的,所以targetSdk < 26也可以实现该新特性规避。

 

方案四

StartService的地方加try catch 防止crash。

 

以此文向刘神表达我的respect!

 

 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值