背景
一般来说,我们都是用的监听android.intent.action.BOOT_COMPLETED。
但凡稍有些经验的开发者都知道,这个广播很慢,非常慢。因为它是一个有序广播,根据优先级来的,而且监听这个广播的apk又非常多。打个log感受一下,这个广播开始到结束在我司的机器上持续了30s!
关键是你把优先级调高了,即便你是前几个收到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。
我们的目的是让我们的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()方法里:
-
mUserController.sendBootCompletedLocked(
-
new IIntentReceiver.Stub() {
-
@Override
-
public void performReceive(Intent intent, int resultCode,
-
String data, Bundle extras, boolean ordered,
-
boolean sticky, int sendingUser) {
-
synchronized (ActivityManagerService.this) {
-
requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-
true, false);
-
}
-
}
-
});
-
scheduleStartProfilesLocked();
-
//potter add
-
Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");
-
customIntent.setPackage("com.honeywell.ezreceiver");
-
mContext.sendBroadcast(customIntent);
-
//potter end
有人可能会问,这样只有一个apk能收到,如何让所有apk都收到。按下面这么写即可。
-
//potter add
-
Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");
-
customIntent. addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-
mContext.sendBroadcast(customIntent);
-
//potter end
可能又有人会问,这样apk收到的顺序没法控制,那么按下面这么写:
-
//potter add
-
Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");
-
customIntent. addFlags(Intent.FLAG_RECEIVER_NO_ABORT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-
mContext. sendOrderedBroadcast(intent, null);
-
//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方法里的
-
if (isCallerSystem) {
-
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
-
isProtectedBroadcast, receivers);
-
}
会报log
Sending non-protected broadcast…
虽然广播正常发送了,并没有影响到广播的收发,但是从设计上来说这么写不安全的。
要想消除掉这个log,可以去checkBroadcastFromSystem里面搞特殊,类似
-
if (isProtectedBroadcast
-
|| Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
-
|| Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
-
....
-
|| AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
-
|| "com.honeywell.intent.action.BOOT_COMPLETED".equals(action)) {
-
// Broadcast is either protected, or it's a public action that
-
// we've relaxed, so it's fine for system internals to send.
-
return;
-
}
也可以sendBroadcast的时候加上权限,收广播的地方同样加上权限即可。
报这个log具体有哪些影响和怎么广播加权限,这里就不赘述了。
Android P自定义开机广播
frameworks\base\services\core\java\com\android\server\am\UserController.java
finishUserUnlockedCompleted方法里:
-
//potter add
-
final Intent bootIntent1 = new Intent("com.honeywell.intent.action.BOOT_COMPLETED", null);
-
bootIntent1.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-
bootIntent1.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-
mInjector.broadcastIntent(bootIntent1, null, null, 0, null, null, null,AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
-
//potter end
-
final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-
bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-
bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-
mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {
-
@Override
-
public void performReceive(Intent intent, int resultCode, String data,
-
Bundle extras, boolean ordered, boolean sticky, int sendingUser)
-
throws RemoteException {
-
Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
-
}
-
}, 0, null, null,
-
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-
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()方法里:
-
mUserController.sendBootCompleted(
-
new IIntentReceiver.Stub() {
-
@Override
-
public void performReceive(Intent intent, int resultCode,
-
String data, Bundle extras, boolean ordered,
-
boolean sticky, int sendingUser) {
-
synchronized (ActivityManagerService.this) {
-
requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
-
}
-
}
-
});
-
mUserController.scheduleStartProfiles();
-
//potter add
-
Log.e("potter","amspotter begin----1111");
-
Intent customIntent=new Intent("com.honeywell.intent.action.BOOT_COMPLETED");
-
customIntent.setPackage("com.honeywell.ezreceiver");
-
mContext.sendBroadcastAsUser(customIntent,UserHandle.OWNER);
-
Log.e("potter","amspotter end----1111");
-
mHandler.postDelayed(new Runnable() {
-
@Override
-
public void run() {
-
Log.e("potter","amspotter begin----2222");
-
mContext.sendBroadcastAsUser(customIntent,UserHandle.OWNER);
-
Log.e("potter","amspotter end----2222");
-
}
-
},5000);
-
//potter end
这里,我们发两次广播,一个是立即发送,一个是延迟5秒发送。
frameworks\base\services\core\java\com\android\server\am\UserController.java
finishUserUnlockedCompleted方法里:
-
//potter add
-
Log.e("potter","amspotter begin----3333");
-
final Intent bootIntent1 = new Intent("com.honeywell.intent.action.BOOT_COMPLETED", null);
-
bootIntent1.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-
bootIntent1.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-
mInjector.broadcastIntent(bootIntent1, null, null, 0, null, null, null,AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
-
Log.e("potter","amspotter end----3333");
-
//potter end
-
final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-
bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-
bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-
mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {
-
@Override
-
public void performReceive(Intent intent, int resultCode, String data,
-
Bundle extras, boolean ordered, boolean sticky, int sendingUser)
-
throws RemoteException {
-
Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
-
}
-
}, 0, null, null,
-
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-
AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
-
//potter add
-
Log.e("potter","amspotter begin----4444");
-
final Intent bootIntent2 = new Intent("com.honeywell.intent.action.BOOT_COMPLETED", null);
-
bootIntent2.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-
bootIntent2.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-
mInjector.broadcastIntent(bootIntent2, null, null, 0, null, null, null,AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
-
Log.e("potter","amspotter end----4444");
-
//potter end
android.intent.action.BOOT_COMPLETED前后各发一个广播。
frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
broadcastIntentLocked (…)方法里:
这里的逻辑是筛选出哪些Receiver是接受对应的广播的
-
// Figure out who all will receive this broadcast.
-
List receivers = null;
-
List<BroadcastFilter> registeredReceivers = null;
-
// Need to resolve the intent to interested receivers...
-
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
-
== 0) {
-
receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
-
//potter add
-
if(receivers==null){
-
Log.e("potter","receivers==null");
-
}else{
-
Log.e("potter","receivers.size():"+receivers.size());
-
}
-
//potter end
-
}
frameworks\base\services\core\java\com\android\server\am\ActivityManagerDebugConfig.java
-
static final boolean DEBUG_BROADCAST = DEBUG_ALL || false;
-
static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;
-
static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
-
改成
-
static final boolean DEBUG_BROADCAST = DEBUG_ALL || true;
-
static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || true;
-
static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || true;
打开log。
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
-
public static final boolean DEBUG_PACKAGE_SCANNING = false;
-
改成
-
public static final boolean DEBUG_PACKAGE_SCANNING = true;
打开log。
我的apk
-
<receiver
-
android:name=".MyReceiver"
-
android:exported="true">
-
<intent-filter>
-
<action android:name="com.honeywell.intent.action.BOOT_COMPLETED"/>
-
<action android:name="android.intent.action.BOOT_COMPLETED"/>
-
</intent-filter>
-
</receiver>
-
public void onReceive(Context context, Intent intent) {
-
LogUtils.e("potter", "MyReceiver action:" + intent.getAction());
-
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED") ||
-
intent.getAction().equals("com.honeywell.intent.action.BOOT_COMPLETED")) {
-
context.startService(new Intent(context, MyService.class));
-
}
-
}
Ok,准备完毕。测试的log如下:
从上述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如下:
时序如下:
开始开机动画>>>开始扫描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.intent.action.MEDIA_MOUNTED.原因是我司产品还新增了一个IPSM分区。
大家关注/storage/emulated/0的挂载广播即可。
广播接受的时序图:
总结
总而言之,针对我司的设备。
目前来看,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要快。