Android O Android P 自定义开机广播

时间:2024-04-09 20:57:13

背景

一般来说,我们都是用的监听android.intent.action.BOOT_COMPLETED。

但凡稍有些经验的开发者都知道,这个广播很慢,非常慢。因为它是一个有序广播,根据优先级来的,而且监听这个广播的apk又非常多。打个log感受一下,这个广播开始到结束在我司的机器上持续了30s!

Android O Android P 自定义开机广播

关键是你把优先级调高了,即便你是前几个收到android.intent.action.BOOT_COMPLETED的,从开机动画走完,锁屏界面跳出来 到你的apk收到这个广播,大概还是有个5s左右的时间。

原因是android.intent.action.BOOT_COMPLETED发出来的时候就不是最早的。它前面还有

android.intent.action.LOCKED_BOOT_COMPLETED,android.intent.action.MEDIA_MOUNTED等。

要等这几个广播处理完了,才轮到android.intent.action.BOOT_COMPLETED。

Android O Android P 自定义开机广播

 

我们的目的是让我们的apk尽早起来(一般都是收了广播,然后receiver的onReceive去start我们的service)。

那么我们有以下几种处理办法:

1.接收android.intent.action.BOOT_COMPLETED,调高优先级。

2.接收android.intent.action.MEDIA_MOUNTED,因为这个广播一方面比android.intent.action.BOOT_COMPLETED发送的要早,一方面监听它的apk比较少。

3.直接在AMS里面去start我们的service,过于粗暴且不通用。

4.自定义开机广播,这样监听这个广播的apk只有我们自己,并且尽早的发出这个广播。

 

我们要考虑的问题:

1.如果监听android.intent.action.MEDIA_MOUNTED,这个广播是挂载存储的时候发的,开机的时候挂载到手机的存储区(storage/emulated/0)会发一下,后面如果插入sdcard也会发。要评估多次触发广播是否会对自己的apk业务逻辑造成影响(以免自己本来stop了的service又被叫起来)。

2.自定义开机广播,这个广播发出来的时机。需要考虑system是否Ready,这个一般都达到了,大家写也会注意。还有点需要考虑,apk的AndroidManifest.xml的组件信息是何时导入的,如果广播发出来的时候,我们的apk的receiver信息还没被PackageManagerService导入,一样是收不到的,这个坑我自己在Android P上面踩到了。后面会详细说。

 

先直接上可行的结论:

Android O自定义开机广播

frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

finishBooting()方法里:

 
  1. mUserController.sendBootCompletedLocked(

  2. new IIntentReceiver.Stub() {

  3. @Override

  4. public void performReceive(Intent intent, int resultCode,

  5. String data, Bundle extras, boolean ordered,

  6. boolean sticky, int sendingUser) {

  7. synchronized (ActivityManagerService.this) {

  8. requestPssAllProcsLocked(SystemClock.uptimeMillis(),

  9. true, false);

  10. }

  11. }

  12. });

  13. scheduleStartProfilesLocked();

  14. //potter add

  15. Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");

  16. customIntent.setPackage("com.honeywell.ezreceiver");

  17. mContext.sendBroadcast(customIntent);

  18. //potter end

有人可能会问,这样只有一个apk能收到,如何让所有apk都收到。按下面这么写即可。

 
  1. //potter add

  2. Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");

  3. customIntent. addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

  4. mContext.sendBroadcast(customIntent);

  5. //potter end

