Android O Android P 自定义开机广播_flag_receiver_include_background-程序员宅基地

技术标签: 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_COMPLETEDandroid.intent.action.MEDIA_MOUNTED等。

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

 

我们的目的是让我们的apk尽早起来(一般都是收了广播,然后receiveronReceivestart我们的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,这个一般都达到了,大家写也会注意。还有点需要考虑,apkAndroidManifest.xml的组件信息是何时导入的,如果广播发出来的时候,我们的apkreceiver信息还没被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_ABORTFlag让这个有序广播不能被abort掉。实际上系统里面的android.intent.action.BOOT_COMPLETED这个广播就是这两个flag

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

我们追下源码:

sendBroadcast>>>

ContextImpl.javasendBroadcast>>>

ActivityManagerService.javabroadcastIntent>>>

ActivityManagerService.javabroadcastIntentLocked

AMSbroadcastIntentLocked方法里的

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的处理方法,发广播的时机太早了,这时候所有apkAndroidManifest.xml的组件信息还没有在PackageManagerService里面导入完毕。

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

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发的太早,此时我们的apkAndroidManifest.xml还没有导入完毕,导致筛选符合条件的receivers时,得到的结果是null

2.广播3,广播4和比广播15s发送的广播2,此时我们的apkAndroidManifest.xml已经导入完毕,所以receivers得到的结果是正确的,size1,对应我们的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

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

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

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

这就很尴尬,因为即使我们找准代码位置,是在PMS 扫描完所有apk后去发自定义开机广播,或者直接去startService。但是这时候距离开机动画结束已经有一段时间了,上面打log1s左右。这样还是不是百分百完美,假设需求是开机就启动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 OAndroid P开机流程上在这块就有些许差异。

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

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

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

 

 

 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Mis_wenwen/article/details/95240346

智能推荐

python解析wav语音文件_python查看.wav编码格式-程序员宅基地

文章浏览阅读3.7k次,点赞3次,收藏17次。说明本文主要使用python编程,实现解析wav语音文件,得到.wav语音文件的声道数,量化位数,采样频率,采样点数。编写python程序使用pycharm。下面简单介绍一下wav文件结构。一 解析.wav文件原理WAVE文件本质上就是一种RIFF格式,它可以抽象成一颗树(数据结构的一种)来看。​ 如图所示,从上到下分别对应着二进制数据在文件中相对于起始位置的偏移量。每一个格子对应一..._python查看.wav编码格式

DP mixture model-程序员宅基地

文章浏览阅读65次。http://people.csail.mit.edu/jacobe/software.html转载于:https://www.cnblogs.com/stoneresearch/archive/2011/09/03/4336539.html_dpmm饭圈

Python对Linux系统的操作,打开文件与读写文件,python模拟linux的cp操作_python读取linux文件-程序员宅基地

文章浏览阅读1.1w次。cp操作请跳到第10节1.open内建打开文件函数作为打开文件的“钥匙”,内建函数open()提供了初始化输入/输出(I/O)操作的通用结接口成功打开文件后会返回一个文件对象,如果打开失败会引发一个错误示例:file_object = open(file_name,mode='r')file_object 是python的变量名用来保存文件对象mode是打开的方式,以读..._python读取linux文件

Celo中的随机数_uint(keccak256(abi.encodepacked(blockhash(block.nu-程序员宅基地

文章浏览阅读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())))

HTML的层级_html怎么调层级-程序员宅基地

文章浏览阅读1.4k次。在网页中,对于已经开启定位的元素可以设置元素层级,如果定位的元素层级是一样的, 下边的元素会盖住上边,在定位的情况下,通过z-index属性设置元素的层级,属性值:是一个正整数,层级越高,优先级也就越高 .box{ width: 200px; height: 200px; } .box1{ background-color: rebeccapurple; position:relative; _html怎么调层级

数据标签化:如何通过标签化数据进行文本分类和自然语言处理自然语言处理教程_简易自然语言处理标签化-程序员宅基地

文章浏览阅读2.8k次。在自然语言处理中,词性标注、命名实体识别、句法分析、语义理解、语音合成、信息检索、文档摘要等功能需要对输入文本进行分析处理。这些任务通常都涉及到大量的数据处理工作。例如,给定一个文本序列(如一段话或一篇文章),如何自动地确定其中的名词短语、动词短语、介词短语、形容词短语?这个过程被称之为词性标注。再比如,给定一段文本,如何识别出其中的人物、组织机构、地点、时间、日期、货币金额等实体?这个过程被称之为命名实体识别。每当我们阅读、回复、输入文字时,都离不开这些功能,它们的背后都是复杂的计算过程。_简易自然语言处理标签化

随便推点

Python将txt文本文件写入Mysql数据库_python将txt写入mysql的表中-程序员宅基地

文章浏览阅读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的表中

【Python搜索算法】广度优先搜索(BFS)算法原理详解与应用,示例+代码_python bfs-程序员宅基地

文章浏览阅读5k次,点赞24次,收藏68次。广度优先搜索(Breadth-First Search,BFS)是一种图遍历算法,用于系统地遍历或搜索图(或树)中的所有节点。BFS的核心思想是从起始节点开始,首先访问其所有相邻节点,然后逐层向外扩展,逐一访问相邻节点的相邻节点,以此类推。这意味着BFS会优先探索距离起始节点最近的节点,然后再逐渐扩展到距离更远的节点。BFS通常用于查找最短路径、解决迷宫问题、检测图是否连通以及广泛的图问题。BFS算法的步骤如下:初始化:选择一个起始节点,将其标记为已访问,并将其放入队列中(作为起始节点)。_python bfs

cpickle支持的python版本_Python序列化模块pickle和cPickle-程序员宅基地

文章浏览阅读113次。Python的序列化是指把变量从内存中变为可以储存/传输的数据/文件的过程. 在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。在Pytho..._cpickle.dump(obj, f, protocol)

Stream.generate-程序员宅基地

文章浏览阅读3.1k次。static<T>Stream<T>generate​(Supplier<? extends T>s)该方法主要用于生成一个无限连续的无序流,流中的元素由用户定义的supplier函数生成。看个简单例子:运行结果:aa该方法一般配合limit方法使用,在这个例子中我们使用limit方法限制这个无限流的长度为2,因此最终打印出两个a。加入我们不使用limit方法限制流长度会出现什么情况?测试一下。Stream<String> ..._stream.generate

tmux分屏解决方案_tmux的分窗口分不了-程序员宅基地

文章浏览阅读326次。安装使用个人推荐方案(自定义按键)先ctrl+a,再输入命令 - u/i : 创建水平/竖直新窗口 - o : tab - h/j/k/l : 更改当前窗口大小_tmux的分窗口分不了

json文件的格式转换_json格式转换-程序员宅基地

文章浏览阅读4.8k次。温故而知新。_json格式转换

推荐文章

热门文章

相关标签