dynamic-load-apk插件原理整理


  因為當前項目功能越來越多,編譯速度越來越慢(公司電腦配置也挺差的...),並且方法數已超出65535的限制了,雖然通過multidex暫時解決了,但是這並不是一個好的解決方式。所以通過插件來加快編譯速度以及解決方法數的限制,算是一個越來越重要的任務了,工作中還有很多新需求,所以趁放假的2天研究了下現在比較流行的插件框架dynamic-load-apk,並整理了下。

 

框架github地址:https://github.com/singwhatiwanna/dynamic-load-apk

lib module的svn地址:https://github.com/singwhatiwanna/dynamic-load-apk/trunk/DynamicLoadApk/lib

 

一、加載apk總流程:

//插件文件
File plugin = new File(apkPath);
PluginItem item = new PluginItem();
//插件文件路徑
item.pluginPath = plugin.getAbsolutePath();
//PackageInfo = PackageManager.getPackageArchiveInfo
item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
//launcherActivity
if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
   item.launcherActivityName = item.packageInfo.activities[0].name;
}
//launcherService
if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
    item.launcherServiceName = item.packageInfo.services[0].name;
}
//加載apk信息
DLPluginManager.getInstance(this).loadApk(item.pluginPath);

 

 

二、loadApk信息過程:
1、createDexClassLoader:

private DexClassLoader createDexClassLoader(String dexPath) {
    dexOutputPath = mContext.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath();
    DexClassLoader loader = new DexClassLoader(dexPath,
            dexOutputPath,  //getDir("dex", Context.MODE_PRIVATE)
            mNativeLibDir,  //optimizedDirectory=getDir("pluginlib", Context.MODE_PRIVATE)
            mContext.getClassLoader());  //host.Appliceation.getClassLoader()
    return loader;
}

 

2、createAssetManager:

private AssetManager createAssetManager(String dexPath) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        //通過反射調用addAssetPath方法,將apk資源加載到AssetManager
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        addAssetPath.invoke(assetManager, dexPath);
        return assetManager;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

后面會重寫DLProxyActivity的getAssets()方法,返回此處生成的AssetManager,從而實現從插件apk加載資源:

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

 

3、createResources:

private Resources createResources(AssetManager assetManager) {
    //通過剛創建的assetManager以及宿主程序的Resources創建Plugin的Resources
    Resources superRes = mContext.getResources();
    Resources resources = new Resources(assetManager,
            superRes.getDisplayMetrics(),
            superRes.getConfiguration());
    return resources;
}

后面會重寫DLProxyActivity的getResources()方法,返回此處生成的Resources,從而實現從插件apk加載資源:

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

 

4、創建pluginPackage並通過插件的packageName保存插件信息:
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
mPackagesHolder.put(packageInfo.packageName, pluginPackage);

5、copySoLib(拷貝so文件到應用的pluginlib目錄下):
SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);

 

三、調用插件
1、要向插件Intent傳遞可序列化對象,必須通過DLIntent,設置Bundle的ClassLoader:

@Override
public Intent putExtra(String name, Parcelable value) {
    setupExtraClassLoader(value);
    return super.putExtra(name, value);
}
@Override
public Intent putExtra(String name, Serializable value) {
    setupExtraClassLoader(value);
    return super.putExtra(name, value);
}
private void setupExtraClassLoader(Object value) {
    ClassLoader pluginLoader = value.getClass().getClassLoader();
    DLConfigs.sPluginClassloader = pluginLoader;
    setExtrasClassLoader(pluginLoader); //設置Bundle的ClassLoader
}

 

2、startPluginActivity:
插件內部的activity之間相互調用,需要使用此方法。

public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
    if (mFrom == DLConstants.FROM_INTERNAL) {
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        performStartActivityForResult(context, dlIntent, requestCode);
        return DLPluginManager.START_RESULT_SUCCESS;
    }
    String packageName = dlIntent.getPluginPackage();
    //驗證intent的包名
    if (TextUtils.isEmpty(packageName)) {
        throw new NullPointerException("disallow null packageName.");
    }
    //檢測插件是否加載
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        return START_RESULT_NO_PKG;
    }
    //要調用的插件Activity的class完整路徑
    final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
    //Class.forName
    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
    if (clazz == null) {
        return START_RESULT_NO_CLASS;
    }
    //獲取代理Activity的class,DLProxyActivity/DLProxyFragmentActivity
    Class<? extends Activity> proxyActivityClass = getProxyActivityClass(clazz);
    if (proxyActivityClass == null) {
        return START_RESULT_TYPE_ERROR;
    }
    //put extra data
    dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
    dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
    dlIntent.setClass(mContext, proxyActivityClass);
    //通過context啟動宿主Activity
    performStartActivityForResult(context, dlIntent, requestCode);
    return START_RESULT_SUCCESS;
}

 

 

四、Activity生命周期的管理
插件apk中的activity其實就是一個普通的對象,不是真正意義上的activity(沒有在宿主程序中注冊且沒有完全初始化),不具有activity的性質,因為系統啟動activity是要做很多初始化工作的,而我們在應用層通過反射去啟動activity是很難完成系統所做的初始化工作的,所以activity的大部分特性都無法使用包括activity的生命周期管理,這就需要我們自己去管理。
DL采用了接口機制,將activity的大部分生命周期方法提取出來作為一個接口(DLPlugin),然后通過代理activity(DLProxyActivity)去調用插件activity實現的生命周期方法,這樣就完成了插件activity的生命周期管理,並且沒有采用反射,當我們想增加一個新的生命周期方法的時候,只需要在接口中聲明一下同時在代理activity中實現一下即可。

public interface DLPlugin {
    public void onCreate(Bundle savedInstanceState);
    public void onStart();
    public void onRestart();
    public void onActivityResult(int requestCode, int resultCode, Intent data);
    public void onResume();
    public void onPause();
    public void onStop();
    public void onDestroy();
    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
    public void onSaveInstanceState(Bundle outState);
    public void onNewIntent(Intent intent);
    public void onRestoreInstanceState(Bundle savedInstanceState);
    public boolean onTouchEvent(MotionEvent event);
    public boolean onKeyUp(int keyCode, KeyEvent event);
    public void onWindowAttributesChanged(LayoutParams params);
    public void onWindowFocusChanged(boolean hasFocus);
    public void onBackPressed();
    public boolean onCreateOptionsMenu(Menu menu);
    public boolean onOptionsItemSelected(MenuItem item);
}

 

DLBasePluginActivity的部分實現:

public class DLBasePluginActivity extends Activity implements DLPlugin {
    /**
     * 代理activity,可以當作Context來使用,會根據需要來決定是否指向this
     */
    protected Activity mProxyActivity;
    /**
     * 等同於mProxyActivity,可以當作Context來使用,會根據需要來決定是否指向this<br/>
     * 替代this來使用(應為this指向的是插件中的Activity,已經不是常規意義上的activity,所以this是沒有意義的)
     * 如果是DLPlugin中已經覆蓋的Activity的方法,就不需使用that了,直接調用this即可
     */
    protected Activity that;
    protected DLPluginManager mPluginManager;
    protected DLPluginPackage mPluginPackage;
    protected int mFrom = DLConstants.FROM_INTERNAL;
    @Override
    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
        mProxyActivity = (Activity) proxyActivity;
        that = mProxyActivity;
        mPluginPackage = pluginPackage;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL);
        }
        if (mFrom == DLConstants.FROM_INTERNAL) {
            super.onCreate(savedInstanceState);
            mProxyActivity = this;
            that = mProxyActivity;
        }
        mPluginManager = DLPluginManager.getInstance(that);
    }
    @Override
    public void setContentView(View view) {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            super.setContentView(view);
        } else {
            mProxyActivity.setContentView(view);
        }
    }
    ......
}

 

在代理類DLProxyActivity中的實現:

public class DLProxyActivity extends Activity implements DLAttachable {
    protected DLPlugin mRemoteActivity;
    private DLProxyImpl impl = new DLProxyImpl(this);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        impl.onCreate(getIntent());
    }
    @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();
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        mRemoteActivity.onActivityResult(requestCode, resultCode, data);
        super.onActivityResult(requestCode, resultCode, data);
    }
    @Override
    protected void onStart() {
        mRemoteActivity.onStart();
        super.onStart();
    }
    ......
}

 

總結:

插件主要的2個問題就是資源加載以及Activity生命周期的管理。

資源加載

通過反射調用AssetManager的addAssetPath方法,我們可以將一個插件apk中的資源加載到AssetManager中,然后再通過AssetManager來創建一個新的Resources對象,然后就可以通過這個Resources對象來訪問插件apk中的資源了。

Activity生命周期管理

采用接口機制,將activity的大部分生命周期方法提取出來作為一個接口(DLPlugin),然后通過代理activity(DLProxyActivity)去調用插件activity實現的生命周期方法,這樣就完成了插件activity的生命周期管理。

 

另外,一個需要注意的地方:

插件項目引用 android-support-v4.jar、lib.jar等libs,生成apk時不能將這些打包到apk,只在編譯時引用,只有host項目里才編譯並打包,保證host以及插件中的代碼只有一份。

在studio里面使用provided而非compile:

dependencies {
  provided files('provide-jars/android-support-v4.jar')
  provided files('provide-jars/lib.jar')
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM