BroadcastReceiver plugin

BroadcastReceiver plugin

27 January 2018

BroadcastReceiver 插件化,做起来非常更简单了。

也无需去 欺骗 AMS,从而不用 hook AMS,不需要这些花里胡哨的东西。

一句话:插件内,静态广播也好,动态广播也好。类加载到宿主中的时候,都用动态广播的方式,去一个一个手动注册上。

这种插件化的方式,就完全接入到了 Framework 中的注册流程代码里。所以,没有 hook Framework 的代码。




流程小记


注册


1. 需要完成 身份权限 的校验。

2. BroadcastFilter 作为 AMS 所在进程 systemserverBroadcastReceiver 对应的进程 通信用的 远程 Binder Proxycom.android.server.am.ActivityManagerService 会有一个集合去保存一堆 BroadcastFilter,方便 systemserverBroadcastReceiver 对应的进程 通信。

3. AMS 所在进程 systemserver 要想和 App 进程 通信。需要用到注册时,App 进程 提供的 远程 Binder Proxy。这样 systemserver 进程 就能和 App 进程 通信。这个 远程 Binder Proxyandroid.content.IIntentReceiver

4. 静态 BroadcastReceiver 注册是在 PMS 中完成的,并缓存在 com.android.server.pm.PackageManagerService 中。

5. 动态 BroadcastReceiver 注册是在 AMS 中完成的,并缓存在 com.android.server.pm.ActivityManagerService 中。


发送


BroadcastReceiver 的 发送接收 都在一个源码的方法内处理,很长。有广播发出后,AMS 会找到 对应的 广播接收者,然后,给这些 广播接收者 处理。


接收


1. android.content.IIntentReceiver 的实现类是 LoadedApk # ReceiverDispatcher # InnerReceiver

2. 会在 LoadedApk # ReceiverDispatcher # InnerReceiver # performReceive(...) 开个名为 Arg 的线程,完成 BroadcastReceiver # onReceive(...) 回调。从而,完成广播接收的过程。这个 Arg 定义描述为 final class Args extends BroadcastReceiver.PendingResult implements Runnable




静态与动态


注册


1. 静态 BroadcastReceiver 注册是在 PMS 中完成的,并缓存在 com.android.server.pm.PackageManagerService 中。

2. 动态 BroadcastReceiver 注册是在 AMS 中完成的,并缓存在 com.android.server.pm.ActivityManagerService 中。


存活


1. 动态 BroadcastReceiver所注册的进程销毁时,无法接收广播。

2. 静态 BroadcastReceiver 可以一直接收广播,系统会唤醒对应进程。




BroadcastReceiver plugin 思路


PackageParser 部分源码

package android.content.pm;

public class PackageParser {

    ...

    public final static class Package {

        ...

        public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
        public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);

        ...

    }

    ...

}


可以看出,Activity 的解析 和 BroadcastReceiver 的解析都放在 PackageParser # Activity 类里。所以,结果都是 ActivityInfo

区别在于: 在给 PackageParser # parsePackage(..., int flags) 方法传 flags 的时候,写 PackageManager.GET_RECEIVERS


1. 反射 PackageParser # generateActivityInfo(...) 获取 BroadcastReceiver 信息( ActivityInfo )

2. 根据这些 BroadcastReceiver 信息( ActivityInfo ),逐个动态注册。




BroadcastReceiver plugin


ReceiverInfoUtils

/**
 * @author CaMnter
 */

public final class ReceiverInfoUtils {

    private static final String TAG = ReceiverInfoUtils.class.getSimpleName();


