今天在用戶的錯(cuò)誤列表上看到這么個(gè)bug
java.lang.RuntimeException: Unable to start receiver com.anysoft.tyyd.appwidget.PlayAppWidgetProvider:
java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.anysoft.tyyd/.play.PlayerService }:
app is in background uid UidRecord{607ef50 u0a127 RCVR idle change:idle|uncached procs:1 seq(0,0,0)}
這個(gè)bug是在適配Android8.0后出現(xiàn)的,解釋下就是,app在后臺(tái)uid的進(jìn)程下面不允許啟動(dòng)Service.
重現(xiàn)情景:
由于我們的桌面小控件在onUpdate()方法里用Context.startService()啟動(dòng)了Service.當(dāng)app的進(jìn)程沒有啟動(dòng)時(shí),把桌面部件拉到Launcher桌面上就會(huì)報(bào)這個(gè)錯(cuò)誤.
先來看看Android官網(wǎng)在8.0時(shí)的后臺(tái)服務(wù)啟動(dòng)優(yōu)化的一些措施:
后臺(tái)服務(wù)限制:處于空閑狀態(tài)時(shí)郁稍,應(yīng)用可以使用的后臺(tái)服務(wù)存在限制。 這些限制不適用于前臺(tái)服務(wù)猖凛,因?yàn)榍芭_(tái)服務(wù)更容易引起用戶注意删掀。
在 Android 8.0 之前,創(chuàng)建前臺(tái)服務(wù)的方式通常是先創(chuàng)建一個(gè)后臺(tái)服務(wù)躬翁,然后將該服務(wù)推到前臺(tái)怠堪。
Android 8.0 有一項(xiàng)復(fù)雜功能;系統(tǒng)不允許后臺(tái)應(yīng)用創(chuàng)建后臺(tái)服務(wù)隙疚。 因此壤追,Android 8.0 引入了一種全新的方法磕道,即 Context.startForegroundService()
,以在前臺(tái)啟動(dòng)新服務(wù)行冰。
在系統(tǒng)創(chuàng)建服務(wù)后溺蕉,應(yīng)用有五秒的時(shí)間來調(diào)用該服務(wù)的 startForeground()方法以顯示新服務(wù)的用戶可見通知。
如果應(yīng)用在此時(shí)間限制內(nèi)未調(diào)用 startForeground()悼做,則系統(tǒng)將停止服務(wù)并聲明此應(yīng)用為 ANR疯特。
我總結(jié)一下就是8.0后,如果一個(gè)處于后臺(tái)的應(yīng)用想要啟動(dòng)Service就必須調(diào)用Context.startForegroundService()并且5秒內(nèi)在該Service內(nèi)調(diào)用startForeground()
下面看看源碼的變動(dòng)情況
源碼解析:
首先是后臺(tái)應(yīng)用調(diào)用Context.startService()啟動(dòng)Service為什么會(huì)報(bào)錯(cuò)
啟動(dòng)Service的入口ContextImpl.startService()
ContextImpl:
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
//進(jìn)入startServiceCommon()
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
} else if (cn.getPackageName().equals("?")) {//1
throw new IllegalStateException(
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
1處就是我們bug拋出異常的地方Not allowed to start service Intent...
我們先看看ActivityManager.getService().startService()的返回邏輯
ActivityManagerService:
@Override
public ComponentName startService(
...
try {
res = mServices.startServiceLocked(caller, service resolvedType, callingPid, callingUid, requireForeground, callingPackage, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
return res;
}
啟動(dòng)Service會(huì)調(diào)用ActiveServices.startServiceLocked()
ActiveServices:
ComponentName startServiceLocked(...){
...
// If this isn't a direct-to-foreground start, check our ability to kick off an
// arbitrary service
if (!r.startRequested && !fgRequired) {
// Before going further -- if this app is not allowed to start services in the
// background, then at this point we aren't going to let it period.
final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
r.appInfo.targetSdkVersion, callingPid, false, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
+ service + " to " + r.name.flattenToShortString()
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage);
if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
return null;
}
UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
//2.
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
}
這里的fgRequired是從ContextImpl.startServiceCommon(fgRequired:false)傳進(jìn)來的,為false.
2標(biāo)記處是不是又看到相關(guān)bug信息了 "app is in background uid...",于是我們看看allowed返回值mAm.getAppStartModeLocked()
ActivityManagerService:
int getAppStartModeLocked(){
UidRecord uidRec = mActiveUids.get(uid);
...
if (uidRec == null || alwaysRestrict || uidRec.idle) {
final int startMode = (alwaysRestrict) ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk) :
appServicesRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
return startMode;
...
}
allowed的返回值就是startMode.這里alwaysRestrict是傳入的參數(shù)false,這里的uidRec由于應(yīng)用進(jìn)程都未啟動(dòng),于是uidRec.idle為true表示空閑進(jìn)程,所以我們直接看appServicesRestrictedInBackgroundLocked()
ActivityManagerService:
int appServicesRestrictedInBackgroundLocked(){
...
// Persistent app?
if (mPackageManagerInt.isPackagePersistent(packageName)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " is persistent; not restricted in background");
}
return ActivityManager.APP_START_MODE_NORMAL;
}
// Non-persistent but background whitelisted?
if (uidOnBackgroundWhitelist(uid)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " on background whitelist; not restricted in background");
}
return ActivityManager.APP_START_MODE_NORMAL;
}
// Is this app on the battery whitelist?
if (isOnDeviceIdleWhitelistLocked(uid)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " on idle whitelist; not restricted in background");
}
return ActivityManager.APP_START_MODE_NORMAL;
}
return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
這個(gè)方法會(huì)判斷是否是Persistent app,白名單,電量白名單應(yīng)用,很顯然普通app都不是,于是進(jìn)入appRestrictedInBackgroundLocked()看看
ActivityManagerService:
// Apps that target O+ are always subject to background check
if (packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
}
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
// ...and legacy apps get an AppOp check
int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
uid, packageName);
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
}
switch (appop) {
case AppOpsManager.MODE_ALLOWED:
return ActivityManager.APP_START_MODE_NORMAL;
case AppOpsManager.MODE_IGNORED:
return ActivityManager.APP_START_MODE_DELAYED;
default:
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
這里的packageTargetSdk剛好是O,所以返回ActivityManager.APP_START_MODE_DELAYED_RIGID了.由于返回值不是ActivityManager.APP_START_MODE_NORMAL.于是就return new ComponentName("?", "app is in background uid " + uidRec);然后就出現(xiàn)了開頭的異常.
下面看下Context.startForegroundService啟動(dòng)Service的邏輯
入口依舊為ContextImpl.startForegroundService()
@Override
public ComponentName startForegroundService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, true, mUser);
}
這里與startService的區(qū)別就在于傳入的fgRequired為true.于是一路
ContextImpl.startServiceCommon()-->ActivityManagerService.startService()-->ActiveServices.startServiceLocked(),由于fgRequired為true,就跳過剛才那段邏輯下面就是正常的Service啟動(dòng)流程了.
那么還有一個(gè)問題,為什么還需要在5秒內(nèi)調(diào)用Service.startForeground()呢?
在啟動(dòng)Service的過程中會(huì)調(diào)用到ActiveServices.bringUpServiceLocked()方法,然后會(huì)調(diào)用ActiveServices.sendServiceArgsLocked()
ActiveServices:
...
while (r.pendingStarts.size() > 0) {
...
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
//3
scheduleServiceForegroundTransitionTimeoutLocked(r);
} else {
r.fgRequired = false;
}
}
...
}
在3處會(huì)調(diào)用scheduleServiceForegroundTransitionTimeoutLocked()作用就是發(fā)送一個(gè)延時(shí)5秒的message
ActiveServices:
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
if (r.app.executingServices.size() == 0 || r.app.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
msg.obj = r;
r.fgWaiting = true;
mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);//這個(gè)值是5*1000
}
看下這個(gè)消息的處理
ActivityManagerService:
class MainHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case SERVICE_FOREGROUND_TIMEOUT_MSG: {
mServices.serviceForegroundTimeout((ServiceRecord)msg.obj);
}
}
}
又來到ActiveServices
ActiveServices:
void serviceForegroundTimeout(ServiceRecord r) {
ProcessRecord app;
synchronized (mAm) {
if (!r.fgRequired || r.destroying) {
return;
}
app = r.app;
r.fgWaiting = false;
stopServiceLocked(r);
}
}
這里就是調(diào)用stopServiceLocked(r)把service關(guān)掉了.那么Service.startForeground()一定會(huì)有代碼取消這個(gè)消息,來看:
Service:
public final void startForeground(int id, Notification notification) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, 0);
} catch (RemoteException ex) {
}
}
mActivityManager就是調(diào)用AMS
ActivityManagerService:
@Override
public void setServiceForeground(ComponentName className, IBinder token,
int id, Notification notification, int flags) {
synchronized(this) {
mServices.setServiceForegroundLocked(className, token, id, notification, flags);
}
}
ActiveServices:
public void setServiceForegroundLocked(ComponentName className, IBinder token,
int id, Notification notification, int flags) {
final int userId = UserHandle.getCallingUserId();
final long origId = Binder.clearCallingIdentity();
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
setServiceForegroundInnerLocked(r, id, notification, flags);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
來看setServiceForegroundInnerLocked()
ActiveServices:
private void setServiceForegroundInnerLocked(){
...
if (r.fgRequired) {
if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "Service called startForeground() as required: " + r);}
r.fgRequired = false;
r.fgWaiting = false;
mAm.mHandler.removeMessages(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
}
...
}
這里就removeMessages(SERVICE_FOREGROUND_TIMEOUT_MSG)取消這個(gè)message了.