BroadcastReceiver插件化解決方案

  • 2019 年 10 月 7 日
  • 筆記

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,無法避免

–摘自《android插件化開發指南》