我是靠谱客的博主 甜美香菇,最近开发中收集的这篇文章主要介绍Android后台源码,Android8.0的后台Service优化源码解析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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

java.lang.RuntimeException: Unable to start receiver com.anysoft.tyyd.appwidget.PlayAppWidgetProvider:

java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.anysoft.tyyd/.play.PlayerService }:

app is in background uid UidRecord{607ef50 u0a127 RCVR idle change:idle|uncached procs:1 seq(0,0,0)}

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

重现情景:

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

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

后台服务限制:处于空闲状态时,应用可以使用的后台服务存在限制。 这些限制不适用于前台服务,因为前台服务更容易引起用户注意。

在 Android 8.0 之前,创建前台服务的方式通常是先创建一个后台服务,然后将该服务推到前台。

Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。

在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground()方法以显示新服务的用户可见通知。

如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。

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

下面看看源码的变动情况

源码解析:

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

启动Service的入口ContextImpl.startService()

ContextImpl:

@Override

public ComponentName startService(Intent service) {

warnIfCallingFromSystemProcess();

return startServiceCommon(service, false, mUser);

}

//进入startServiceCommon()

private ComponentName startServiceCommon(Intent service, boolean requireForeground,

UserHandle user) {

try {

validateServiceIntent(service);

service.prepareToLeaveProcess(this);

ComponentName cn = ActivityManager.getService().startService(

mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(

getContentResolver()), requireForeground,

getOpPackageName(), user.getIdentifier());

if (cn != null) {

if (cn.getPackageName().equals("!")) {

throw new SecurityException(

"Not allowed to start service " + service

+ " without permission " + cn.getClassName());

} else if (cn.getPackageName().equals("!!")) {

throw new SecurityException(

"Unable to start service " + service

+ ": " + cn.getClassName());

} else if (cn.getPackageName().equals("?")) {//1

throw new IllegalStateException(

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

}

}

return cn;

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

}

1处就是我们bug抛出异常的地方Not allowed to start service Intent...

我们先看看ActivityManager.getService().startService()的返回逻辑

ActivityManagerService:

@Override

public ComponentName startService(

...

try {

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

} finally {

Binder.restoreCallingIdentity(origId);

}

return res;

}

启动Service会调用ActiveServices.startServiceLocked()

ActiveServices:

ComponentName startServiceLocked(...){

...

// If this isn't a direct-to-foreground start, check our ability to kick off an

// arbitrary service

if (!r.startRequested && !fgRequired) {

// Before going further -- if this app is not allowed to start services in the

// background, then at this point we aren't going to let it period.

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

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

if (allowed != ActivityManager.APP_START_MODE_NORMAL) {

Slog.w(TAG, "Background start not allowed: service "

+ service + " to " + r.name.flattenToShortString()

+ " from pid=" + callingPid + " uid=" + callingUid

+ " pkg=" + callingPackage);

if (allowed == ActivityManager.APP_START_MODE_DELAYED) {

return null;

}

UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);

//2.

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

}

}

}

这里的fgRequired是从ContextImpl.startServiceCommon(fgRequired:false)传进来的,为false.

2标记处是不是又看到相关bug信息了 "app is in background uid...",于是我们看看allowed返回值mAm.getAppStartModeLocked()

ActivityManagerService:

int getAppStartModeLocked(){

UidRecord uidRec = mActiveUids.get(uid);

...

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

final int startMode = (alwaysRestrict) ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk) :

appServicesRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);

}

return startMode;

...

}

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

ActivityManagerService:

int appServicesRestrictedInBackgroundLocked(){

...

// Persistent app?

if (mPackageManagerInt.isPackagePersistent(packageName)) {

if (DEBUG_BACKGROUND_CHECK) {

Slog.i(TAG, "App " + uid + "/" + packageName

+ " is persistent; not restricted in background");

}

return ActivityManager.APP_START_MODE_NORMAL;

}

// Non-persistent but background whitelisted?

if (uidOnBackgroundWhitelist(uid)) {

if (DEBUG_BACKGROUND_CHECK) {

Slog.i(TAG, "App " + uid + "/" + packageName

+ " on background whitelist; not restricted in background");

}

return ActivityManager.APP_START_MODE_NORMAL;

}

// Is this app on the battery whitelist?

if (isOnDeviceIdleWhitelistLocked(uid)) {

if (DEBUG_BACKGROUND_CHECK) {

Slog.i(TAG, "App " + uid + "/" + packageName

+ " on idle whitelist; not restricted in background");

}

return ActivityManager.APP_START_MODE_NORMAL;

}

return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);

}

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

