700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 探讨8.0版本下后台service存活机制及保活

探讨8.0版本下后台service存活机制及保活

时间:2019-04-30 17:53:49

相关推荐

探讨8.0版本下后台service存活机制及保活

Android进阶之路系列:/chzphoenix/column/info/16488

前段时间时间对targetsdkversion进行升级,结果发现了一个问题:

在不升级前,app退出后,后台service可以存活很长一段时间;而升级后,8.0以下版本手机还是一样,但是8.0及以上版本的手机上,app退出一分钟后后台service就被杀死了。

经过对比发现升级后当service被杀死时有这样一条日志:

Stopping service due to app idle: u0a309 -1m19s437ms xxx.xxx.xxx/xxx.xxxx.xxxx

通过相关文章得知,一分钟后停止后台service的逻辑在ActivityManagerService中,也就是AMS。

(AMS的源码在android sdk根目录下的sources/android-xx/com/android/server/am中)

这里有个函数updateOomAdjLocked,代码过多这里就不全贴出来了,其中有一个sendMsg的操作,代码如下:

// UID is now in the background (and not on the temp whitelist). Was it

// previously in the foreground (or on the temp whitelist)?

if (!ActivityManager.isProcStateBackground(uidRec.setProcState)

|| uidRec.setWhitelist) {

uidRec.lastBackgroundTime = nowElapsed;

if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {

// Note: the background settle time is in elapsed realtime, while

// the handler time base is uptime. All this means is that we may

// stop background uids later than we had intended, but that only

// happens because the device was sleeping so we are okay anyway.

mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,

mConstants.BACKGROUND_SETTLE_TIME);

}

}

可以看到如果是后台service且不在临时白名单中(临时白名单未找到更多的文档,不过DOZE模式有一个调试命令,可以将一个app放入临时白名单,所以这个临时白名单应该只与调试有关),会发送一个延迟消息,这个延迟与常量BACKGROUND_SETTLE_TIME有关,根据命名就知道这是service在后台的存活时间,它的值是:

public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;

private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;

可以看到这个是默认是一分钟,但是这个值可以改变: 在ActivityManagerConstants下

private void updateConstants() {

final String setting = Settings.Global.getString(mResolver,

Settings.Global.ACTIVITY_MANAGER_CONSTANTS);

synchronized (mService) {

try {

mParser.setString(setting);

} catch (IllegalArgumentException e) {

// Failed to parse the settings string, log this and move on

// with defaults.

Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);

}

MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,

DEFAULT_MAX_CACHED_PROCESSES);

BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,

DEFAULT_BACKGROUND_SETTLE_TIME);

...

}

}

而Settings.Global.ACTIVITY_MANAGER_CONSTANTS是被隐藏的,而且由于是全局的设置,大家了解一下即可,这里我们不继续讨论。 回到开始,我们来继续看看BACKGROUND_SETTLE_TIME这条消息的处理。 同样在AMS中:

case IDLE_UIDS_MSG: {

idleUids();

} break;

这样我们就来看看idleUids函数:

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);

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);

}

}

}

抛去一些判断逻辑,看到停止服务实际是执行了doStopUidLocked函数:

final void doStopUidLocked(int uid, final UidRecord uidRec) {

mServices.stopInBackgroundLocked(uid);

enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);

}

执行了ActivityService的stopInBackgroundLocked函数:

void stopInBackgroundLocked(int uid) {

// Stop all services associated with this uid due to it going to the background

// stopped state.

ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));

ArrayList<ServiceRecord> stopping = null;

