赞
踩
上个礼拜刘神在微信上跟我探讨人生的时候突然提到,小高你的博客很久没更新了。我心里一惊,是啊已经一年没更新博客了。但是写点什么呢?刘神说跟你讲下我最近遇到一个很有意思的bug,也许你就有想法了
当你在activity里面中delay(这里可以用handler或者各种延时方法)启动service时,按home键回到桌面,如果delay的时间为70s,那么程序会报错,但是如果delay的时间为50s,却可以正常运行
相信聪明的同学肯定已经心中有数了,google果然对安全性越来越看重了,很明显google这是在限制后台app 只能在一定的时间内startService,那么这个问题我们该怎么去解决呢,我们可以去看下google官方有没有相应解释,以及将startService相应流程看一遍,相信问题自然迎刃而解
https://developer.android.google.cn/about/versions/oreo/background.html#services
启动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,刚刚我们已经知道问题产生的位置就是这里,为了方便阅读我就直接把变量名字改成类名好了。我们来仔细看看这一部分代码
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!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。