Android-9: 持续打印non-protected broadcast 问题

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. 解决办法

有两个解决办法:

  1. 简单粗暴,将自定义的广播在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" />
  1. 如果不希望改Android的源码,可以将自己的App放置到/system/priv-app下,而不是默认的/system/app目录下:
    可在自己app的Android.mk中指定路径:
    LOCAL_MODULE_PATH := $(TARGET_OUT_APPS_PRIVILEGED)

    但是特别注意的是:如果App放置到priv-app目录,是会一些权限限制的。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值