首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Android 插件化核心技術:動態加載 Activity 的兩種方案深度剖析

     編輯:Java 相關知識

背景:為什麼需要動態加載 Activity?

在 Android 插件化、熱修復、模組化解耦場景中,宿主 App 需要在運行時加載外部 APK(插件)中的 Activity。由於 Android 系統只識別安裝時註冊在 AndroidManifest.xml 中的組件,直接 startActivity 插件類會拋出 ClassNotFoundException。本文深入剖析兩種主流繞過方案:**替換系統類加載器** 與 **代理 Activity 模式**。


方案一:替換系統類加載器(Hook ClassLoader)

核心原理

Android 啟動 Activity 時,最終在 ActivityThread.performLaunchActivity() 中通過 LoadedApk.getClassLoader() 獲取類加載器(預設為 PathClassLoader,僅能加載已安裝 APK)。若將宿主 LoadedApk 中的 mClassLoader 替換為可加載插件 APK 的 DexClassLoader,系統即可正常實例化插件 Activity。

關鍵步驟

  1. 創建 DexClassLoader,指定插件 APK 路徑、優化目錄、父加載器(宿主 ClassLoader,保證雙親委派)。
  2. 反射獲取 ActivityThread 單例 → mPackages Map → 宿主包名對應的 LoadedApk。
  3. 反射修改 LoadedApk.mClassLoader 為步驟 1 創建的 DexClassLoader。
  4. 在 AndroidManifest 註冊插件 Activity(或通過動態註冊技術繞過)。
  5. startActivity(new Intent(this, PluginActivity.class)) 正常啟動。

核心代碼

private void replaceClassLoader(DexClassLoader pluginLoader) {
    try {
        Class atClass = Class.forName("android.app.ActivityThread");
        Class laClass = Class.forName("android.app.LoadedApk");
        
        Object currentAT = atClass.getMethod("currentActivityThread").invoke(null);
        Field pkgsField = atClass.getDeclaredField("mPackages");
        pkgsField.setAccessible(true);
        @SuppressWarnings("unchecked")
        Map> mPackages = (Map) pkgsField.get(currentAT);
        
        WeakReference ref = mPackages.get(getPackageName());
        Object loadedApk = ref.get();
        
        Field clField = laClass.getDeclaredField("mClassLoader");
        clField.setAccessible(true);
        clField.set(loadedApk, pluginLoader); // 關鍵替換
    } catch (Exception e) { throw new RuntimeException(e); }
}

資源訪問問題與解決

替換類加載器後,R.layout.xxx 仍指向宿主資源 ID。插件佈局需單獨加載:

// 宿主中預先創建包含插件資源的 Resources
AssetManager assetManager = AssetManager.class.newInstance();
assetManager.getClass().getMethod("addAssetPath", String.class).invoke(assetManager, pluginApkPath);
Resources pluginRes = new Resources(assetManager, getResources().getDisplayMetrics(), getResources().getConfiguration());

// 插件 Activity 中通過反射設置佈局
View view = LayoutInflater.from(hostContext).inflate(pluginRes.getLayout(R.layout.plugin_main), null);
hostContext.setContentView(view);

並重寫宿主 Activity 的 getResources()、getAssets() 返回插件資源對象。

優缺點

優點缺點
系統完整管理生命週期,無需手動轉發依賴反射私有 API(mPackages、mClassLoader),Android 版本升級易失效
插件 Activity 可直接用系統服務、主題、返回棧需在宿主 Manifest 靜態註冊所有插件 Activity(或動態註冊更複雜)
代碼侵入性小,插件寫法與普通 Activity 一致資源隔離需額外處理,多插件類加載器隔離較麻煩

方案二:代理 Activity 模式

核心原理

宿主預先在 Manifest 註冊一個通用 ProxyActivity。啟動時傳遞插件 Activity 類名作為 Intent Extra。ProxyActivity 在自身生命週期中,通過 DexClassLoader 加載插件類,反射實例化並轉發 onCreate/Start/Resume/Pause/Stop/Destroy 等回調。插件類不繼承 Activity,而是實現一個自定義介面(如 IPluginActivity)。

關鍵介面

public interface IPluginActivity {
    void onCreate(Bundle savedInstanceState);
    void onStart();
    void onResume();
    void onPause();
    void onStop();
    void onDestroy();
    void setProxyContext(Context proxy); // 提供 Context 能力
}

ProxyActivity 實現

public class ProxyActivity extends AppCompatActivity {
    private IPluginActivity plugin;
    private DexClassLoader loader;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String className = getIntent().getStringExtra("plugin_class");
        loader = new DexClassLoader(pluginApkPath, getDir("dex", MODE_PRIVATE).getAbsolutePath(), null, getClassLoader());
        Class clazz = loader.loadClass(className);
        plugin = (IPluginActivity) clazz.newInstance();
        plugin.setProxyContext(this); // 關鍵:提供 Context
        plugin.onCreate(savedInstanceState);
    }
    @Override protected void onStart() { super.onStart(); plugin.onStart(); }
    @Override protected void onResume() { super.onResume(); plugin.onResume(); }
    @Override protected void onPause() { super.onPause(); plugin.onPause(); }
    @Override protected void onStop() { super.onStop(); plugin.onStop(); }
    @Override protected void onDestroy() { super.onDestroy(); plugin.onDestroy(); }
}

插件實現範例

public class PluginMain implements IPluginActivity {
    private Context proxyContext;
    @Override public void setProxyContext(Context ctx) { this.proxyContext = ctx; }
    @Override public void onCreate(Bundle s) {
        TextView tv = new TextView(proxyContext);
        tv.setText("來自插件的 UI");
        ((Activity)proxyContext).setContentView(tv);
    }
    // 其他生命週期...
    @Override public void onStart() { Toast.makeText(proxyContext, "插件 onStart", 0).show(); }
}

啟動方式

Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("plugin_class", "com.plugin.PluginMain");
startActivity(intent);

優缺點

優點缺點
不依賴系統私有 API,相容性極強需手動轉發所有生命週期、觸摸事件、權限回調、ActivityResult 等,樣板代碼多
無需在宿主 Manifest 註冊具體插件 Activity插件無法直接使用 this 作為 Context,需持有代理引用
天然支援多插件隔離(每插件一個 ClassLoader)系統服務(如 ActionBar、FragmentManager)綁定在 Proxy 上,插件使用受限

方案選型建議

  • 替換 ClassLoader:適合少量、固定插件,追求原生體驗、團隊有能力維護版本適配。
  • 代理 Activity:適合動態下發、大量、頻繁更新的插件場景(如小程序容器、業務熱插拔),犧牲部分原生特性換取穩定性。

成熟框架(VirtualApk、RePlugin、Atlas)多採用混合策略:核心組件用代理,關鍵頁面通過 Hook AMS/PMS 實現真組件啟動。


總結

動態加載 Activity 的本質是打破「安裝時靜態註冊」與「運行時動態代碼」的矛盾。替換類加載器從「類加載入口」切入,代理模式從「組件生命週期入口」切入。理解兩者的核心流程——performLaunchActivity → ClassLoader → newInstance 與 ProxyActivity → 反射轉發,是掌握 Android 插件化技術的關鍵基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved