技术标签: MEDIA_MOUNTED 自定义开机广播 Android P BOOT_COMPLETED Android
一般来说,我们都是用的监听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上面踩到了。后面会详细说。
先直接上可行的结论:
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具体有哪些影响和怎么广播加权限,这里就不赘述了。
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)后处理自定义广播。这样自定义广播就没有意义了。
这个原因其实一开始就提到了。如果按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要快。
文章浏览阅读3.7k次,点赞3次,收藏17次。说明本文主要使用python编程,实现解析wav语音文件,得到.wav语音文件的声道数,量化位数,采样频率,采样点数。编写python程序使用pycharm。下面简单介绍一下wav文件结构。一 解析.wav文件原理WAVE文件本质上就是一种RIFF格式,它可以抽象成一颗树(数据结构的一种)来看。 如图所示,从上到下分别对应着二进制数据在文件中相对于起始位置的偏移量。每一个格子对应一..._python查看.wav编码格式
文章浏览阅读65次。http://people.csail.mit.edu/jacobe/software.html转载于:https://www.cnblogs.com/stoneresearch/archive/2011/09/03/4336539.html_dpmm饭圈
文章浏览阅读1.1w次。cp操作请跳到第10节1.open内建打开文件函数作为打开文件的“钥匙”,内建函数open()提供了初始化输入/输出(I/O)操作的通用结接口成功打开文件后会返回一个文件对象,如果打开失败会引发一个错误示例:file_object = open(file_name,mode='r')file_object 是python的变量名用来保存文件对象mode是打开的方式,以读..._python读取linux文件
文章浏览阅读208次。1. 引言在无需可信第三方的情况下,实现不可预测的伪随机数的方案有:VRFVDFCommit-reveal当前,Celo项目采用的是简单的commit-reveal方案来随机选择Validator.对于某特定Validator propose的第nnn个区块,该Validator会在该区块内附加值(rn,sn)(r_n,s_n)(rn,sn),使得keccak256(rn)=sn−1\text{keccak256}(r_n)=s_{n-1}keccak256(rn)=sn−1。对于_uint(keccak256(abi.encodepacked(blockhash(block.number - 1), gasleft())))
文章浏览阅读1.4k次。在网页中,对于已经开启定位的元素可以设置元素层级,如果定位的元素层级是一样的, 下边的元素会盖住上边,在定位的情况下,通过z-index属性设置元素的层级,属性值:是一个正整数,层级越高,优先级也就越高 .box{ width: 200px; height: 200px; } .box1{ background-color: rebeccapurple; position:relative; _html怎么调层级
文章浏览阅读2.8k次。在自然语言处理中,词性标注、命名实体识别、句法分析、语义理解、语音合成、信息检索、文档摘要等功能需要对输入文本进行分析处理。这些任务通常都涉及到大量的数据处理工作。例如,给定一个文本序列(如一段话或一篇文章),如何自动地确定其中的名词短语、动词短语、介词短语、形容词短语?这个过程被称之为词性标注。再比如,给定一段文本,如何识别出其中的人物、组织机构、地点、时间、日期、货币金额等实体?这个过程被称之为命名实体识别。每当我们阅读、回复、输入文字时,都离不开这些功能,它们的背后都是复杂的计算过程。_简易自然语言处理标签化
文章浏览阅读2.9k次,点赞5次,收藏32次。Python将txt文本文件写入Mysql数据库import pymysqlimport re#数据库连接con=pymysql.connect( host='localhost', port=3306, user='root', passwd='123456', db='case_data', charset='utf8', )def insert(con,case_ID,case_process): cue = con.cur_python将txt写入mysql的表中
文章浏览阅读5k次,点赞24次,收藏68次。广度优先搜索(Breadth-First Search,BFS)是一种图遍历算法,用于系统地遍历或搜索图(或树)中的所有节点。BFS的核心思想是从起始节点开始,首先访问其所有相邻节点,然后逐层向外扩展,逐一访问相邻节点的相邻节点,以此类推。这意味着BFS会优先探索距离起始节点最近的节点,然后再逐渐扩展到距离更远的节点。BFS通常用于查找最短路径、解决迷宫问题、检测图是否连通以及广泛的图问题。BFS算法的步骤如下:初始化:选择一个起始节点,将其标记为已访问,并将其放入队列中(作为起始节点)。_python bfs
文章浏览阅读113次。Python的序列化是指把变量从内存中变为可以储存/传输的数据/文件的过程. 在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。在Pytho..._cpickle.dump(obj, f, protocol)
文章浏览阅读3.1k次。static<T>Stream<T>generate(Supplier<? extends T>s)该方法主要用于生成一个无限连续的无序流,流中的元素由用户定义的supplier函数生成。看个简单例子:运行结果:aa该方法一般配合limit方法使用,在这个例子中我们使用limit方法限制这个无限流的长度为2,因此最终打印出两个a。加入我们不使用limit方法限制流长度会出现什么情况?测试一下。Stream<String> ..._stream.generate
文章浏览阅读326次。安装使用个人推荐方案(自定义按键)先ctrl+a,再输入命令 - u/i : 创建水平/竖直新窗口 - o : tab - h/j/k/l : 更改当前窗口大小_tmux的分窗口分不了
文章浏览阅读4.8k次。温故而知新。_json格式转换