可能又有人会问,这样apk收到的顺序没法控制,那么按下面这么写:

 
  1. //potter add

  2. Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");

  3. customIntent. addFlags(Intent.FLAG_RECEIVER_NO_ABORT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

  4. mContext. sendOrderedBroadcast(intent, null);

  5. //potter end

我们多给加上一个Intent.FLAG_RECEIVER_NO_ABORT的Flag,让这个有序广播不能被abort掉。实际上系统里面的android.intent.action.BOOT_COMPLETED这个广播就是这两个flag。

这么写,功能已经实现了。但是从设计的角度很不好。因为这么写作为一个在System发出去的广播并没有加权限,这是不符合android的规范的。

我们追下源码:

sendBroadcast>>>

ContextImpl.java的sendBroadcast>>>

ActivityManagerService.java的broadcastIntent>>>

ActivityManagerService.java的broadcastIntentLocked

在AMS的broadcastIntentLocked方法里的

 
  1. if (isCallerSystem) {

  2. checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,

  3. isProtectedBroadcast, receivers);

  4. }

会报log

Sending non-protected broadcast…

虽然广播正常发送了,并没有影响到广播的收发,但是从设计上来说这么写不安全的。

要想消除掉这个log,可以去checkBroadcastFromSystem里面搞特殊,类似

 
  1. if (isProtectedBroadcast

  2. || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)

  3. || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)

  4. ....

  5. || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)

  6. || "com.honeywell.intent.action.BOOT_COMPLETED".equals(action)) {

  7. // Broadcast is either protected, or it's a public action that

  8. // we've relaxed, so it's fine for system internals to send.

  9. return;

  10. }

也可以sendBroadcast的时候加上权限,收广播的地方同样加上权限即可。

报这个log具体有哪些影响和怎么广播加权限,这里就不赘述了。

Android P自定义开机广播

frameworks\base\services\core\java\com\android\server\am\UserController.java

finishUserUnlockedCompleted方法里:

 
  1. //potter add

  2. final Intent bootIntent1 = new Intent("com.honeywell.intent.action.BOOT_COMPLETED", null);

  3. bootIntent1.putExtra(Intent.EXTRA_USER_HANDLE, userId);

  4. bootIntent1.addFlags(Intent.FLAG_RECEIVER_NO_ABORT

  5. | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

  6. mInjector.broadcastIntent(bootIntent1, null, null, 0, null, null, null,AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);

  7. //potter end

  8. final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);

  9. bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);

  10. bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT

  11. | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

  12. mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {

  13. @Override

  14. public void performReceive(Intent intent, int resultCode, String data,

  15. Bundle extras, boolean ordered, boolean sticky, int sendingUser)

  16. throws RemoteException {

  17. Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);

  18. }

  19. }, 0, null, null,

  20. new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},

  21. AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);

这里一定要放在android.intent.action.BOOT_COMPLETED之前发,如果放在它之后,

虽然发送广播的时间是差了很短的时间,实测不到100ms。

但是由于处理的时候是按队列来的,也就是android.intent.action.BOOT_COMPLETED处理完(至少10s以上,我司设备长达30s)后处理自定义广播。这样自定义广播就没有意义了。

 

P和O处理方式不同的原因

这个原因其实一开始就提到了。如果按O的处理方法,发广播的时机太早了,这时候所有apk的AndroidManifest.xml的组件信息还没有在PackageManagerService里面导入完毕。

导入AndroidManifest.xml是在PackageManagerService的scanDirLI方法里面做的。

P上面按O的代码来测试:

frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

finishBooting()方法里:

 
  1. mUserController.sendBootCompleted(

  2. new IIntentReceiver.Stub() {

  3. @Override

  4. public void performReceive(Intent intent, int resultCode,

  5. String data, Bundle extras, boolean ordered,

  6. boolean sticky, int sendingUser) {

  7. synchronized (ActivityManagerService.this) {

  8. requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);

  9. }

  10. }

  11. });

  12. mUserController.scheduleStartProfiles();

  13. //potter add

  14. Log.e("potter","amspotter begin----1111");

  15. Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");

  16. customIntent.setPackage("com.honeywell.ezreceiver");

  17. mContext.sendBroadcastAsUser(customIntent,UserHandle.OWNER);

  18. Log.e("potter","amspotter end----1111");

  19. mHandler.postDelayed(new Runnable() {

  20. @Override

  21. public void run() {

  22. Log.e("potter","amspotter begin----2222");

  23. mContext.sendBroadcastAsUser(customIntent,UserHandle.OWNER);

  24. Log.e("potter","amspotter end----2222");

  25. }

  26. },5000);

  27. //potter end

