DynamicLoadApk插件框架的動(dòng)態(tài)加載代碼分析

導(dǎo)讀:學(xué)習(xí)了任玉剛的動(dòng)態(tài)加載框架技術(shù),進(jìn)行插件化的開發(fā),實(shí)現(xiàn)host項(xiàng)目+plugin項(xiàng)目的獨(dú)立開發(fā),和動(dòng)態(tài)加載,這里通過demo詳細(xì)梳理一下流程

DynamicLoaderApkDemo 項(xiàng)目

github地址: https://github.com/George-Soros/DynamicLoaderApkDemo

一:介紹代碼結(jié)構(gòu)

DynamicLoaderApkdemo中包括了HostApp,PluginApp ,先介紹一下他們:

1:HostApp

com.ryg.dynamicload包中的文件是動(dòng)態(tài)插件框架提供的,其中包括了DL相關(guān)的代理類。


image.png

com.ryg.sample包中的MainActivity是一個(gè)啟動(dòng)頁面如下:

image.png

2:PluginApp

插件需要加入動(dòng)態(tài)加載庫

其中l(wèi)ibs中加入動(dòng)態(tài)加載庫dl-apk.jar包,它只參與編譯,不打包到apk中,這個(gè)包就是host中的com.ryg.dynamic下面的代碼打包而來!

dependencies {
//    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.google.code.gson:gson:2.8.0'
    provided files('libs/dl-lib.jar')
}
image.png
頁面繼承動(dòng)態(tài)庫頁面

com.practise.pluginapp.MainActivity,啟動(dòng)頁面,需要繼承了動(dòng)態(tài)加載庫提供的DLBasePluginFragmentActivity類(當(dāng)然可以是DLBasePluginActivity,或者對(duì)于service等),方便實(shí)現(xiàn)host加載時(shí)代理該類Activity的責(zé)任和生命周期。

二:動(dòng)態(tài)加載分析流程

1:加載插件PluginApp.apk到內(nèi)存中的過程

HostApp->MainActivity->loadPlugin, 加載手機(jī)sd卡中的PluginApp.apk, 其中PluginApp.apk是PluginApp項(xiàng)目打包而來

private void loadPlugin() {

        //apk名稱
        String apkName = "PluginApp.apk";

        //插件apk的位置
        String apkAllPath = Environment.getExternalStorageDirectory().getAbsolutePath()
                + "/" + apkName;

        //加載插件apk到內(nèi)存中
        DLPluginPackage dlPluginPackage = DLPluginManager.getInstance(this).loadApk(apkAllPath);

        if(dlPluginPackage.classLoader != null){
            Toast.makeText(this, "加載插件apk到內(nèi)存中成功!",Toast.LENGTH_SHORT).show();
        }
    }

DLPluginManager.getInstance(this).loadApk(apkAllPath);

上面的這個(gè)邏輯是主要的加載apk到內(nèi)存的過程

 public DLPluginPackage loadApk(String dexPath) {
        // when loadApk is called by host apk, we assume that plugin is invoked
        // by host.
        return loadApk(dexPath, true);
    }

    /**
     * @param dexPath
     *            plugin path
     * @param hasSoLib
     *            whether exist so lib in plugin
     * @return
     */
    public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
        mFrom = DLConstants.FROM_EXTERNAL;

        PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
                PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
        if (packageInfo == null) {
            return null;
        }
        //加載插件apk,獲取到apk的資源
        DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
        if (hasSoLib) {
            copySoLib(dexPath);
        }

        return pluginPackage;
    }

loadApk方法中,主要看

DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);

private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {

        DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
        if (pluginPackage != null) {
            return pluginPackage;
        }
        //下面的3個(gè)主要方法,完成了類加載,和資源的獲取?。?        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        // create pluginPackage
        pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
        mPackagesHolder.put(packageInfo.packageName, pluginPackage);
        return pluginPackage;
    }

其中準(zhǔn)備好了DexClassLoader的類加載,并加載完成插件apk,還有插件apk的資源獲取到了!

