广播(Broadcast)作为消息传递的一种方式,在Android系统中有着广泛的应用。系统提供了一系列的广播发送、接收接口,可以非常方便的实现广播的传递。系统中相当部分的状态变化也都是通过广播的方式通知到应用的,例如:系统启动完成(Intent.ACTION_BOOT_COMPLETED),电池状态变化(Intent.ACTION_BATTERY_CHANGED),网络连接状态变化(Intent.CONNECTIVITY_ACTION)等。

但同时也引发一个问题,如果任意应用都能随意发送此类广播,势必会引起系统状态的混乱,因此系统一定会有一套机制来保护此类所谓的受限广播。以下以Intent.ACTION_SCREEN_ON为例,深入探究下Android的受限广播保护机制。

Intent.ACTION_SCREEN_ON为屏幕点亮后由系统电源管理服务(PowerManagerService)所发,试想如果任意应用都能随意发送这个广播,则容易被恶意应用利用,造成监听该广播的应用状态、逻辑混乱。因此该广播一定是所谓的受限广播,这点在注释中也得到证实。

This is a protected intent that can only be sent by the system.

系统中发送该广播的代码如上,当然Android中还开放了如下非常丰富的发送广播的接口。这些方法有一个共同点,都调用了ActivityManagerService中的broadcastIntent接口,而这些开放出来的不同的发送广播接口,只是在调用broadcastIntent时传递的参数有所差异,如ordered、sticky等。

在ActivityManagerService的broadcastIntent方法中,首先是对Intent的flags做了一些校验,不是本文讨论范围之内,紧接着便是调用核心的broadcastIntentLocked方法,我们看到,受限保护的检验正是在这里做的。

在以上这段代码中,首先查看调用者的UID,如果是 ROOT:0 SYSTEM_UID:1000 PHONE_UID:1001 BLUETOOTH_UID:1002 SHELL_UID:2000 其中之一,则默认拥有发送受限广播权限,跳过校验过程。否则调用AppGlobals.getPackageManager().isProtectedBroadcast()判断是否为受限广播,如是则抛出权限异常,终止广播流程。

至此,受限广播校验流程基本清晰,那么问题来了,AppGlobals.getPackageManager().isProtectedBroadcast()是以什么依据来判断是否受限广播呢?继续从代码中找答案。

在PackageManagerService中维护了一个散列表mProtectedBroadcasts,用以标识哪些广播是受限的,而AppGlobals.getPackageManager().isProtectedBoradcast()所做的仅仅查询所发广播是否在这个列表中。继续跟谁在维护这个受限广播表,发现只有PackageManagerService.scanPackageLI会往受限表中添加元素,而添加的元素则来自pkg.protectedBroadcasts。

需要说明的是,scanPackageLI会在以下几种情况触发:构造PackageManagerService(扫描apks)、安装全新应用、安装应用更新、卸载系统应用、SD卡Mount/Unmount、APP目录(Framework/System/Vendor)新增文件。scanpackageLI被触发后,会将pkg.protectedcasts中的元素取出,添加到受限表中,由于HashSet的唯一性,不用担心重复添加的问题。因此判断是否受限广播的关键,落在了pkg.protectedBroadcasts上。

protectedBroadcasts是Package类中维护的列表,元素由PackageParser在解析apk时添加。

从PackageParser.parsePackage方法中可以清楚的看到,pkg.protectedBroadcasts对应AndroidManifest.xml中的protected-broadcast标签。回到本文开头的Intent.ACTION_SCREEN_ON广播,我们可以在frameworks/base/core/res/AndroidManifest.xml中找到,对应的package为system/framework/framework-res.apk

至此,我们将受限广播的声明、解析、校验过程全部了解完了。