这里,我们发两次广播,一个是立即发送,一个是延迟5秒发送。

frameworks\base\services\core\java\com\android\server\am\UserController.java

finishUserUnlockedCompleted方法里:

 
  1. //potter add

  2. Log.e("potter","amspotter begin----3333");

  3. final Intent bootIntent1 = new Intent("com.honeywell.intent.action.BOOT_COMPLETED", null);

  4. bootIntent1.putExtra(Intent.EXTRA_USER_HANDLE, userId);

  5. bootIntent1.addFlags(Intent.FLAG_RECEIVER_NO_ABORT

  6. | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

  7. mInjector.broadcastIntent(bootIntent1, null, null, 0, null, null, null,AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);

  8. Log.e("potter","amspotter end----3333");

  9. //potter end

  10. final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);

  11. bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);

  12. bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT

  13. | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

  14. mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {

  15. @Override

  16. public void performReceive(Intent intent, int resultCode, String data,

  17. Bundle extras, boolean ordered, boolean sticky, int sendingUser)

  18. throws RemoteException {

  19. Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);

  20. }

  21. }, 0, null, null,

  22. new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},

  23. AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);

  24. //potter add

  25. Log.e("potter","amspotter begin----4444");

  26. final Intent bootIntent2 = new Intent("com.honeywell.intent.action.BOOT_COMPLETED", null);

  27. bootIntent2.putExtra(Intent.EXTRA_USER_HANDLE, userId);

  28. bootIntent2.addFlags(Intent.FLAG_RECEIVER_NO_ABORT

  29. | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

  30. mInjector.broadcastIntent(bootIntent2, null, null, 0, null, null, null,AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);

  31. Log.e("potter","amspotter end----4444");

  32. //potter end

android.intent.action.BOOT_COMPLETED前后各发一个广播。

frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

broadcastIntentLocked (…)方法里:

这里的逻辑是筛选出哪些Receiver是接受对应的广播的

 
  1. // Figure out who all will receive this broadcast.

  2. List receivers = null;

  3. List<BroadcastFilter> registeredReceivers = null;

  4. // Need to resolve the intent to interested receivers...

  5. if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)

  6. == 0) {

  7. receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);

  8. //potter add

  9. if(receivers==null){

  10. Log.e("potter","receivers==null");

  11. }else{

  12. Log.e("potter","receivers.size():"+receivers.size());

  13. }

  14. //potter end

  15. }

frameworks\base\services\core\java\com\android\server\am\ActivityManagerDebugConfig.java

 
  1. static final boolean DEBUG_BROADCAST = DEBUG_ALL || false;

  2. static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;

  3. static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;

  4. 改成

  5. static final boolean DEBUG_BROADCAST = DEBUG_ALL || true;

  6. static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || true;

  7. static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || true;

打开log。

frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java

 
  1. public static final boolean DEBUG_PACKAGE_SCANNING = false;

  2. 改成

  3. public static final boolean DEBUG_PACKAGE_SCANNING = true;

打开log。

我的apk

 
  1. <receiver

  2. android:name=".MyReceiver"

  3. android:exported="true">

  4. <intent-filter>

  5. <action android:name="com.honeywell.intent.action.BOOT_COMPLETED"/>

  6. <action android:name="android.intent.action.BOOT_COMPLETED"/>

  7. </intent-filter>

  8. </receiver>

 
  1. public void onReceive(Context context, Intent intent) {

  2. LogUtils.e("potter", "MyReceiver action:" + intent.getAction());

  3. if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED") ||

  4. intent.getAction().equals("com.honeywell.intent.action.BOOT_COMPLETED")) {

  5. context.startService(new Intent(context, MyService.class));

  6. }

  7. }