ActivityManagerService:

// Apps that target O+ are always subject to background check

if (packageTargetSdk >= Build.VERSION_CODES.O) {

if (DEBUG_BACKGROUND_CHECK) {

Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");

}

return ActivityManager.APP_START_MODE_DELAYED_RIGID;

}

// ...and legacy apps get an AppOp check

int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,

uid, packageName);

if (DEBUG_BACKGROUND_CHECK) {

Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);

}

switch (appop) {

case AppOpsManager.MODE_ALLOWED:

return ActivityManager.APP_START_MODE_NORMAL;

case AppOpsManager.MODE_IGNORED:

return ActivityManager.APP_START_MODE_DELAYED;

default:

return ActivityManager.APP_START_MODE_DELAYED_RIGID;

}

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

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

入口依旧为ContextImpl.startForegroundService()

@Override

public ComponentName startForegroundService(Intent service) {

warnIfCallingFromSystemProcess();

return startServiceCommon(service, true, mUser);

}

这里与startService的区别就在于传入的fgRequired为true.于是一路

ContextImpl.startServiceCommon()-->ActivityManagerService.startService()-->ActiveServices.startServiceLocked(),由于fgRequired为true,就跳过刚才那段逻辑下面就是正常的Service启动流程了.

那么还有一个问题,为什么还需要在5秒内调用Service.startForeground()呢?

在启动Service的过程中会调用到ActiveServices.bringUpServiceLocked()方法,然后会调用ActiveServices.sendServiceArgsLocked()

ActiveServices:

...

while (r.pendingStarts.size() > 0) {

...

if (r.fgRequired && !r.fgWaiting) {

if (!r.isForeground) {

//3

scheduleServiceForegroundTransitionTimeoutLocked(r);

} else {

r.fgRequired = false;

}

}

...

}

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

ActiveServices:

void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {

if (r.app.executingServices.size() == 0 || r.app.thread == null) {

return;

}

Message msg = mAm.mHandler.obtainMessage(

ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);

msg.obj = r;

r.fgWaiting = true;

mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);//这个值是5*1000

}

看下这个消息的处理

ActivityManagerService:

class MainHandler extends Handler{

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

...

case SERVICE_FOREGROUND_TIMEOUT_MSG: {

mServices.serviceForegroundTimeout((ServiceRecord)msg.obj);

}

}

}

又来到ActiveServices

ActiveServices:

void serviceForegroundTimeout(ServiceRecord r) {

ProcessRecord app;

synchronized (mAm) {

if (!r.fgRequired || r.destroying) {

return;

}

app = r.app;

r.fgWaiting = false;

stopServiceLocked(r);

}

}

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

Service:

public final void startForeground(int id, Notification notification) {

try {

mActivityManager.setServiceForeground(

new ComponentName(this, mClassName), mToken, id,

notification, 0);

} catch (RemoteException ex) {

}

}

mActivityManager就是调用AMS

ActivityManagerService:

@Override

public void setServiceForeground(ComponentName className, IBinder token,

int id, Notification notification, int flags) {

synchronized(this) {

mServices.setServiceForegroundLocked(className, token, id, notification, flags);

}

}

ActiveServices:

public void setServiceForegroundLocked(ComponentName className, IBinder token,

int id, Notification notification, int flags) {

final int userId = UserHandle.getCallingUserId();

final long origId = Binder.clearCallingIdentity();

try {

ServiceRecord r = findServiceLocked(className, token, userId);

if (r != null) {

setServiceForegroundInnerLocked(r, id, notification, flags);

}

} finally {

Binder.restoreCallingIdentity(origId);

}

}

来看setServiceForegroundInnerLocked()

ActiveServices:

private void setServiceForegroundInnerLocked(){

...

if (r.fgRequired) {

if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "Service called startForeground() as required: " + r);}

r.fgRequired = false;

r.fgWaiting = false;

mAm.mHandler.removeMessages(

ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);

}

...

}

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

最后

以上就是甜美香菇为你收集整理的Android后台源码,Android8.0的后台Service优化源码解析的全部内容,希望文章能够帮你解决Android后台源码,Android8.0的后台Service优化源码解析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部