Fork me on GitHub

Android广播的顺序

为了利于各个进程应用之间的通信,android提供了一个很方便的处理方式:广播机制。广播机制顾名思义,就是广播发送者无需判断具体某个接收者的存在,把广播发送出去,任务即完成。这样保证了有效通信的同时又最大限度的降低了android系统各个模块的耦合性。

Android广播有两个很重要的要素:

  • 1 广播 - 用于发送广播

    有序广播  -  被广播接收器接收后,可被终止,无法往下继续传达。         典型代表:短信广播
    
    普通广播  -  发送至每一个已经注册(订阅)的广播接收器,无法被终止。 典型代表:开机启动广播
    
  • 2 广播接收器 - 用于订阅广播后接收广播

    静态注册广播 - 在AndroidManifest.xml中设置,程序不用启动亦可接收。 典型代表:很多开机启动的APP,都是接收开机启动广播带起服务的。
    
    动态注册广播 - 代码中注册广播,程序未启动时,无法接收广播。
    

有序广播的优先级:

  • 1)动态注册优先级别最高,其次静态注册
  • 2)在动态注册中,最早动态注册优先级别最高
  • 3)在静态注册中,最早安装的程序,静态注册优先级别最高(注:安装APK会解析manifest.xml,把其加入队列)
  • 4)都是静态注册的情况下,按照字符排列顺序决定谁先收到广播消息
  • 5)adb install xxx.apk安装的应用优先级比adb push到其他目录的应用高
  • 总体来说:对于接收同一个广播,在相同优先级的情况下,动态注册优先级别高于静态注册。在动态注册中,最早动态注册优先级别最高;在静态注册中,最早安装的程序,静态注册优先级别最高(安装APK会解析manifest.xml,把其加入队列)

我们以如何抢先开机启动为例,来说明接收无序广播的静态广播接收器的接收顺序。首先我们要明确两个问题:

  • A,接收无序广播的接收器接收到广播的顺序是有序的,
  • B,接收无序广播的接收器也一样可以设置优先级的。

    我们以开机时候发出的广播android.intent.action.BOOT_COMPLETED为例,这是个无序广播。如果应用想要开启自启动,那么就要监听这个广播,程序启动之前,动态广播接收器肯定是无法使用的,只能在XML中静态注册。大家都知道,第三方应用是存放在/data/app目录下,当安装完毕之后,你会找到一个文件,他的名字是以与安装的应用包名开始的,然后可能会跟着"-数字.apk",比如:com.android.test-1.apk。接收的顺序与这个名字是有关的!那么关系是怎样的呢?
    

(1)系统在开机的时候,会按着一个顺序解析apk
1,首先,会解析手机中的/system/framework这个目录,原生系统中,这下面就一个apk - framework-res.apk,当然各个厂商也会加入自己的内容
2,然后受到重视的文件夹按顺序分别为:
/system/app
/vendor/app
/data/app
/drm/app-private

那么每个文件夹下解析的顺序是怎样的呢?我们先只看/data/app,也就是用户安装的第三方应用的存放位置,这与下面代码返回结果的顺序是一致的

1
2
File file = new File("/data/app/");
String[] files = file.list();

也就是说,我们按顺序打印这个数组,就能知道哪个接收器会先接收到这个广播,哪个会后接收到

(2)网上的CODE DEMO

做了一个实验,我写了几个只有receiver的应用,把他们的包名分别设置为大家常用的、关系的应用包名

飞信:cn.com.fetion
LBE隐私卫士:com.lbe.security.lite
Handsent:com.handsent.nextsms
金山手机卫士:com.ijinshan.mguard
360手机卫士:com.qihoo360.mobilesafe
QQ手机管家:com.tencent.qqpimsecure
一个测试应用:com.example.boottest

1
2
3
4
5
File file = new File("/data/app/");
String[] files = file.list();
for (int i = 0; i < files.length; i++){
System.out.println("/data/app/:files["+(i+1)+"]:" + files[i]); //在打印语句中(i+1)的值可以自动转成字符
}

结果为:
/data/app/:files[8]:com.tencent.qqpimsecure-1.apk
/data/app/:files[9]:com.qihoo360.mobilesafe-1.apk
/data/app/:files[10]:com.ijinshan.mguard-1.apk
/data/app/:files[11]:cn.com.fetion-1.apk
/data/app/:files[12]:com.lbe.security.lite-1.apk
/data/app/:files[13]:com.handsent.nextsms-1.apk
/data/app/:files[14]:com.example.boottest-1.apk

如果其中一个优先级较高,比如cn.com.fetion,那么实际的接收顺序为
getPackageName:cn.com.fetion
getPackageName:com.tencent.qqpimsecure
getPackageName:com.qihoo360.mobilesafe
getPackageName:com.ijinshan.mguard
getPackageName:com.lbe.security.lite
getPackageName:com.handsent.nextsms
getPackageName:com.example.boottest
所以如果同优先级的静态接收器想先接收某个广播,就要在包名上修改已让它靠前显示。

(3)自己的CODE实验,在一个安卓应用中加入了如下几行测试