Ok,准备完毕。测试的log如下:

Android O Android P 自定义开机广播

从上述log可以分析出:

1.发了4次广播,只收到了3次。广播1发的太早,此时我们的apk的AndroidManifest.xml还没有导入完毕,导致筛选符合条件的receivers时,得到的结果是null。

2.广播3,广播4和比广播1晚5s发送的广播2,此时我们的apk的AndroidManifest.xml已经导入完毕,所以receivers得到的结果是正确的,size是1,对应我们的apk。

3.最后按时间顺序收到的广播次序是

14:55:29.430      广播3

14:55:49.005      android.intent.action.BOOT_COMPLETED

14:56:00.200      广播4

14:56:00.225      广播2

可以看到广播4,和广播2由于发送的时候是在android.intent.action.BOOT_COMPLETED之后,所以由于队列的关系,推迟了很久才收到。

所以最佳的方案来看就是使用广播3,也是前面提到的solution。

总的log如下:

Android O Android P 自定义开机广播

时序如下:

开始开机动画>>>开始扫描apk,导入AndroidManifest.xml

>>>开机动画完毕>>>走入Ams的finishBooting>>>过一会才扫描apk结束

>>>这时候发广播才能保证是百分百ok的

实测的结果就是如此,这里让人疑惑的是为什么finishBooting后竟然才所有的apk扫描结束,可以推论出PMS的扫描和bootanimation是异步的,有可能由于我们预装的apk比较多,才导致了开机动画结束后依然还没有scan结束。

这就很尴尬,因为即使我们找准代码位置,是在PMS 扫描完所有apk后去发自定义开机广播,或者直接去startService。但是这时候距离开机动画结束已经有一段时间了,上面打log是1s左右。这样还是不是百分百完美,假设需求是开机就启动apk去禁止下拉状态栏,这样的话客户操作快的话,还是有可能在开机动画刚结束,就把statusbar拉了下来。

 

最后,之所以14:55:25发的广播,为什么14:55:29才收到。是因为25秒发广播的时候,广播队列里面已经有其它的广播了,而其它广播有的由于监听的apk比较多,耗时比较久。比较值得关注的是

android.intent.action.LOCKED_BOOT_COMPLETED和

android.intent.action.MEDIA_MOUNTED。

看下面的图就一目了然了。

广播发送的时序图:

Android O Android P 自定义开机广播

 这里可以看到,我司其实发了两次android.intent.action.MEDIA_MOUNTED.原因是我司产品还新增了一个IPSM分区。

大家关注/storage/emulated/0的挂载广播即可。

广播接受的时序图:

Android O Android P 自定义开机广播

总结

总而言之,针对我司的设备。

目前来看,Android O上自定义开机广播适用于AMS里面发广播。

Android P上自定义广播适用于UserController里面发广播,只要做到刚好在android.intent.action.BOOT_COMPLETED之前即可。

Android O上我暂时就没去加log一步步追为什么AMS里面发广播没有P上面碰到的问题了,有可能是O上面预置的apk少,所有PMS scan所有的apk比较快,在finishBooting前就scan 结束了,也可能是Android O和Android P开机流程上在这块就有些许差异。

最后,可能有人会问,为什么不直接去监听android.intent.action.MEDIA_MOUNTED,

其实这也是个办法,可以看到,android.intent.action.MEDIA_MOUNTED这个广播发出来的时候是14:55:24.967,而PMS scan apk在14:55:24.920之前都没有结束scan apk。这两个时间点距离太接近了,考虑到后续OS可能还要再预制apk,到时候PMS 结束scan apk的时间节点还会往后推。其实监听android.intent.action.MEDIA_MOUNTED是可能有风险的,和上面的广播1一样。极端情况下可能receivers == null.

所以还是折衷用的自定义广播,这样稍微晚一点点也能接受。至少百分之百比android.intent.action.BOOT_COMPLETED要快。