mPackagesHolders是一個(gè)Map文件,存儲(chǔ)了對(duì)于插件apk的信息(類加載器,資源等), 方便了下一步啟動(dòng)插件apk中的頁面需要的資源信息等

private DexClassLoader createDexClassLoader(String dexPath) {
        File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
        dexOutputPath = dexOutputDir.getAbsolutePath();
        DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader());
        return loader;
    }

android提供的類加載器DexClassLoader,傳入了apk的路徑,指定class.dex的路徑,本地方法庫,父類加載器等,完成了apk中class.dex文件的加載到內(nèi)存,到這里插件Apk的動(dòng)態(tài)加載就完成了!

2:啟動(dòng)插件頁面,實(shí)現(xiàn)插件的功能

tv_start_plugin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v){
                Toast.makeText(MainActivity.this, "開始啟動(dòng)插件頁面....",Toast.LENGTH_SHORT).show();
                startPluginActivity(MainActivity.this, new DLIntent("com.practise.pluginapp"
                        , "com.practise.pluginapp.TestActivity"));
            }
        });
//傳人的DLIntent中包括了需要啟動(dòng)插件的包名和頁面
private void startPluginActivity(Context context, DLIntent intent) {
        DLPluginManager dlPluginManager = DLPluginManager.getInstance(context);
        if (!dlPluginManager.isHostPackageSet()){
            //當(dāng)前host的包名
            dlPluginManager.setHostPackageName("com.ryg");
        }
        dlPluginManager.startPluginActivity(this, intent);
    }
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
        //如果已經(jīng)執(zhí)行了動(dòng)態(tài)加載apk,mfrom為1跳過if
        if (mFrom == DLConstants.FROM_INTERNAL) {
            if (isHostPackageSet() && dlIntent.getPluginPackage() != null
                    && !hostPackageName.equals(dlIntent.getPluginPackage())){
                return DLPluginManager.START_RESULT_NO_PKG;
            }
            dlIntent.setClassName(context, dlIntent.getPluginClass());
            performStartActivityForResult(context, dlIntent, requestCode);
            return DLPluginManager.START_RESULT_SUCCESS;
        }
        //得到插件的包名
        String packageName = dlIntent.getPluginPackage();
        if (TextUtils.isEmpty(packageName)) {
            throw new NullPointerException("disallow null packageName.");
        }
        //這里看到mPackagesHolder這個(gè)map獲取之前保存的信息
        DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
        if (pluginPackage == null) {
            return START_RESULT_NO_PKG;
        }

        final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
        //關(guān)鍵點(diǎn)來了:通過DexClassLoader類加載器將插件頁面(已經(jīng)在內(nèi)存中的)生成對(duì)于的class對(duì)象
        Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
        if (clazz == null) {
            return START_RESULT_NO_CLASS;
        }

        // get the proxy activity class, the proxy activity will launch the
        // plugin activity.
       //根據(jù)生成的對(duì)象,判斷它的代理頁面,就是我們插件頁面繼承的DLBasePluginFragmentActivity,這樣就實(shí)現(xiàn)了啟動(dòng)代理頁面,然后再調(diào)用生成對(duì)象中的方法,代理了插件頁面的生命周期
        Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
        if (activityClass == null) {
            return START_RESULT_TYPE_ERROR;
        }

        // put extra data
        dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
        dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
        dlIntent.setClass(mContext, activityClass);
        performStartActivityForResult(context, dlIntent, requestCode);
        return START_RESULT_SUCCESS;
    }

下面的方法是DexClassLoader來加載生成對(duì)象,不太明白類加載的可以看看:虛擬機(jī)的類加載器理解和實(shí)踐

private Class<?> loadPluginClass(ClassLoader classLoader, String className) {
        Class<?> clazz = null;
        try {
            clazz = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return clazz;
    }

判斷class對(duì)象是繼承的哪個(gè)頁面

private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
        Class<? extends Activity> activityClass = null;
        if (DLBasePluginActivity.class.isAssignableFrom(clazz)) {
            activityClass = DLProxyActivity.class;
        } else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) {
            activityClass = DLProxyFragmentActivity.class;
        }

        return activityClass;
    }

