1. 问题
将App打包成系统App时,即使在自己App的AndroidManifest.xml中声明了的新增protected-broadcast广播,为什么发送广播时还总是打印如下log:
ActivityManager: Sending non-protected broadcast
2. 分析
Log是在ActivityManagerService.java的以下代码打印出来的。
从以下代码看,会检测是不是isProtectedBroadcast广播,如果是就直接返回,则不会再打印此警告了。
private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
// Don't yell about broadcasts sent via shell
return;
}
final String action = intent.getAction();
if (isProtectedBroadcast
|| Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
|| Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
|| Intent.ACTION_MEDIA_BUTTON.equals(action)
|| Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
|| Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
|| Intent.ACTION_MASTER_CLEAR.equals(action)
|| Intent.ACTION_FACTORY_RESET.equals(action)
|| AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
|| AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
|| LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)
|| TelephonyIntents.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
|| SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
|| AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
|| AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.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;
}
// This broadcast may be a problem... but there are often system components that
// want to send an internal broadcast to themselves, which is annoying to have to
// explicitly list each action as a protected broadcast, so we will check for that
// one safe case and allow it: an explicit broadcast, only being received by something
// that has protected itself.
if (intent.getPackage() != null || intent.getComponent() != null) {
if (receivers == null || receivers.size() == 0) {
// Intent is explicit and there's no receivers.
// This happens, e.g. , when a system component sends a broadcast to
// its own runtime receiver, and there's no manifest receivers for it,
// because this method is called twice for each broadcast,
// for runtime receivers and manifest receivers and the later check would find
// no receivers.
return;
}
boolean allProtected = true;
for (int i = receivers.size()-1; i >= 0; i--) {
Object target = receivers.get(i);
if (target instanceof ResolveInfo) {
ResolveInfo ri = (ResolveInfo)target;
if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
allProtected = false;
break;
}
} else {
BroadcastFilter bf = (BroadcastFilter)target;
if (bf.requiredPermission == null) {
allProtected = false;
break;
}
}
}
if (allProtected) {
// All safe!
return;
}
}
// The vast majority of broadcasts sent from system internals
// should be protected to avoid security holes, so yell loudly
// to ensure we examine these cases.
if (callerApp != null) {
Log.wtf(TAG, "Sending non-protected broadcast " + action
+ " from system " + callerApp.toShortString() + " pkg " + callerPackage,
new Throwable());
} else {
Log.wtf(TAG, "Sending non-protected broadcast " + action
+ " from system uid " + UserHandle.formatUid(callingUid)
+ " pkg " + callerPackage,
new Throwable());
}
}
而isProtectedBroadcast是通过以下代码获取到当前广播是否是protected的广播
isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
isProtectedBroadcast()函数的具体实现是在PackageManagerService.java中如下代码:
5543 @Override
5544 public boolean isProtectedBroadcast(String actionName) {
5545 // allow instant applications
5546 synchronized (mProtectedBroadcasts) {
5547 if (mProtectedBroadcasts.contains(actionName)) {
5548 return true;
5549 } else if (actionName != null) {
5550 // TODO: remove these terrible hacks
5551 if (actionName.startsWith("android.net.netmon.lingerExpired")
5552 || actionName.startsWith("com.android.server.sip.SipWakeupTimer")
5553 || actionName.startsWith("com.android.internal.telephony.data-reconnect")
5554 || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {
5555 return true;
5556 }
5557 }
5558 }
5559 return false;
5560 }
通过以上代码可知,实际是PackageManagerService里面维护一个名为mProtectedBroadcasts的系统广播白名单。在PackageManagerService扫描系统App时会将AndroidManifest.xml中的所有protected-broadcast加入到此ArraySet变量。
11298 private void commitPackageSettings(PackageParser.Package pkg,
11299 @Nullable PackageParser.Package oldPkg, PackageSetting pkgSetting, UserHandle user,
11300 final @ScanFlags int scanFlags, boolean chatty) {
.........
11634 if (pkg.protectedBroadcasts != null) {
11635 N = pkg.protectedBroadcasts.size();
11636 synchronized (mProtectedBroadcasts) {
11637 for (i = 0; i < N; i++) {
11638 mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
11639 }
11640 }
11641 }
.......
PackageManagerService每次启动时会扫描下面三个目录的App:
/system/app
/system/priv-app
/system/framework/framework-res.apk
所以理论上只要在自己App中的AndroidManifest.xml中将自己新增的广播声明为protected-broadcast即可,不应该会出现Sending non-protected broadcast才对。
无意中看到加入广播白名单的参数有一个protectedBroadcasts,通过搜寻发现PackageManagerService中还有如下代码段:
10808 private static void applyPolicy(PackageParser.Package pkg, final @ParseFlags int parseFlags,
10809 final @ScanFlags int scanFlags, PackageParser.Package platformPkg) {
.........
10847 if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
10848 // clear protected broadcasts
10849 pkg.protectedBroadcasts = null;
10850 // ignore export request for single user receivers
10851 if (pkg.receivers != null) {
10852 for (int i = pkg.receivers.size() - 1; i >= 0; --i) {
10853 final PackageParser.Activity receiver = pkg.receivers.get(i);
10854 if ((receiver.info.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
10855 receiver.info.exported = false;
10856 }
10857 }
10858 }
...........
从以上代码可知,PackageManagerService在扫描App时,发现如果App不是来自priv-app目录下的App(当然framework-res.apk除外),就会将此App的中声明的protected广播清空,这样这个广播就不会加入到白名单,所以才就一直打印上面的warning log
不过这个仅仅是warning的log而已,广播还是可以正常发送和接收的。只是如果持续打印,会影响到系统性能的,而且看log也心塞,持续打印没用的log。
3. 解决办法
有两个解决办法:
- 简单粗暴,将自定义的广播在frameworks/base/core/res/AndroidManifest.xml中声明为protected-broadcast
29 <protected-broadcast android:name="android.intent.action.SCREEN_OFF" />
30 <protected-broadcast android:name="android.intent.action.SCREEN_ON" />
31 <protected-broadcast android:name="android.intent.action.USER_PRESENT" />
32 <protected-broadcast android:name="android.intent.action.TIME_SET" />
33 <protected-broadcast android:name="android.intent.action.TIME_TICK" />
34 <protected-broadcast android:name="android.intent.action.TIMEZONE_CHANGED" />
-
如果不希望改Android的源码,可以将自己的App放置到/system/priv-app下,而不是默认的/system/app目录下:
可在自己app的Android.mk中指定路径:
LOCAL_MODULE_PATH := $(TARGET_OUT_APPS_PRIVILEGED)但是特别注意的是:如果App放置到priv-app目录,是会一些权限限制的。