从源码角度看Android_O_AMS新特性

简介

Android O上以后对AMS的广播与服务做了严格的限制,静态注册的广播不再能够无所欲为的接收隐式的广播,后台的应用进程也不再能够调用startService了。Android 作出这样的限制主要是为了限制住不自觉的开发者,不让他们随意的浪费系统资源。

这篇文章里,我将从源码的角度分析Android O是如何限制静态广播与后台服务的,相关的广播和服务源码背景知识大家可以看我以前的文章。分析的后面会列出Android O上广播和服务的适配指南。谷歌推荐使用的JobScheduler原理大家可以看我的上一篇文章。

文章的最后是我的一些对这些限制的自问自答,也欢迎大家提出问题~

BroadcastReceiver

没有权限的前提下, 发出的隐式Intent,静态的接收者不能收到广播

查看大图

[frameworks/base/services/core/java/com/android/server/am/BroadcastQueue#processNextBroadcast]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (!skip) {
final int allowed = mService.getAppStartModeLocked(
info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
&& !isSignaturePerm(r.requiredPermissions))) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
Slog.w(TAG, "Background execution not allowed: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
skip = true;
}
}
}

[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService#getAppStartModeLocked]

1
2
3
4
5
6
7
8
9
10
11
12
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
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;
}
return ActivityManager.APP_START_MODE_NORMAL;
}

[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService#appRestrictedInBackgroundLocked]

1
2
3
4
5
6
7
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// 如果targetSdkVerision设置的为26+的话,即使是在前台的进程,也不能够收到广播
// 2018年下半年Google将强制开发者将targetSdkVersion设置为27以上
if (packageTargetSdk >= Build.VERSION_CODES.O) {
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
}

隐式发送的静态广播不能接收解决方案

方案 建议
发送端直接将intent加入FLAG_RECEIVER_INCLUDE_BACKGROUND的flag 频繁发送的重要广播不推荐
将隐式广播转换为显式,调用setPackage、setComponent其中一个即可 确定接收端组件或包名时可用,低扩展性
使用动态注册广播的方式替代静态注册 推荐
广播发送端与接收端使用signature级别的权限 推荐
使用JobScheduler代替广播接收事件 部分场景下,推荐

发送端实例代码

1
2
3
4
5
<permission
android:name="top.navyblue.bugsfree.permission"
android:protectionLevel="signature"/>
<uses-permission android:name="top.navyblue.bugsfree.permission" />
1
2
3
4
5
6
Intent intent = new Intent(Utils.IMPLICT_INTENT_ACTION);
// intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
// intent.setPackage("com.example.yongbiaoai.bugsfreeclient");
// intent.setComponent(new ComponentName("com.example.yongbiaoai.bugsfreeclient", "com.example.yongbiaoai.bugsfreeclient.ImplictReceiver"));
// intent.addFlags(0x01000000);
sendBroadcast(intent, "top.navyblue.bugsfree.permission");

如果需要自定义权限则需要声明一个客户端自己的权限,然后在调用sendBroadcast时加入这个权限

接收端实例代码

1
2
3
4
5
6
7
8
9
10
11
<uses-permission android:name="top.navyblue.bugsfree.permission" />
<receiver
android:name=".ImplictReceiver"
android:permission="top.navyblue.bugsfree.permission">
<intent-filter>
<action android:name="top.navyblue.top.implict.broadcast"/>
</intent-filter>
</receiver>

定义静态接收者时,需要填入permission选项

Service

idle状态下,应用启动服务将抛异常

查看大图

active与idle的变换规则

  • 如果应用变为前台,即procState小于PROCESS_STATE_TRANSIENT_BACKGROUND(8)时,UID状态马上变更为active状态
  • 如果应用变为后台,即procState大于等于PROCESS_STATE_TRANSIENT_BACKGROUND(8)时,应用持续在后台60s后,UID状态会变更为idle状态

调试方法:

  • 输入 “adb logcat -b events | grep am_uid” 来查看uid状态变化的LOG
  • 输入 “adb shell am make-uid-idle “ 使指定应用的uid变为idle状态

idle白名单,updateWhitelistAppIdsLocked场景

场景 意义
DeviceIdleController.addPowerSaveWhitelistApp 使用系统Service “deviceidle”,动态添加白名单
DeviceIdleController.removePowerSaveWhitelistApp 使用系统Service “deviceidle”,动态删除白名单
DeviceIdleController.onStart 从文件中读取包名,初始化idle白名单

查看大图

[frameworks/base/services/java/com/android/server/SystemServer]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
try {;
startOtherServices();
} catch (Throwable ex) {
throw ex;
} finally {
traceEnd();
}
}
private void startOtherServices() {
mSystemServiceManager.startService(DeviceIdleController.class);
}

在system_server启动时,会将DeviceIdleController的系统服务启动

