mingyuanblogs

android后台应用如何接收intent广播

背景

我们有一个android系统应用常驻在后台运行,想要通知系统应用执行某个操作,因此想到使用intent广播发送给系统应用,应用中设置broadcastReceiver来接收intent并执行相应动作。

AndroidManifest.xml文件中注册静态broadcastReceiver:

<receiver
    android:name="com.alibaba.cloudgame.agent.service.AutoAdaptReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <!-- 人机结合-UI阻塞告警 -->
        <action android:name="com.alibaba.cloudgame.agent.adapt.ReportAutoAdaptUIBlockWarn" />

        <!-- 人机结合-游戏设置告警 -->
        <action android:name="com.alibaba.cloudgame.agent.adapt.ReportGameSettingWarn"/>
    </intent-filter>
</receiver>

定义broadcastReceiver处理类:

public class AutoAdaptReceiver extends BroadcastReceiver {

    public static final String ACTION_AUTO_ADAPT_UI_BLOCK_WARNING = "com.alibaba.cloudgame.agent.adapt.ReportAutoAdaptUIBlockWarn";
    public static final String ACTION_AUTO_ADAPT_GAME_SETTING_WARNING = "com.alibaba.cloudgame.agent.adapt.ReportGameSettingWarn";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        LogR.i(String.format("Get broadcast intent message: %s", action));

        // get report data
        String reportData = intent.getStringExtra("reportData");
        // get extension data
        String extension = intent.getStringExtra("extension");

        LogR.d(String.format("get report data: %s", reportData));
        LogR.d(String.format("get extension data: %s", extension));

        // convert extension string to map
        Map<String, String> extensionMap = null;
        try {
            extensionMap = JSON.parseObject(extension, new TypeReference<Map<String, String>>() {});
        }catch (Exception e) {
            LogR.e(String.format("parse extension message to map failed with error: %s", e));
        }

        if (extensionMap == null) {
            LogR.e("extension map is empty, can not send message to paas");
            parseAndSendMsgFailed();
            return;
        }

        LogR.d(String.format("extension map %s", extensionMap));

        if (action.equals(ACTION_AUTO_ADAPT_UI_BLOCK_WARNING)) {

            CallbackManager.replyMsgToAutoAdaptCallback("reportAutoAdaptUIBlockWarn", reportData, extensionMap);
            receiveBroadcastIntentSuccess();
            AutoAdaptMessageHandler.INSTANCE.get().checkAndResendAfterTimeout("reportAutoAdaptUIBlockWarn", reportData, extensionMap);

        } else if (action.equals(ACTION_AUTO_ADAPT_GAME_SETTING_WARNING)) {

            CallbackManager.replyMsgToAutoAdaptCallback("reportGameSettingWarn", reportData, extensionMap);
            receiveBroadcastIntentSuccess();
            AutoAdaptMessageHandler.INSTANCE.get().checkAndResendAfterTimeout("reportGameSettingWarn", reportData, extensionMap);

        } else {
            LogR.w(String.format("do not support this action: %s", action));
            parseAndSendMsgFailed();
        }

    }
}

问题

编译安装好apk到手机上后,通过am命令发送intent广播:

adb shell am broadcast -a com.alibaba.cloudgame.agent.adapt.ReportAutoAdaptUIBlockWarn --es reportData "{"slmUrl": "https://xxxxxx", "h5StreamingUrl":"http://xxxxx"}" --es extension "{"taskId": "123", "packType": "nas"}"

系统应用始终无法接收到intent广播,也没有执行receiver中的逻辑。

排查

通过拉取logcat日志,发现有一行输出

05-05 15:39:01.989   739   977 W BroadcastQueue: Background execution not allowed: receiving Intent { act=com.alibaba.cloudgame.agent.adapt.ReportGameSettingWaro com.alibaba.cloudgame.agent/.service.AutoAdaptReceiver}

查阅资料后,这条命令是因为android O对后台应用在接收和处理广播上做了限制,特别是对于静态注册的广播,从而导致应用接收不到广播。

如何解决?一筹莫展的时候就去阅读源码吧~

原理

俗话说:所有的秘密都藏在源代码中,让我们进入android源码一探究竟吧。

