从Permission Denial错误看Android广播权限演变:4.4到13的适配指南
如果你在Android开发中遇到过那个令人头疼的Permission Denial: not allowed to send broadcast异常,那你一定知道这不仅仅是代码里多了一个权限声明那么简单。这个看似简单的错误背后,是Android系统近十年来对广播机制安全性和隐私保护的持续演进。从KitKat到Tiramisu,每个版本都在重新定义应用间通信的边界,而开发者需要做的,就是在这不断变化的规则中找到适配的路径。
这篇文章不是简单的API列表罗列,而是基于我这些年处理各种广播权限问题的实战经验,为你梳理出一条清晰的适配思路。我们会从Android 4.4那个标志性的<protected-broadcast>机制开始,一路追踪到Android 13对运行时权限的进一步收紧,看看Google是如何一步步构建起今天的广播安全体系的。无论你是维护着需要兼容多个Android版本的老项目,还是正在开发面向最新系统的新应用,理解这些变化背后的逻辑,都能让你在遇到权限问题时更快地找到解决方案。
1. 广播权限机制的基石:从Android 4.4的protected-broadcast说起
Android 4.4 KitKat在2013年发布时,引入了一个对广播机制影响深远的特性:保护性广播。如果你现在去翻看Android 4.4的源代码,会在frameworks/base/core/res/AndroidManifest.xml里看到大量这样的声明:
<protected-broadcast android:name="android.intent.action.MEDIA_MOUNTED" />
<protected-broadcast android:name="android.intent.action.PHONE_STATE" />
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE" />
这些标记意味着什么?简单来说,Google开始意识到,有些系统事件广播不应该被普通应用随意发送。以MEDIA_MOUNTED为例,在Android 4.4之前,任何应用都可以发送这个广播来模拟存储设备挂载事件,这可能导致其他应用做出错误的响应。从4.4开始,只有系统应用(位于/system/framework、/system/app或/vendor/app目录下)才能发送这些被保护的广播。
1.1 保护性广播的实现机制
要理解为什么你的应用会收到Permission Denial错误,我们需要看看Android系统内部是如何处理这些广播的。在ActivityManagerService的broadcastIntentLocked方法中,系统会检查发送者是否有权限发送特定的广播:
// 简化的检查逻辑
if (intent.getAction() != null) {
if (mProtectedBroadcasts.contains(intent.getAction())) {
// 检查发送者是否为系统应用
if (!isCallerSystem()) {
Slog.w(TAG, "Permission Denial: not allowed to send broadcast "
+ intent.getAction());
throw new SecurityException("Permission Denial");
}
}
}
这里的关键在于mProtectedBroadcasts这个集合,它在系统启动时由PackageManagerService从所有系统应用的AndroidManifest.xml中收集而来。这意味着即使你的应用声明了android.permission.BROADCAST_STICKY这样的系统权限,只要你不是系统应用,就无法发送保护性广播。
1.2 开发者遇到的典型问题
在实际开发中,Android 4.4带来的变化让很多开发者措手不及。我印象最深的是一个媒体播放器应用,它原本依赖MEDIA_MOUNTED广播来刷新媒体库。升级到4.4后,这个功能突然失效了。查看日志,就是那个熟悉的错误:
W/BroadcastQueue: Permission Denial: not allowed to send broadcast
android.intent.action.MEDIA_MOUNTED from pid=1234, uid=10086
当时团队里的第一反应是检查权限声明,但很快发现这根本不是权限问题。真正的解决方案是改用MediaScannerConnection:
// 错误的做法(Android 4.4+会失败)
Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED);
intent.setData(Uri.parse("file://" + filePath));
sendBroadcast(intent);
// 正确的替代方案
MediaScannerConnection.scanFile(context,
new String[]{filePath},
null,
new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
// 扫描完成后的处理
}
});
注意:不仅仅是发送保护性广播会受限,接收某些系统广播也可能需要特定权限。比如接收
PHONE_STATE广播,从Android 6.0开始需要READ_PHONE_STATE运行时权限,而在某些设备上,系统发送的这个广播可能还需要READ_PRIVILEGED_PHONE_STATE权限,这是普通应用无法获取的。
2. Android 5.0-7.0:运行时权限与隐式广播限制
如果说Android 4.4只是给广播权限开了个头,那么从Lollipop到Nougat的这几个版本,则是系统对广播机制进行系统性重构的阶段。这里的变化主要体现在两个方面:运行时权限的引入和隐式广播的限制。
2.1 运行时权限对广播接收的影响
Android 6.0 Marshmallow引入的运行时权限模型,改变了应用获取敏感权限的方式。对于广播接收来说,最直接的影响是:某些广播的接收现在需要用户明确授权。
以电话状态监听为例,在Android 6.0之前,你只需要在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<receiver android:name=".PhoneStateReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
但从6.0开始,如果应用的目标SDK版本>=23,你还需要在运行时请求权限:
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_PHONE_STATE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);


2841

被折叠的 条评论
为什么被折叠?