這樣根據(jù)生成的對(duì)象,判斷它的代理頁面,就是我們插件頁面繼承的DLBasePluginFragmentActivity,這樣就實(shí)現(xiàn)了啟動(dòng)代理頁面DLProxyFragmentActivity。

來看看DLProxyFragmentActivity,
public class DLProxyFragmentActivity extends FragmentActivity implements DLAttachable {

    protected DLPlugin mRemoteActivity;
    private DLProxyImpl impl = new DLProxyImpl(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /**
         * DLProxyImpl中啟動(dòng)的插件的oncreate (mPluginActivity.onCreate(bundle);)
         *
         * 相當(dāng)于在當(dāng)前調(diào)用  mRemoteActivity.onCreate(savedInstanceState);
         */
        impl.onCreate(getIntent());
    }

    /**
     * 通過這個(gè)DlProxyFragmentActivity 代理
     * @param remoteActivity
     * @param pluginManager DLPluginManager instance, manager the plugins
     */
    @Override
    public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
        mRemoteActivity = remoteActivity;
    }

    @Override
    public AssetManager getAssets() {
        return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
    }

    @Override
    public Resources getResources() {
        return impl.getResources() == null ? super.getResources() : impl.getResources();
    }

    @Override
    public Theme getTheme() {
        return impl.getTheme() == null ? super.getTheme() : impl.getTheme();
    }

    @Override
    public ClassLoader getClassLoader() {
        return impl.getClassLoader();
    }

........
其中的一個(gè)關(guān)鍵, DLProxyImpl實(shí)現(xiàn)了host代理和插件頁面的類加載等,通過attach接口實(shí)現(xiàn)了關(guān)聯(lián)!

private DLProxyImpl impl = new DLProxyImpl(this);

public void onCreate(Intent intent) {

        // set the extra's class loader
        intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);

        mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
        mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
        Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);

        mPluginManager = DLPluginManager.getInstance(mProxyActivity);
        mPluginPackage = mPluginManager.getPackage(mPackageName);
        if (mPluginPackage == null){
            if (mProxyActivity != null) {
                mProxyActivity.finish();
            }
            return;
        }
        mAssetManager = mPluginPackage.assetManager;
        mResources = mPluginPackage.resources;

        initializeActivityInfo();
        handleActivityInfo();
        launchTargetActivity();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    protected void launchTargetActivity() {
        try {
            Class<?> localClass = getClassLoader().loadClass(mClass);
            Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
            Object instance = localConstructor.newInstance(new Object[] {});
            mPluginActivity = (DLPlugin) instance;
            ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
            Log.d(TAG, "instance = " + instance);
            // attach the proxy activity and plugin package to the mPluginActivity
            mPluginActivity.attach(mProxyActivity, mPluginPackage);

            Bundle bundle = new Bundle();
            bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
            mPluginActivity.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
最后attach方法中傳入了插件頁面,當(dāng)啟動(dòng)host的代理DLProxyFragmentActivity的生命周期,就調(diào)用插件mRemoteActivity的生命周期,實(shí)現(xiàn)了代理,插件頁面的生命周期。 代理模式看結(jié)構(gòu)型設(shè)計(jì)模式之代理, 享元, 門面, 橋接

然后就啟動(dòng)了插件頁面,如下:


image.png

需要看動(dòng)態(tài)加載插件開發(fā)的參考:
https://github.com/singwhatiwanna/dynamic-load-apk 開源代碼dlapk-lib

http://blog.csdn.net/singwhatiwanna/article/details/39937639 任玉剛的分析

http://blog.csdn.net/fanpeihua123/article/details/51364521 范陪華的分析 , 很棒, 重點(diǎn)看 ! !

http://blog.csdn.net/u012124438/article/details/53241755 在范陪華文章,加入了更多的代碼解讀

http://blog.csdn.net/u012124438/article/details/53242838 在范陪華文章,加入了更多的代碼解讀

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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