从源码角度看 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]

  • 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]

  • 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]

  • 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代替广播接收事件 部分场景下,推荐

发送端实例代码

  • <permission
  • android:name="top.navyblue.bugsfree.permission"
  • android:protectionLevel="signature"/>
  • <uses-permission android:name="top.navyblue.bugsfree.permission" />
  • 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时加入这个权限

接收端实例代码

  • <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]

  • 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]

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

}

top Created with Sketch.