    /**
     * 解析 Apk 文件中的 <receiver>
     * 并缓存
     *
     * 主要 调用 PackageParser 类的 generateActivityInfo 方法
     *
     * @param apkFile apkFile
     * @throws Exception exception
     */
    @SuppressWarnings("unchecked")
    @SuppressLint("PrivateApi")
    public static Map<ActivityInfo, List<? extends IntentFilter>> getReceiverInfos(@NonNull final File apkFile,
                                                                                   @NonNull final Context context)
        throws Exception {

        final Map<ActivityInfo, List<? extends IntentFilter>> receiverInfoMap = new HashMap<>();

        /**
         * 反射 获取 PackageParser # parsePackage(File packageFile, int flags)
         */
        final Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");

        /**
         * <= 4.0.0
         *
         * Don't deal with
         *
         * >= 4.0.0
         *
         * parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int flags)
         *
         * ---
         *
         * >= 5.0.0
         *
         * parsePackage(File packageFile, int flags)
         *
         */
        final int sdkVersion = Build.VERSION.SDK_INT;
        if (sdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            throw new RuntimeException(
                "[" + TAG + "]   the sdk version must >= 14 (4.0.0)");
        }

        final Object packageParser;
        final Object packageObject;
        final Method parsePackageMethod;

        if (sdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
            // >= 5.0.0
            // parsePackage(File packageFile, int flags)
            /**
             * 反射创建 PackageParser 对象,无参数构造
             *
             * 反射 调用 PackageParser # parsePackage(File packageFile, int flags)
             * 获取 apk 文件对应的 Package 对象
             */
            packageParser = packageParserClass.newInstance();

            parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage",
                File.class, int.class);
            packageObject = parsePackageMethod.invoke(
                packageParser,
                apkFile,
                PackageManager.GET_RECEIVERS
            );
        } else {
            // >= 4.0.0
            // parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int flags)
            /**
             * 反射创建 PackageParser 对象,PackageParser(String archiveSourcePath)
             *
             * 反射 调用 PackageParser # parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int flags)
             * 获取 apk 文件对应的 Package 对象
             */
            final String apkFileAbsolutePath = apkFile.getAbsolutePath();
            packageParser = packageParserClass.getConstructor(String.class)
                .newInstance(apkFileAbsolutePath);

            parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage",
                File.class, String.class, DisplayMetrics.class, int.class);
            packageObject = parsePackageMethod.invoke(
                packageParser,
                apkFile,
                apkFile.getAbsolutePath(),
                context.getResources().getDisplayMetrics(),
                PackageManager.GET_RECEIVERS
            );
        }

        if (sdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // >= 4.2.0
            // generateActivityInfo(Activity a, int flags, PackageUserState state, int userId)
            /**
             * 读取 Package # ArrayList<Activity> receivers
             * 通过 ArrayList<Activity> receivers 获取 Receiver 对应的 ActivityInfo
             */
            final Field activitiesField = packageObject.getClass().getDeclaredField("receivers");
            final List receivers = (List) activitiesField.get(packageObject);

            /**
             * 反射调用 UserHandle # static @UserIdInt int getCallingUserId()
             * 获取到 userId
             *
             * 反射创建 PackageUserState 对象
             */
            final Class<?> packageParser$ActivityClass = Class.forName(
                "android.content.pm.PackageParser$Activity");
            final Class<?> packageUserStateClass = Class.forName(
                "android.content.pm.PackageUserState");
            final Class<?> userHandler = Class.forName("android.os.UserHandle");
            final Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
            final int userId = (Integer) getCallingUserIdMethod.invoke(null);
            final Object defaultUserState = packageUserStateClass.newInstance();

            final Class<?> componentClass = Class.forName(
                "android.content.pm.PackageParser$Component");
            final Field intentsField = componentClass.getDeclaredField("intents");

            // 需要调用 android.content.pm.PackageParser#generateActivityInfo(Activity a, int flags, PackageUserState state, int userId)
            final Method generateActivityInfo = packageParserClass.getDeclaredMethod(
                "generateActivityInfo",
                packageParser$ActivityClass, int.class, packageUserStateClass, int.class);

            /**
             * 反射调用 PackageParser # generateActivityInfo(Activity a, int flags, PackageUserState state, int userId)
             * 解析出 Receiver 对应的 ActivityInfo
             *
             * 然后保存
             */
            for (Object receiver : receivers) {
                final ActivityInfo info = (ActivityInfo) generateActivityInfo.invoke(
                    packageParser,
                    receiver,
                    0,
                    defaultUserState,
                    userId
                );
                final List<? extends IntentFilter> filters
                    = (List<? extends IntentFilter>) intentsField.get(receiver);
                receiverInfoMap.put(info, filters);
            }
        } else if (sdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
            // >= 4.1.0
            // generateActivityInfo(Activity a, int flags, boolean stopped, int enabledState, int userId)
            /**
             * 读取 Package # ArrayList<Activity> receivers
             * 通过 ArrayList<Activity> receivers 获取 Receiver 对应的 ActivityInfo
             */
            final Field activitiesField = packageObject.getClass().getDeclaredField("receivers");
            final List receivers = (List) activitiesField.get(packageObject);

            // 需要调用 android.content.pm.PackageParser#generateActivityInfo(Activity a, int flags, boolean stopped, int enabledState, int userId)
            final Class<?> packageParser$ActivityClass = Class.forName(
                "android.content.pm.PackageParser$Activity");
            final Class<?> userHandler = Class.forName("android.os.UserId");
            final Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
            final int userId = (Integer) getCallingUserIdMethod.invoke(null);
            final Method generateActivityInfo = packageParserClass.getDeclaredMethod(
                "generateActivityInfo",
                packageParser$ActivityClass, int.class, boolean.class, int.class, int.class);

            final Class<?> componentClass = Class.forName(
                "android.content.pm.PackageParser$Component");
            final Field intentsField = componentClass.getDeclaredField("intents");

            /**
             * 反射调用 PackageParser # generateActivityInfo(Activity a, int flags, boolean stopped, int enabledState, int userId)
             * 解析出 Receiver 对应的 ActivityInfo
             *
             * 在之前版本的 4.0.0 中 存在着
             * public class PackageParser {
             *     public final static class Package {
             *         // User set enabled state.
             *         public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
             *
             *         // Whether the package has been stopped.
             *         public boolean mSetStopped = false;
             *     }
             * }
             *
             * 然后保存
             */
            for (Object receiver : receivers) {
                final ActivityInfo info = (ActivityInfo) generateActivityInfo.invoke(
                    packageParser,
                    receiver,
                    0,
                    false,
                    PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
                    userId
                );
                final List<? extends IntentFilter> filters
                    = (List<? extends IntentFilter>) intentsField.get(receiver);
                receiverInfoMap.put(info, filters);
            }
        } else if (sdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            // >= 4.0.0
            // generateActivityInfo(Activity a, int flags)
            /**
             * 读取 Package # ArrayList<Activity> receivers
             * 通过 ArrayList<Activity> receivers 获取 Receiver 对应的 ActivityInfo
             */
            final Field activitiesField = packageObject.getClass().getDeclaredField("receivers");
            final List receivers = (List) activitiesField.get(packageObject);

            // 需要调用 android.content.pm.PackageParser#generateActivityInfo(Activity a, int flags)
            final Class<?> packageParser$ActivityClass = Class.forName(
                "android.content.pm.PackageParser$Activity");
            final Method generateActivityInfo = packageParserClass.getDeclaredMethod(
                "generateActivityInfo",
                packageParser$ActivityClass, int.class);

            final Class<?> componentClass = Class.forName(
                "android.content.pm.PackageParser$Component");
            final Field intentsField = componentClass.getDeclaredField("intents");

            /**
             * 反射调用 PackageParser # generateActivityInfo(Activity a, int flags)
             * 解析出 Receiver 对应的 ActivityInfo
             *
             * 然后保存
             */
            for (Object receiver : receivers) {
                final ActivityInfo info = (ActivityInfo) generateActivityInfo.invoke(
                    packageParser,
                    receiver,
                    0
                );
                final List<? extends IntentFilter> filters
                    = (List<? extends IntentFilter>) intentsField.get(receiver);
                receiverInfoMap.put(info, filters);
            }
        }

        return receiverInfoMap;

    }

}