首先,我们得知道为什么android会对后台应用接收广播进行限制,参考:https://developer.android.com/about/versions/oreo/background#broadcasts

如果应用注册为接收广播,则在每次发送广播时,应用的接收器都会消耗资源。 如果多个应用注册为接收基于系统事件的广播,这会引发问题;触发广播的系统事件会导致所有应用快速地连续消耗资源,从而降低用户体验

可以看到,Google也认识到广播这个机制的存在对于用户的使用体验包括性能以及功耗方面的影响比较大,所以才致力于不断的增加后台的限制。

和这行日志相关直接相关的android源码链接:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android10-mainline-a-release/services/core/java/com/android/server/am/BroadcastQueue.java

找到对应日志输出的代码:
BroadcastQueue.processNextBroadcast

 if (!skip) {
            final int allowed = mService.getAppStartModeLocked(
                    info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                // We won't allow this receiver to be launched if the app has been
                // completely disabled from launches, or it was not explicitly sent
                // to it and the app is in a state that should not receive it
                // (depending on how getAppStartModeLocked has determined that).
                if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                    Slog.w(TAG, "Background execution disabled: receiving "
                            + r.intent + " to "
                            + component.flattenToShortString());
                    skip = true;
                } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                        || (r.intent.getComponent() == null
                            && r.intent.getPackage() == null
                            && ((r.intent.getFlags()
                                    & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
                            && !isSignaturePerm(r.requiredPermissions))) {
                    mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                            component.getPackageName());
                    Slog.w(TAG, "Background execution not allowed: receiving "
                            + r.intent + " to "
                            + component.flattenToShortString());
                    skip = true;
                }
            }
        }

这块逻辑就是在对intent的flag和intent类型进行校验,两种情况会跳过intent广播,即skip=true:

  1. intent的flags有FLAG_RECEIVER_EXCLUDE_BACKGROUND,那就直接skip掉了
  2. 针对manifest中定义的静态注册的广播,也会skip

从条件 (r.intent.getFlags()& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)可以看到,如果intent中带有FLAG_RECEIVER_INCLUDE_BACKGROUND标志,那么整个条件将为false,不再忽略intent广播。

那么FLAG_RECEIVER_INCLUDE_BACKGROUND在哪里设置的?
ActivityManagerService.broadcastIntentLocked intent广播将在此函数中发起

if (getBackgroundLaunchBroadcasts().contains(action)) {
	if (DEBUG_BACKGROUND_CHECK) {
		Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
	}
	intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
}

private ArraySet<String> getBackgroundLaunchBroadcasts() {
    if (mBackgroundLaunchBroadcasts == null) {
        mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
    }
    return mBackgroundLaunchBroadcasts;
}

后台运行的广播可以增加FLAG_RECEIVER_INCLUDE_BACKGROUND标志,也就说如果intent带有FLAG_RECEIVER_INCLUDE_BACKGROUND标志,那么receiver也可以处于后台运行状态。

到这里就比较清晰了,我们想让后台运行的应用接收intent广播,那么就需要在发送intent时带上FLAG_RECEIVER_INCLUDE_BACKGROUND标志。
其中FLAG_RECEIVER_INCLUDE_BACKGROUND标志的定义:

/**
      * If set, the broadcast will always go to manifest receivers in background (cached
      * or not running) apps, regardless of whether that would be done by default.  By
       * default they will only receive broadcasts if the broadcast has specified an
       * explicit component or package name.
       *
       * NOTE: dumpstate uses this flag numerically, so when its value is changed
       * the broadcast code there must also be changed to match.
       *
       * @hide
       */
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;

解决

我们在使用am命令发送intent时加上0x01000000标志,强制让后台应用接收广播

adb shell am broadcast -a com.alibaba.cloudgame.agent.adapt.ReportAutoAdaptUIBlockWarn --es reportData "{"slmUrl": "https://xxxxxx", "h5StreamingUrl":"http://xxxxx"}" --es extension "{"taskId": "123", "packType": "nas"}" -f 0x01000000

参考

https://blog.csdn.net/u011733869/article/details/83932178

posted on 2023-05-05 17:35  mingyuan996  阅读(145)  评论(0)    收藏  举报

导航