转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/39937639 (来自singwhatiwanna的csdn博客)java
很久没有发布新的文章,此次打算发表一下我这几个月的一个核心研究成果:APK动态加载框架(DL)。这段时间我致力于github的开源贡献,开源了2个比较有用且有意义的项目,一个是PinnedHeaderExpandableListView,另外一个是APK动态加载框架。具体能够参见个人github:https://github.com/singwhatiwannaandroid
本次要介绍的是APK动态加载框架(DL),这个项目除了我之外,还有两个共同开发者:田啸(时之沙),宋思宇。git
为了更好地理解本文,你须要首先阅读Android apk动态加载机制的研究这一系列文章,分别为:github
Android apk动态加载机制的研究(二):资源加载和activity生命周期管理
微信
另外,这个开源项目我起了个名字,叫作DL。本文中的DL均指APK动态加载框架。架构
https://github.com/singwhatiwanna/dynamic-load-apk,欢迎star和fork。
框架
运行效果图:eclipse
这里说说这个开源项目的意义。首先要说的是动态加载技术(或者说插件化)在技术驱动型的公司中扮演这至关重要的角色,当项目愈来愈庞大的时候,须要经过插件化来减轻应用的内存和cpu占用,还能够实现热插拔,即在不发布新版本的状况下更新某些模块。ide
我几个月前开始进行这项技术的研究,当时查询了不少资料,没有找到很好的开源。目前淘宝、微信等都有成熟的动态加载框架,包括apkplug,可是它们都是不开源的。还有github上有一个开源项目AndroidDynamicLoader,其思想是经过Fragment 以及 schema的方式实习的,这是一种可行的技术方案,可是还有限制太多,这意味这你的activity必须经过Fragment去实现,这在activity跳转和灵活性上有必定的不便,在实际的使用中会有一些很奇怪的bug很差解决,总之,这仍是一种不是特别完备的动态加载技术。而后,我发现,目前针对动态加载这一块成熟的开源基本仍是空白的,无论是国内仍是国外。而在公司内部,动态加载做为一项核心技术,也不多是初级开发人员所可以接触到的,因而,我决定作一个成熟点的开源,期待能填补这一块空白。
DL支持不少特性,而这些特性使得插件的开发过程变得透明、高效。
1. plugin无需安装便可由宿主调起。
2. 支持用R访问plugin资源4. 基本无反射调用
5. 插件安装后仍可独立运行从而便于调试
6. 支持3种plugin对host的调用模式:(3)彻底调用,plugin能够彻底调用host内容。这种模式适用于plugin开发者能得到host代码的状况。
7. 只需引入DL的一个jar包便可高效开发插件,DL的工做过程对开发者彻底透明
8. 支持android2.x版本若是你们阅读过本文头部提到的两篇文章,那么对DL的架构应该有大体的了解,本文就再也不从头开始介绍了,而是从以下变动的几方面进行解析,这些优化使得DL的功能和以前比起来更增强大更加易用使用易于扩展。
1. DL对activity生命周期管理的改进
2. DL对类加载器的支持(DLClassLoader)
3. DL对宿主(host)和插件(plugin)通讯的支持
4. DL对插件独立运行的支持
5. DL对activity随意跳转的支持(DLIntent)
6. DL对插件管理的支持(DLPluginManager)
其中5和6属于增强功能,目前正在dev分支上进行开发(本文暂不介绍),其余功能均在稳定版分支master上。
你们知道,DL最开始的时候采用反射去管理activity的生命周期,这样存在一些不便,好比反射代码写起来复杂,而且过多使用反射有必定的性能开销。针对这个问题,咱们采用了接口机制,将activity的大部分生命周期方法提取出来做为一个接口(DLPlugin),而后经过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,而且没有采用反射,当咱们想增长一个新的生命周期方法的时候,只须要在接口中声明一下同时在代理activity中实现一下便可,下面看一下代码:
接口DLPlugin
public interface DLPlugin { 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 onCreate(Bundle savedInstanceState); public void setProxy(Activity proxyActivity, String dexPath); 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(); }在代理类DLProxyActivity中的实现
... @Override protected void onStart() { mRemoteActivity.onStart(); super.onStart(); } @Override protected void onRestart() { mRemoteActivity.onRestart(); super.onRestart(); } @Override protected void onResume() { mRemoteActivity.onResume(); super.onResume(); } @Override protected void onPause() { mRemoteActivity.onPause(); super.onPause(); } @Override protected void onStop() { mRemoteActivity.onStop(); super.onStop(); } ...说明:经过上述代码应该不难理解DL对activity生命周期的管理,其中mRemoteActivity就是DLPlugin的实现。
为了更好地对多插件进行支持,咱们提供了一个DLClassoader类,专门去管理各个插件的DexClassoader,这样,同一个插件就能够采用同一个ClassLoader去加载类从而避免多个classloader加载同一个类时所引起的类型转换错误。
public class DLClassLoader extends DexClassLoader { private static final String TAG = "DLClassLoader"; private static final HashMap<String, DLClassLoader> mPluginClassLoaders = new HashMap<String, DLClassLoader>(); protected DLClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } /** * return a available classloader which belongs to different apk */ public static DLClassLoader getClassLoader(String dexPath, Context context, ClassLoader parentLoader) { DLClassLoader dLClassLoader = mPluginClassLoaders.get(dexPath); if (dLClassLoader != null) return dLClassLoader; File dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE); final String dexOutputPath = dexOutputDir.getAbsolutePath(); dLClassLoader = new DLClassLoader(dexPath, dexOutputPath, null, parentLoader); mPluginClassLoaders.put(dexPath, dLClassLoader); return dLClassLoader; } }
这一点很重要,由于每每宿主须要和插件进行各类通讯,所以DL对宿主和插件的通讯作了很好的支持,目前总共有3中模式,以下图所示:
下面分别介绍上述三种模式,针对上述三种模式,咱们分别提供了3组例子,其中:
1. depend_on_host:插件彻底依赖宿主的模式,适合于可以能到宿主的源代码的状况
其中host指宿主工程,plugin指插件工程
2. depend_on_interface:插件部分依赖宿主的模式,或者说插件依赖宿主提供的接口,适合可以拿到宿主的接口的状况
其中host指宿主工程,plugin指插件工程,common指接口工程
3. main:插件不依赖宿主的模式,这是DL推荐的模式
其中host指宿主工程,plugin指插件工程
模式1:这是DL推荐的模式,对应的工程目录为main。在这种模式下,宿主和插件不须要通讯,二者是独立开发的,宿主引用DL的jar包(dl-lib.jar),插件也须要引用DL的jar包,可是不能放入到插件工程的libs目录下面,换句话说,就是插件编译的时候依赖jar包可是打包成apk的时候不要把jar包打进去,这是由于,dl-lib.jar已经在宿主工程中存在了,若是插件中也有这个jar包,就会发生类连接错误,缘由很简单,内存中有两份同样的类,重复了。至于support-v4也是一样的道理。对于eclipse很简单,只须要在插件工程中建立一个目录,好比external-jars,而后把dl-lib.jar和support-v4.jar放进去,同时在.classpath中追加以下两句便可:
<classpathentry kind="lib" path="external-jars/dl-lib.jar"/>
<classpathentry kind="lib" path="external-jars/android-support-v4.jar"/>
这样,编译的时候就可以正常进行,可是打包的时候,就不会把上面两个jar包打入到插件apk中。
至于ant环境和gradle,解决办法不同,具体方法后面再补上,可是思想都是同样的,即:插件apk中不要打入上述2个jar包。
模式2:插件部分依赖宿主的模式,或者说插件依赖宿主提供的接口,适合可以拿到宿主的接口的状况。在这种模式下,宿主放出一些接口并实现一些接口,而后给插件调用,这样插件就能够访问宿主的一些服务等
模式3:插件彻底依赖宿主的模式,适合于可以能到宿主的源代码的状况。这种模式通常多用在公司内部,插件能够访问宿主的全部代码,可是,这样插件和宿主的耦合比较高,宿主一动,插件就必须动,比较麻烦
具体采用哪一种方式,须要结合实际状况来选择,通常来讲,若是是宿主和插件不是同一个公司开发,建议选择模式1和模式2;若是宿主和插件都在同一个公司开发,那么选择哪一个均可以。从DL的实现出发,咱们推荐采用模式1,真的须要通讯的话采用模式2,尽可能不要采用模式3.
为了便于调试,采用DL所开发的插件均可以独立运行,固然,这要分状况来讲:
对于模式1,若是插件想独立运行,只须要把external-jars下的jar包拷贝一份到插件的libs目录下便可
对于模式2,只须要提供一个宿主接口的默认实现便可
对于模式3,只须要apk打包时把所引用的宿主代码打包进去便可,具体方式能够参看sample/depend_on_host目录。
在开发过程当中,应该先开启插件的独立运行功能以便于调试,等功能开发完毕后再将其插件化。
这两项都属于增强功能,目前正在dev分支进行code review,你们感兴趣能够去dev分支上查看,等验证经过即merge到稳定版master分支。
DLIntent:经过DLIntent来完成activity的无约束调起
DLPluginManager:对宿主的全部插件提供综合管理功能。
目前DL已经达到了第一个稳定版,通过大量机型的验证,目前得出的结论是DL是可靠的(兼容到android2.x),能够用在实际的应用开发中。可是,咱们知道,动态加载是一个技术壁垒,其很难达到一种完美的状态,毕竟,让一个apk不安装跑起来,这是多么难以想象的事情。所以,但愿你们辩证地去看这个问题,下面列出咱们目前还不支持的功能,或者说是一种开发规范吧,但愿你们在开发过程当中去遵照这个规范,这样才能让插件稳定地跑起来。
DL 1.0开发规范:
1. 目前不支持service
2. 目前只支持动态注册广播
3. 目前支持Activity和FragmentActivity,这也是经常使用的activity
4. 目前不支持插件中的assets
5. 调用Context的时候,请适当使用that,大部分经常使用api是不须要用that的,可是一些不经常使用api仍是须要用that来访问。that是apk中activity的基类BaseActivity系列中的一个成员,它在apk安装运行的时候指向this,而在未安装的时候指向宿主程序中的代理activity,因为that的动态分配特性,经过that去调用activity的成员方法,在apk安装之后仍然能够正常运行。
6. 慎重使用this,由于this指向的是当前对象,即apk中的activity,可是因为activity已经不是常规意义上的activity,因此this是没有意义的,可是,当this表示的不是Context对象的时候除外,好比this表示一个由activity实现的接口。
但愿可以给你们带来一些帮助,但愿你们多多支持!
本开源项目地址:https://github.com/singwhatiwanna/dynamic-load-apk,欢迎你们star和fork。