SmartApplication

/**
 * @author CaMnter
 */

public class SmartApplication extends Application {

    private static final Map<ActivityInfo, List<? extends IntentFilter>> receiverInfoMap
        = new HashMap<>();


    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     *
     * @param base The new base context for this wrapper.
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            // assets 的 broadcast-receiver-plugin-plugin.apk 拷贝到 /data/data/files/[package name]
            AssetsUtils.extractAssets(base, "broadcast-receiver-plugin-plugin.apk");
            final File apkFile = getFileStreamPath("broadcast-receiver-plugin-plugin.apk");
            final File odexFile = getFileStreamPath("broadcast-receiver-plugin-plugin.odex");
            // Hook ClassLoader, 让插件中的类能够被成功加载
            BaseDexClassLoaderHooker.patchClassLoader(this.getClassLoader(), apkFile, odexFile);
            receiverInfoMap.putAll(ReceiverInfoUtils.getReceiverInfos(apkFile, base));
            // 插件广播注册成 动态广播
            this.registerPluginBroadcast(base, receiverInfoMap);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 注册 插件 Broadcast
     *
     * @param context context
     * @param receiverInfoMap receiverInfoMap
     * @throws ClassNotFoundException ClassNotFoundException
     * @throws IllegalAccessException IllegalAccessException
     * @throws InstantiationException InstantiationException
     */
    private void registerPluginBroadcast(@NonNull final Context context,
                                         @NonNull final Map<ActivityInfo, List<? extends IntentFilter>> receiverInfoMap)
        throws ClassNotFoundException,
               IllegalAccessException,
               InstantiationException {
        for (Map.Entry<ActivityInfo, List<? extends IntentFilter>> entry : receiverInfoMap.entrySet()) {
            final ActivityInfo activityInfo = entry.getKey();
            final List<? extends IntentFilter> intentFilters = entry.getValue();

            final ClassLoader classLoader = this.getClassLoader();
            for (IntentFilter intentFilter : intentFilters) {
                final BroadcastReceiver receiver = (BroadcastReceiver) classLoader.loadClass(
                    activityInfo.name).newInstance();
                context.registerReceiver(receiver, intentFilter);
            }
        }
    }

}




发送插件 BroadcastReceiver


/**
 * Called when a view has been clicked.
 *
 * @param v The view that was clicked.
 */
@Override
public void onClick(View v) {  
    switch (v.getId()) {
        case R.id.start_first_text:
            this.startFirstText.setEnabled(false);
            try {
                final Intent intent =
                    new Intent("com.camnter.broadcast.receiver.plugin.plugin.FirstReceiver");
                intent.putExtra("message", UUID.randomUUID().toString());
                this.sendBroadcast(intent);
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.startFirstText.setEnabled(true);
            break;
        case R.id.start_second_text:
            this.startSecondText.setEnabled(false);
            try {
                final Intent intent =
                    new Intent("com.camnter.broadcast.receiver.plugin.plugin.SecondReceiver");
                intent.putExtra("message", UUID.randomUUID().toString());
                this.sendBroadcast(intent);
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.startSecondText.setEnabled(true);
            break;
    }
}




参考资料


Android插件化原理解析——广播的管理