1
2
3
4
5
6
7
8
9
File file = new File("/data/app/");
if (file == null)
return;
String[] files = file.list();
if (files == null)
return;
for (int i = 0; i < files.length; i++){
Log.i("zhangcheng","/data/app/:files["+String.valueOf(i+1)+"]:" + files[i]);
}

需要注意的【1】:目录权限一定要有,除了ROOT外,我还设置了777属性,否则file和files都可能是空指针,应用运行报错。【2】LOG的输出必须全都是字符,不是字符的要手工转成字符。我的输出如下:

/data/app/:files[1]:.restore_list
/data/app/:files[2]:HN_Facebook.apk
/data/app/:files[3]:HN_IndonesiaCanggih.apk
/data/app/:files[4]:HN_KingsoftOffice.apk
/data/app/:files[5]:HN_Messenger.apk
/data/app/:files[6]:HN_Twitter.apk
/data/app/:files[7]:com.qihoo.appstore-1.apk
/data/app/:files[8]:com.qihoo360.contacts-1.apk
/data/app/:files[9]:com.example.app-1.apk
/data/app/:files[10]:com.example.test-2.apk
(4)以上讲述的是静态接收器,那动态接收器的优先级呢?

android系统在收到短信息的时候会发送广播,但是此广播是有序广播,也就是说:先接收到广播的人,如果心情不好,它就不会向后传递此广播,后面的人就不会知道有短信到来。这与无序广播不同,无序广播并不是真的没有顺序,无序广播的接收者也是排队等待广播,只不过是在传递过程中,大家必须遵守规则一直把消息传递给最后一个。

以大家关心的接收短消息为例,想要在程序中接收短信,就要接收如下广播android.provider.Telephony.SMS_RECEIVED。系统把它作为有序广播进行发送,那么,谁第一个接收到短信将变得至关重要。

上一节说过静态接收器的接收顺序,那么动态接收器和静态接收器相比呢?答案是静态接收器优先级低于动态接收器,也就是说,无论多么高级别的静态接收器和多么低级别的动态接收器都接收同一广播,永远都是动态接收器先接收到!同等优先级的动态接收器,先注册的先接收。

(5)有些广播必须是动态注册的receiver才能收到
比如ACTION_SCREEN_ON,当屏幕被点亮的时候系统发送此广播,如果你尝试在manifest中注册receiver来接收,那么会失败,这是为什么呢?他们在Intent中都设置了Intent.FLAG_RECEIVER_REGISTERED_ONLY,所以如果要接收,必须动态注册广播接收器,ACTION_SCREEN_OFF也是如此。

广播注册过程和接收广播顺序过程

                                         图1 注册广播流程简图

静态广播接收器 由PackageManagerService负责,当手机启动时(或者新安装了应用),PackageManagerService负责扫描手机中所有已安装的APP应用(题外话,确定不再使用的APP需要卸载了),将AndroidManifest.xml中 有关注册广播的信息 解析出来,存储至一个全局静态变量当中mReceivers。

需要注意的是:

1 PackageManagerService扫描目录的顺序如下:

  system/framework

  system/app

  vendor/app

  data/app

  drm/app-private

2 当处于同一目录下时:按照file.list()的返回顺序。(题外话:因为在data/app下的应用都是用户安装的,并且都是以com.xxx.xxx-1.apk 的形式出现,所以如果打算做手机管家之类的应用,需要好好的研究下包名,争取在file.list()的独木桥下抢的头筹—优先接收开机启动完成的广播。)

3 在此处并未对 接收顺序做完整的排序。(注意修饰词完整的,毕竟先扫描的当然会有一定优先级)

动态广播接收器 由ActivityManagerService负责,当APP的服务或者进程起来之后,执行了注册广播接收的代码逻辑,即进行加载,最后会存储在一个全局静态变量

mReceiverResolver中。

需要注意的是:

1 这个并非是一成不变的,当程序被杀死之后,  已注册的动态广播接收器也会被移出mReceiverResolver,直到下次程序启动,再进行动态广播的注册,当然这里面的顺序也已经变更了一次。

2  这里也并没完整的进行广播的排序,只记录的注册的先后顺序,并未有结合优先级的处理。

当有广播发出时,接收顺序如下:

图2 广播接收流程简图

在ActivityManagerService处理广播,当广播为有序广播时,将动态广播接收器和动态广播接收器合并起来,形成最终的有序广播接收顺序。
上述的规则1排序为:
1 优先级高的先接收
2 同优先级的动静态广播接收器,动态优先于静态
3 同优先级的动态广播接收器 或者同优先级的静态广播接收器,按照图1 的流程注册顺序。
即静态:先扫描的大于后扫描的,动态:先注册的大于后注册的。

当广播为普通广播时,规则2排序为:
1 无视优先级,动态广播接收器优先于静态广播接收器
2 同规则1排序的第3点

最后举个例子:

(以下的静A 表示静态广播接收器,同理动B。)

1 静A (优先级1)

2 动B(优先级1)

3 静C (优先级2,后扫描)

4 静D (优先级2,先扫描)

5 动E (优先级2,先注册)

6 动F (优先级2,后注册)

当来了一个 有序广播,接收顺序如下:动E > 动F > 静D > 静C > 动B > 静A

当来了一个 普通广播,接收顺序如下:动E > 动F > 动B > 静D > 静C > 静A