当前位置: 代码迷 >> Android >> dynamic-load-apk插件原理拾掇
  详细解决方案

dynamic-load-apk插件原理拾掇

热度:23   发布时间:2016-04-28 01:10:34.0
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.getPackageArchiveInfoitem.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);//launcherActivityif (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {   item.launcherActivityName = item.packageInfo.activities[0].name;}//launcherServiceif (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加载资源:

@Overridepublic 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加载资源:

@Overridepublic 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:

@Overridepublic Intent putExtra(String name, Parcelable value) {    setupExtraClassLoader(value);    return super.putExtra(name, value);}@Overridepublic 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')
}

  相关解决方案