上个礼拜刘神在微信上跟我探讨人生的时候突然提到,小高你的博客很久没更新了。我心里一惊,是啊已经一年没更新博客了。但是写点什么呢?刘神说跟你讲下我最近遇到一个很有意思的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 IActivityManagerSingleton =
new Singleton() {
@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!