if (services != null) {

for (int i=services.mServicesByName.size()-1; i>=0; i--) {

ServiceRecord service = services.mServicesByName.valueAt(i);

if (service.appInfo.uid == uid && service.startRequested) {

if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,

service.appInfo.targetSdkVersion, -1, false, false)

!= ActivityManager.APP_START_MODE_NORMAL) {

if (stopping == null) {

stopping = new ArrayList<>();

}

String compName = service.name.flattenToShortString();

EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);

StringBuilder sb = new StringBuilder(64);

sb.append("Stopping service due to app idle: ");

UserHandle.formatUid(sb, service.appInfo.uid);

sb.append(" ");

TimeUtils.formatDuration(service.createTime

- SystemClock.elapsedRealtime(), sb);

sb.append(" ");

sb.append(compName);

Slog.w(TAG, sb.toString());

stopping.add(service);

}

}

}

if (stopping != null) {

for (int i=stopping.size()-1; i>=0; i--) {

ServiceRecord service = stopping.get(i);

service.delayed = false;

services.ensureNotStartingBackgroundLocked(service);

stopServiceLocked(service);

}

}

}

}

这里可以看到首先遍历service,经过一些条件判断,将满足条件的service放入stopping列表中,然后遍历这个列表停止service。

同时这里我们可以看到文章开头那段日志的由来,当满足条件可以放入stopping列表中才打印日志。

这里我们得到了一个小结论:8.0及以上版本手机中有一个机制,app退出后一分钟后会清理后台service(满足条件的),但是foreground service不会。所以在8.0上可以通过foregroundservice的形式提高存活。

那么为什么targetsdkversion不同,有的不会停止?

那么targetsdkversion一定影响了判断条件,我们注意到其中一行代码:

if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,

service.appInfo.targetSdkVersion, -1, false, false)

!= ActivityManager.APP_START_MODE_NORMAL) {

这样的判断中有targetsdkversion的参与,所以猜测在答案这个函数里。

这个函数是在AMS中,源码如下:

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,

int callingPid, boolean alwaysRestrict, boolean disabledOnly) {

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 || 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));

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;

}

这里也有很多判断,我们看首先判断是不是ephemeral apps,短暂应用?这个我没有找到更多的文档,只有一篇说chrome团队开发一款无需下载直接使用的,也不确定就是这里这个。不过我们看注释可以看到,ephemeral apps是完全不允许后台运行的,所以我们的app一定不是ephemeral apps。(这里以后有机会我们再仔细调查一下)继续,disabledOnly在前面的调用可以看到这个参数是false;继续,alwaysRestrict同样参数是false,所以这样startMode就是函数appServicesRestrictedInBackgroundLocked的返回值,这个函数如下:

int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {

// 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;

}

// None of the service-policy criteria apply, so we apply the common criteria

return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);

}

这里有三个判断,是否Persistent app;是否在允许后台运行白名单;是否在省电(耗电)白名单。

我们看都是返回ActivityManager.APP_START_MODE_NORMAL

回到开始的判断我们知道当不等于ActivityManager.APP_START_MODE_NORMAL时,才会将service放入stopping列表,所以这三种情况都不会停掉service

省电(耗电)白名单比较好理解,在系统设置的省电模式中可以设置允许某个app后台运行。

那么第一个Persistent app是什么意思?

实际上在Manifest中,我们可以为application设置android:persistent=”true”,但是前提是系统应用,也就是说我们第三方应用设置这个也没效果。关于Persistent app我们以后另开一篇文章细说。

第二个白名单又是什么意思?

我们来看看uidOnBackgroundWhitelist的代码:

private boolean uidOnBackgroundWhitelist(final int uid) {

final int appId = UserHandle.getAppId(uid);

final int[] whitelist = mBackgroundAppIdWhitelist;

final int N = whitelist.length;

for (int i = 0; i < N; i++) {

if (appId == whitelist[i]) {

return true;

}

}

return false;

}

我们再来看看哪里为mBackgroundAppIdWhitelist赋值,在backgroundWhitelistUid函数中:

@Override

