zoukankan      html  css  js  c++  java
  • BroadcastReceiver插件化解决方案

    --摘自《android插件化开发指南》

    1.静态广播和动态广播仅区别于注册方式的不同。静态广播的注册信息保存在PMS中,动态广播的注册信息保存在AMS中

    2.发送广播,也就是Context的sendBroadcast方法,最终会调用AMN.getDefault().broadcastIntent,把要发送的广播告诉AMS;

      AMS在收到上述信息后,搜索AMS和PMS中保存的广播,看哪些广播符合条件,然后通知App进程启动这些广播,也就是调用这些广播的onReceive方法

    3.无论发送广播还是接受广播,都携带一个筛选条件:intent-filter。

    <receiver
        android:name=".MyReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="baobao2"/>
    </receiver>
    MyReceiver myReceiver = new MyReceiver();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("baobao2");
    registerReceiver(myReceiver,intentFilter);

    ***动态广播的插件化解决方案***

    使用前面介绍的dex合并技术,插件中的动态广播就可以被宿主App正常调用了

    ***静态广播的插件化解决方案***

    1)PMS只能读取宿主App的AndroidManifest文件,读取其中的静态广播并注册。我们可以通过反射,手动控制PMS读取插件的AndroidManifest中声明的静态广播列表

    2)遍历这个静态广播列表。使用插件的classLoader加载列表中的每个广播类,实例化成一个对象,然后作为动态广播注册到AMS中

    public final class ReceiverHelper {
    
        private static final String TAG = "ReceiverHelper";
    
        /**
         * 解析插件Apk文件中的 <receiver>, 并存储起来
         *
         * @param apkFile
         * @throws Exception
         */
        public static void preLoadReceiver(Context context, File apkFile) {
            // 首先调用parsePackage获取到apk对象对应的Package对象
            Object packageParser = RefInvoke.createObject("android.content.pm.PackageParser");
            Class[] p1 = {File.class, int.class};
            Object[] v1 = {apkFile, PackageManager.GET_RECEIVERS};
            Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);
    
            // 读取Package对象里面的receivers字段,注意这是一个 List<Activity> (没错,底层把<receiver>当作<activity>处理)
            // 接下来要做的就是根据这个List<Activity> 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了)
            List receivers = (List) RefInvoke.getFieldObject(packageObj, "receivers");
    
            for (Object receiver : receivers) {
                registerDynamicReceiver(context, receiver);
            }
        }
    
        // 解析出 receiver以及对应的 intentFilter
        // 手动注册Receiver
        public static void registerDynamicReceiver(Context context, Object receiver) {
            //取出receiver的intents字段
            List<? extends IntentFilter> filters = (List<? extends IntentFilter>) RefInvoke.getFieldObject(
                    "android.content.pm.PackageParser$Component", receiver, "intents");
    
            try {
                // 把解析出来的每一个静态Receiver都注册为动态的
                for (IntentFilter intentFilter : filters) {
                    ActivityInfo receiverInfo = (ActivityInfo) RefInvoke.getFieldObject(receiver, "info");
    
                    BroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);
                    context.registerReceiver(broadcastReceiver, intentFilter);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    ***不启动App和插件中的静态广播通信***

    在宿主的androidmanifest中注册占位StubReceiver

    <application
        android:name="jianqiang.com.receiverhook.UPFApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
    
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    
        <receiver
            android:name=".StubReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="jianqiang1" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang2" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang3" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang4" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang5" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang6" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang7" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang8" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang9" />
            </intent-filter>
            <intent-filter>
                <action android:name="jianqiang10" />
            </intent-filter>
        </receiver>
    </application>

    插件的androidmanifest中注册

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
    
        <receiver
            android:name=".MyReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="baobao" />
            </intent-filter>
            <meta-data android:name="oldAction" android:value="jianqiang1"></meta-data>
        </receiver>
        <receiver
            android:name=".MyReceiver2"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="baobao2" />
            </intent-filter>
            <meta-data android:name="oldAction" android:value="jianqiang2"></meta-data>
        </receiver>
    </application>

    具体流程是宿主清单文件中查找jianqiang1,然后到插件的清单文件中查找jianqiang1,最后找到jianqiang1对应的baobao,这才是真正要注册的广播

    实现逻辑如下

    public final class ReceiverHelper {
    
        private static final String TAG = "ReceiverHelper";
    
        /**
         * 解析插件Apk文件中的 <receiver>, 并存储起来
         *
         * @param apkFile
         * @throws Exception
         */
        public static void preLoadReceiver(Context context, File apkFile) {
            // 首先调用parsePackage获取到apk对象对应的Package对象
            Object packageParser = RefInvoke.createObject("android.content.pm.PackageParser");
            Class[] p1 = {File.class, int.class};
            Object[] v1 = {apkFile, PackageManager.GET_RECEIVERS};
            Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);
    
            String packageName = (String)RefInvoke.getFieldObject(packageObj, "packageName");
    
            // 读取Package对象里面的receivers字段,注意这是一个 List<Activity> (没错,底层把<receiver>当作<activity>处理)
            // 接下来要做的就是根据这个List<Activity> 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了)
            List receivers = (List) RefInvoke.getFieldObject(packageObj, "receivers");
    
            try {
                for (Object receiver : receivers) {
                    Bundle metadata = (Bundle)RefInvoke.getFieldObject(
                            "android.content.pm.PackageParser$Component", receiver, "metaData");
                    String oldAction = metadata.getString("oldAction");
    
                    // 解析出 receiver以及对应的 intentFilter
                    List<? extends IntentFilter> filters = (List<? extends IntentFilter>) RefInvoke.getFieldObject(
                            "android.content.pm.PackageParser$Component", receiver, "intents");
    
                    // 把解析出来的每一个静态Receiver都注册为动态的
                    for (IntentFilter intentFilter : filters) {
                        ActivityInfo receiverInfo = (ActivityInfo) RefInvoke.getFieldObject(receiver, "info");
                        BroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);
                        context.registerReceiver(broadcastReceiver, intentFilter);
    
                        String newAction = intentFilter.getAction(0);
                        ReceiverManager.pluginReceiverMappings.put(oldAction, newAction);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public class StubReceiver extends BroadcastReceiver {
        public StubReceiver() {
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            String newAction = intent.getAction();
            if(ReceiverManager.pluginReceiverMappings.containsKey(newAction)) {
                String oldAction = ReceiverManager.pluginReceiverMappings.get(newAction);
                context.sendBroadcast(new Intent(oldAction));
            }
        }
    }

    缺点是要为StubReceiver配置几百个Action,无法避免

    欢迎关注我的微信公众号:安卓圈

  • 相关阅读:
    java 微信公众服务平台 下发 模板消息
    java web实现 忘记密码(找回密码)功能及代码
    DES加密后get获取url参数无法解密问题
    java实现url转码、解码
    java 实现 DES加密 解密算法
    oracle sqlplus 导出csv文件
    oracle导出多CSV文件的靠谱的
    Oracle 释放过度使用的Undo表空间
    Archiving not possible: No primary destinations errors
    Linux 之 shell 比较运算符
  • 原文地址:https://www.cnblogs.com/anni-qianqian/p/10108871.html
Copyright © 2011-2022 走看看