占位式插件化原理

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

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

上文只是個人目前知識的一個理解,如有不正確不恰當?shù)牡胤秸埗喽嘀附獭?/em>

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

核心原理

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

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

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

而這里傳入的對應的上下文和調用對應的生命周期,就需要一個公用的組件來占位實現(xiàn)。

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

實現(xiàn)

以實現(xiàn)打開插件中Activity為例。

首先需要定義一個Activity的標準,定義需要使用到的生命周期的方法,例如:

public interface IActivity {
    void setHostActivity(Activity host);

    void onCreate(Bundle savedInstanceState);

    void onResume();

    void onPause();

    void onDestroy();
}

其中setHostActivity是把占位的代理Activity的實例傳給插件中定義的Activity,因為插件中沒有上下文環(huán)境,使用一切Activity相關的方法都應該使用代理Activity的對應方法。

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

然后在代理Activity端,需要先把插件包使用dexClassLoader和assetManager加載進來:

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();
    }
}

然后在啟動Activity其實就是啟動代理Activity,傳入需要打開的插件Activity的全類名

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

在ProxyActivity代理Activity中,重寫

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

在onCreate方法中加載插件的class創(chuàng)建出插件activity對象,并在對應的生命周期中都調用插件activity對象的對應生命周期,當然這里的這些生命周期需要在標準中定義好。以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);
}

而在上文說到的插件Activity中定義的BaseActivity中重寫的方法都需要使用這個代理activity的實例。例如:

@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);
}

這樣基本就可以實現(xiàn)了,需要注意的一點就是這個宿主Activity不要繼承自AppCompatActivity,目前還不知道原因為什么找不到mDecorContentParent,調用mDecorContentParent.setWindowCallback(this.getWindowCallback());會報空指針。只要繼承Activity就可以解決。

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

任玉剛大神寫的Apk動態(tài)加載框架就是這個原理。

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

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

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