第一代插件化:代理實現(xiàn)(dynamic-load-apk)

第一代插件化:代理實現(xiàn)(dynamic-load-apk).jpg

原理與背景

如何啟動未安裝的apk(四大組件)里的類。如何加載類、加載資源、管理組件生命周期。

類加載

外部的dex文件,通過DexClassLoader類加載。根據(jù)插件的apk路徑,構(gòu)造對應的類加載器:

    /**
     * 類加載:
     * 外部dex文件,通過 DexClassLoader 類加載。
     * @param apkPath apk路徑
     * @return DexClassLoader對象
     */
    private DexClassLoader createDexClassLoader(String apkPath) {
        File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
        return new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(), null, mContext.getClassLoader());
    }

資源加載

通過Resource對象加載資源,把插件apk里的資源加載到AssetManager中:

/**
     *
     * @param apkPath apk路徑
     * @return pluginApk
     */
    private PluginApk createApk(String apkPath) {
        String addAssetPathMethod = "addAssetPath";
        PluginApk pluginApk = null;
        try { // 資源加載
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
            addAssetPath.invoke(assetManager, apkPath);// 把資源通過反射加載進去
            Resources pluginRes = new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
            pluginApk = new PluginApk(pluginRes);
            pluginApk.classLoader = createDexClassLoader(apkPath);
        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return pluginApk;
    }

生命周期

早期dynamic-load-apk采用代理方式,通過空殼Activity作為代理(Proxy),系統(tǒng)對該Activity的回調(diào)都映射到插件Activity。插件Activity都繼承這個PluginActivity,侵入性強。避免侵入性成了二代插件化框架目標。

代理實現(xiàn)

在plugin(module庫library級別)中:

PluginManager類來實現(xiàn)插件的加載,通過Map集合管理所有插件apk。根據(jù)插件apk路徑實現(xiàn)類加載、資源加載、啟動插件activity等。
PluginApk類:是插件apk的實體映射類。
PluginActivity:所有插件都要繼承這個類,這是一個殼子。繼承了Activity的一些方法。
ProxyActivity:系統(tǒng)實際啟動的類,必須在主app中注冊(代理,啟動的時候躲過主app對插件activity注冊檢查),系統(tǒng)通過該類觸發(fā)對應方法。代理給LifeCircleController去具體實現(xiàn)。
LifeCircleController:具體實現(xiàn)類,系統(tǒng)實際啟動的類。

在插件(pluginapp)

只需要繼承plugin(library)中的PluginActivity就行:

MainActivity extends PluginActivity

我是直接生成plugin.apk(debug.apk方便我調(diào)試)。如果直接運行這個apk會包錯

java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.res.Resources.getBoolean(int)'

但是不影響生成apk。把這個plugin.apk放到sdcard/plugins文件下。方便下面主app去加載。

在主工程(app)中:

注冊:

        <!--注冊 躲過插件activity注冊的檢查 -->
        <activity android:name="com.george.plugin.ProxyActivity" />

添加權(quán)限:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

啟動插件pluginapp代碼:根據(jù)路徑和包名啟動

public class MainActivity extends Activity {
    public final static String PLUGIN_NAME = "plugin.apk";
    public final static String PLUGIN_PACKAGE_NAME = "com.george.pluginapp";
    public final static String PLUGIN_CLAZZ_NAME = "com.george.pluginapp.MainActivity";
    private PluginManager mPluginManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.tv_content).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.putExtra(Constants.PACKAGE_NAME, PLUGIN_PACKAGE_NAME);
                intent.putExtra(Constants.PLUGIN_CLASS_NAME, PLUGIN_CLAZZ_NAME);
                mPluginManager.startActivity(intent);
            }
        });
        Utils.verifyStoragePermissions(this);

        PluginManager.init(getApplicationContext());// 初始化插件加載器
        mPluginManager = PluginManager.getInstance();
        String pluginApkPath = Environment.getExternalStorageDirectory() + File.separator + "plugins" + File.separator + PLUGIN_NAME;// 插件apk路徑
        VLog.log("can read: " + Environment.getExternalStorageDirectory().canRead());
        VLog.log(pluginApkPath);
        mPluginManager.loadApk(pluginApkPath);// 插件apk加載進主app中
    }
}

打開主app,點擊跳轉(zhuǎn)插件按鈕,發(fā)現(xiàn)成功加載。
demo地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容