占位式插件化原理

在我看來(lái),插件化的核心目的就是將未安裝的apk代碼,在已經(jīng)安裝的apk中執(zhí)行,未安裝的apk就是插件。

其實(shí)這個(gè)未安裝的apk也只是一個(gè)有固定格式能識(shí)別的文件而已,更廣一點(diǎn)其實(shí)所有的apk也都是有固定格式的文件而已,代碼的執(zhí)行靠的還是虛擬機(jī)運(yùn)行java代碼,最開(kāi)始還是執(zhí)行main函數(shù),只是在函數(shù)中,存在無(wú)限循環(huán)直到應(yīng)用退出而已。

上文只是個(gè)人目前知識(shí)的一個(gè)理解,如有不正確不恰當(dāng)?shù)牡胤秸?qǐng)多多指教。

本文中介紹如何使用占位的方式實(shí)現(xiàn)插件化。

核心原理

首先需要明確一點(diǎn),沒(méi)有安裝的apk不能執(zhí)行其代碼,主要原因有兩點(diǎn):

  • 類或資源沒(méi)有被加載
  • 四大組件沒(méi)有運(yùn)行環(huán)境,就是沒(méi)有上下文對(duì)象(context),沒(méi)有生命周期的

而如果我們使用classloader手動(dòng)加載類文件,使用AssetManager手動(dòng)加入資源文件,然后再傳入一個(gè)對(duì)應(yīng)的上下文對(duì)象,那么缺少的東西就都有了,那么是不是就可以運(yùn)行了呢?答案是肯定的,這里講的占位式插件化就是利用這樣的原理。

而這里傳入的對(duì)應(yīng)的上下文和調(diào)用對(duì)應(yīng)的生命周期,就需要一個(gè)公用的組件來(lái)占位實(shí)現(xiàn)。

說(shuō)完原理,接著我們看看具體是怎么操作的。

實(shí)現(xiàn)

以實(shí)現(xiàn)打開(kāi)插件中Activity為例。

首先需要定義一個(gè)Activity的標(biāo)準(zhǔn),定義需要使用到的生命周期的方法,例如:

public interface IActivity {
    void setHostActivity(Activity host);

    void onCreate(Bundle savedInstanceState);

    void onResume();

    void onPause();

    void onDestroy();
}

其中setHostActivity是把占位的代理Activity的實(shí)例傳給插件中定義的Activity,因?yàn)椴寮袥](méi)有上下文環(huán)境,使用一切Activity相關(guān)的方法都應(yīng)該使用代理Activity的對(duì)應(yīng)方法。

然后在插件中實(shí)現(xiàn)該接口并封裝到BaseActivity中,保存代理Activity的實(shí)例。只要是在插件中使用到Activity的方法時(shí),都在BaseActivity中重寫(xiě)并使用代理Activity的對(duì)應(yīng)方法,這樣插件端的工作就基本完成了。

然后在代理Activity端,需要先把插件包使用dexClassLoader和assetManager加載進(jìn)來(lái):

public void loadPlugin(String path) {
    try {
        File pluginApk = new File(path);
        if (!pluginApk.exists()) {
            Log.d("ljw >>>", "loadPlugin: 插件包" + path + "不存在");
            return;
        }
        File optimizedDirectory = context.getDir("optimizedDirectory", Context.MODE_PRIVATE);
        dexClassLoader = new DexClassLoader(path, optimizedDirectory.getAbsolutePath(),
                null, context.getClassLoader());
        AssetManager assetManager = AssetManager.class.newInstance();
        //private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
        Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
        addAssetPathMethod.invoke(assetManager, path);
        Resources resources = context.getResources();
        pluginResource = new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

然后在啟動(dòng)Activity其實(shí)就是啟動(dòng)代理Activity,傳入需要打開(kāi)的插件Activity的全類名

PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(pluginApkPath, PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[0];//拿的第一個(gè)Activity,真正使用時(shí)這個(gè)地方應(yīng)該去打開(kāi)指定的activity
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra(ProxyActivity.KEY_CLASS_NAME, activityInfo.name);
startActivity(intent);

在ProxyActivity代理Activity中,重寫(xiě)

@Override
public ClassLoader getClassLoader() {
    return PluginManager.get().getDexClassLoader();
}
@Override
public Resources getResources() {
    return PluginManager.get().getPluginResource();
}

在onCreate方法中加載插件的class創(chuàng)建出插件activity對(duì)象,并在對(duì)應(yīng)的生命周期中都調(diào)用插件activity對(duì)象的對(duì)應(yīng)生命周期,當(dāng)然這里的這些生命周期需要在標(biāo)準(zhǔn)中定義好。以onCreate為例:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    String className = getIntent().getStringExtra(KEY_CLASS_NAME);
    iActivity = PluginManager.get().loadActivityClass(this, className);
    if (iActivity == null) {
        return;
    }
    iActivity.onCreate(savedInstanceState);
}

而在上文說(shuō)到的插件Activity中定義的BaseActivity中重寫(xiě)的方法都需要使用這個(gè)代理activity的實(shí)例。例如:

@Override
public void setContentView(int layoutResID) {
    host.setContentView(layoutResID);
}
@Override
public <T extends View> T findViewById(int id) {
    return host.findViewById(id);
}
@Override
public void startActivity(Intent intent) {
    host.startActivity(intent);
}

這樣基本就可以實(shí)現(xiàn)了,需要注意的一點(diǎn)就是這個(gè)宿主Activity不要繼承自AppCompatActivity,目前還不知道原因?yàn)槭裁凑也坏絤DecorContentParent,調(diào)用mDecorContentParent.setWindowCallback(this.getWindowCallback());會(huì)報(bào)空指針。只要繼承Activity就可以解決。

還有其他的對(duì)于Service,BroadcastReceiver也是同樣的方法,先占位,再啟動(dòng)即可。內(nèi)部的跳轉(zhuǎn)也調(diào)用宿主的跳轉(zhuǎn)方法,然后宿主中使用代理的Activity或者Service或者BroadcastReceiver來(lái)代替。

任玉剛大神寫(xiě)的Apk動(dòng)態(tài)加載框架就是這個(gè)原理。

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

相關(guān)閱讀更多精彩內(nèi)容

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