[frameworks/base/services/core/java/com/android/server/DeviceIdleController]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
@Override
public void onStart() {
synchronized (this) {
readConfigFileLocked();
updateWhitelistAppIdsLocked();
}
mBinderService = new BinderService();
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
}
private final class BinderService extends IDeviceIdleController.Stub {
@Override
public void addPowerSaveWhitelistApp(String name) {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
try {
addPowerSaveWhitelistAppInternal(name);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void removePowerSaveWhitelistApp(String name) {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
try {
removePowerSaveWhitelistAppInternal(name);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}

在调用onStart方法时会将系统服务在ServiceManager进行publish

startForegroundService的ANR与FC:

  • 调用startForegroundService后,如果5s内没有在Service中调用startForeground,那么就会发生ANR; “Context.startForegroundService() did not then call Service.startForeground()”
  • 调用startForegroundService后,直到将Service停止之前都没有在Service中调用startForeground,那么就会发生FC

startForegroundService ANR超时机制

[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public ComponentName startForegroundService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, true, 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());
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

调用startForegroundService方法的区别在于requireForeground的FLAG被置为true,和startService并没有什么区别

[frameworks/base/services/core/java/com/android/server/am/ActiveServices#sendServiceArgsLocked]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
boolean oomAdjusted) throws TransactionTooLargeException {
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
scheduleServiceForegroundTransitionTimeoutLocked(r);
} else {
r.fgRequired = false;
}
}
}
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);
}
void serviceForegroundTimeout(ServiceRecord r) {
ProcessRecord app;
synchronized (mAm) {
app = r.app;
r.fgWaiting = false;
stopServiceLocked(r);
}
if (app != null) {
mAm.mAppErrors.appNotResponding(app, null, null, false,
"Context.startForegroundService() did not then call Service.startForeground()");
}
}

调用Service.onStartCommand时会设置一个定时器,如果5s内没有设置调用startForeground将服务置为前台,那么就会出现ANR

startForegroundService FC机制

[frameworks/base/core/java/android/app/Service#stopSelf]

1
2
3
4
5
6
7
8
9
10
public final void stopSelf(int startId) {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}

[frameworks/base/services/core/java/com/android/server/am/ActiveServices#stopServiceTokenLocked]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
boolean stopServiceTokenLocked(ComponentName className, IBinder token,
int startId) {
bringDownServiceIfNeededLocked(r, false, false);
}
private final void bringDownServiceLocked(ServiceRecord r) {
if (r.fgRequired) {
r.fgRequired = false;
r.fgWaiting = false;
mAm.mHandler.removeMessages(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
if (r.app != null) {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
msg.obj = r.app;
mAm.mHandler.sendMessage(msg);
}
}
}
void serviceForegroundCrash(ProcessRecord app) {
mAm.crashApplication(app.uid, app.pid, app.info.packageName, app.userId,
"Context.startForegroundService() did not then call Service.startForeground()");
}

在调用startForegroundService拉起服务后,如果直接stop掉Service却没有将Service置为前台的话会出现FC

ANR与FC解除

[frameworks/base/core/java/android/app/Service#startForeground]

1
2
3
4
5
6
7
8
public final void startForeground(int id, Notification notification) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, 0);
} catch (RemoteException ex) {
}
}

[frameworks/base/services/core/java/com/android/server/am/ActiveServices#setServiceForegroundInnerLocked]

1
2
3
4
5
6
7
8
9
private void setServiceForegroundInnerLocked(ServiceRecord r, int id,
Notification notification, int flags) {
if (r.fgRequired) {
r.fgRequired = false;
r.fgWaiting = false;
mAm.mHandler.removeMessages(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
}
}

当Service被启动后,客户端需要调用Service.startForeground解除ANR和FC

常见问题

问:为什么Android O只限制静态广播而不限制动态广播?
答:静态注册的广播在没有被限制的前提下,甚至可以拉起应用进程。虽然在原生3.1之后,Intent如果没有携带FLAG_INCLUDE_STOPPED_PACKAGES,是不能发送广播给被force-stop的应用的静态广播,但是如果应用进程只是被杀死而没有被force-stop,还是可以通过注册隐式广播来接收到与自己无关的广播,从而拉起自己的进程或者提升自身进程的优先级,浪费用户的资源。反观动态广播,一不能拉起进程,二生命周期往往很短,相比静态广播的权限威力小的。

问:为什么Android O只限制了后台Service而不限制前台和bounded Service?
答:一言以蔽之,就是大部分的应用开发者太不自觉了,后台Service对比前台Service,实现的成本低了很多,可以在用户毫不知情的前提下耗费手机的资源。很多Service被应用在后台偷偷拉起,这样就躲避了AMS的一些检查机制,比如说,不会被当作空进程与缓存进程杀死。通过这类保活手段,虽然可以延长应用的寿命,但是却使手机电池寿命与内存占用恶化。

扫码支持0.99元,您的支持将鼓励我继续创作!