public void backgroundWhitelistUid(final int uid) {

if (Binder.getCallingUid() != Process.SYSTEM_UID) {

throw new SecurityException("Only the OS may call backgroundWhitelistUid()");

}

if (DEBUG_BACKGROUND_CHECK) {

Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist");

}

synchronized (this) {

final int N = mBackgroundAppIdWhitelist.length;

int[] newList = new int[N+1];

System.arraycopy(mBackgroundAppIdWhitelist, 0, newList, 0, N);

newList[N] = UserHandle.getAppId(uid);

mBackgroundAppIdWhitelist = newList;

}

}

第一行代码就明确表明了,只能系统应用使用这个方法,所以我们知道第三方应用无法使用这个白名单。

继续,这三个条件我们都不满足,就执行了appRestrictedInBackgroundLocked函数:

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;

}

// ...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;

}

}

终于看到我们寻找的了,第一段代码就可以看到当targetsdkversion大于等于Build.VERSION_CODES.O = 26,即8.0时,返回

ActivityManager.APP_START_MODE_DELAYED_RIGID

根据前面我们知道这个值service一定会放入stopping列表。所以当targetsdkversion升级到8.0后,在8.0及以上版本的手机上app退出后一分钟service就会被停掉。

当targetsdkversion小于26,会检查app是否有后台运行的权限AppOpsManager.OP_RUN_IN_BACKGROUND,如果有权限则返回ActivityManager.APP_START_MODE_NORMAL,则根据前面的判断不会停止服务。

注意:这里AppOpsManager权限是SDK19引入的,是google隐藏的,不推荐我们使用。这里我们通过app的运行情况得知默认OP_RUN_IN_BACKGROUND权限是允许的。

这样我们基本上弄清楚8.0上service的存活机制了,按顺序经历下面几个判断

1、是否后台service,如果是foreground service则不停,否则继续

2、是否在临时白名单中,如果是则不停,否则继续

3、是否是ephemeral apps,如果是则停,否则继续

4、是否是Persistent app,如果是则不停,否则继续

5、是否在允许后台运行白名单,在则不停,否则继续

6、是否在省电白名单,在则不停,否则继续

7、是否targetsdkversion大于等于26,是则停,否则继续

8、如果targetsdkversion小于26,是否有OP_RUN_IN_BACKGROUND权限,有则不停,否则停

而在8.0上,service的停止则有一分钟的延迟。

那么如果升级了targetsdkversion,怎么才能让后台service存活?

这里有几个条件不需要考虑了,如:

临时白名单用于调试;Persistent app需要系统app;后台运行白名单也需要系统app

那么剩下的有哪些呢?

1、改成前台service,这个相关文章比较多,就不细说了

2、加入省电白名单

怎么将app加入省电白名单?

首先使用PowerManager.isIgnoringBatteryOptimizations判断是否有已经加入了

private boolean isIgnoringBatteryOptimizations(){

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

String packageName = getPackageName();

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);

return pm.isIgnoringBatteryOptimizations(packageName);

}

return false;

}

如果未加入,则需要通过Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS拉起请求弹窗

private void gotoSettingIgnoringBatteryOptimizations() {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

try {

Intent intent = new Intent();

String packageName = getPackageName();

intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);

intent.setData(Uri.parse("package:" + packageName));

startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE);

} catch (Exception e) {

e.printStackTrace();

}

}

}

注意还要添加权限

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

然后可以在onActivityResult中处理结果

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if(resultCode == RESULT_OK){

if (requestCode == REQUEST_IGNORE_BATTERY_CODE) {

Log.d("Hello World!","开启省电模式成功");

}

}else if (resultCode == RESULT_CANCELED) {

if (requestCode == REQUEST_IGNORE_BATTERY_CODE) {

Toast.makeText(this, "请用户开启忽略电池优化~", Toast.LENGTH_LONG).show();

}

}

}

编译运行,会弹窗请求白名单的弹窗,允许。开启后台service,退出app,会发现一分钟后service依然存活。成功!

但是这个方案需要用户主动行为,所以如果有强烈存活需求还是建议改用foreground service

Android进阶之路系列:/chzphoenix/column/info/16488

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。