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')
}
dynamic-load-apk插件原理整理的更多相关文章
- Android 换肤功能的实现(Apk插件方式)
一.概述 由于Android 没有提供一套统一的换肤机制,我猜可能是因为国外更注重功能和体验的原因 所以国内如果要做一个漂亮的换肤方案,需要自己去实现. 目前换肤的方法大概有三种方案: (1)把皮肤资 ...
- APK签名原理
网上已有多篇分析签名的类似文章,但是都有一个共同的问题,就是概念混乱,混乱的一塌糊涂. 在了解APK签名原理之前,首先澄清几个概念: 消息摘要 -Message Digest 简称摘要,请看英文翻译, ...
- Mybatis插件原理分析(二)
在上一篇中Mybatis插件原理分析(一)中我们主要介绍了一下Mybatis插件相关的几个类的源码,并对源码进行了一些解释,接下来我们通过一个简单的插件实现来对Mybatis插件的运行流程进行分析. ...
- mybatis 插件原理
[传送门]:mybatis 插件原理
- MyBATIS插件原理第一篇——技术基础(反射和JDK动态代理)(转)
在介绍MyBATIS插件原理前我们需要先学习一下一些基础的知识,否则我们是很难理解MyBATIS的运行原理和插件原理的. MyBATIS最主要的是反射和动态代理技术,让我们首先先熟悉它们. 1:Jav ...
- 如何编写一个WebPack的插件原理及实践
_ 阅读目录 一:webpack插件的基本原理 二:理解 Compiler对象 和 Compilation 对象 三:插件中常用的API 四:编写插件实战 回到顶部 一:webpack插件的基本原理 ...
- Mybatis框架(8)---Mybatis插件原理
Mybatis插件原理 在实际开发过程中,我们经常使用的Mybaits插件就是分页插件了,通过分页插件我们可以在不用写count语句和limit的情况下就可以获取分页后的数据,给我们开发带来很大 的便 ...
- mybatis插件机制及分页插件原理
MyBatis 插件原理与自定义插件: MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要注意的是,如果没有完全理解MyBatis 的运行原理和插件的工作方式 ...
- mybatis(六)插件机制及分页插件原理
转载:https://www.cnblogs.com/wuzhenzhao/p/11120848.html MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要 ...
随机推荐
- 走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享
在上周解决“博客程序异步化改造之后遭遇的性能问题”的过程中,我们干了一件自以为很有成就感的事——在表现层(MVC与WebForms)将所有使用await的地方都加上了ConfigureAwait(fa ...
- 清除Windows的DNS缓存
最近ESET杀毒软件老是提示受到DNS缓存攻击,然后就不能打开网页,或者打开得很慢.这是由于缓存的DNS被更改,访问的是错误的IP地址造成的. 解决的办法就是清除DNS缓存,打开DOS命令窗口,先后输 ...
- Eclipse的SVN插件下载
Links for 1.8.x Release:Eclipse update site URL: http://subclipse.tigris.org/update_1.8.xsvn插件包下载: h ...
- Scalaz(53)- scalaz-stream: 程序运算器-application scenario
从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...
- php中读写excel表格文件示例。
测试环境:php5.6.24.这块没啥兼容问题. 需要更多栗子,请看PHPExcel的examples.还是蛮强大的. 读取excel文件. 第一步.下载开源的PHPExcel的类库文件,官方网站是h ...
- ahjesus 单词单数-复数相互转换C#
看codesmith内置的模板在生成存储过程的时候有单复数的转换,用相同的函数名实现了一个 public static class StringUtil { /// <summary> / ...
- java注释规范
前言: 现在java的出产地sun公司并没有定义一个java注释规范,注释规范目前是每个公司自己有自己的一套规范,主要是为了团队之间的协作. 1.基本规则 1.注释应该使代码更加清 ...
- GJM : 用JIRA管理你的项目(一)JIRA环境搭建 [转载]
感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...
- JS代码实用代码实例(输入框监听,点击显示点击其他地方消失,文件本地预览上传)
前段时间写前端,遇到一些模块非常有用,总结以备后用 一.input框字数监听 <!DOCTYPE html> <html lang="en"> <he ...
- Typescript 中类的继承
Typescript中类的定义与继承与后端开发语言java/C#等非常像,实现起来非常方便,而且代码便于阅读. 用Typescript写较大项目时是非常有优势的. /** * BaseClass */ ...