APK动态加载框架(DL)解析

转载请注明出处: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动态加载机制的研究
api

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功能介绍

DL支持不少特性,而这些特性使得插件的开发过程变得透明、高效。

1. plugin无需安装便可由宿主调起。

2. 支持用R访问plugin资源
3. plugin支持Activity和FragmentActivity(将来还将支持其余组件)

4. 基本无反射调用 

5. 插件安装后仍可独立运行从而便于调试

6. 支持3种plugin对host的调用模式:
   (1)无调用(但仍然能够用反射调用)。
   (2)部分调用,host可公开部分接口供plugin调用。 这前两种模式适用于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生命周期管理的改进

你们知道,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的实现。

DL对类加载器的支持

为了更好地对多插件进行支持,咱们提供了一个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对宿主(host)和插件(plugin)通讯的支持

这一点很重要,由于每每宿主须要和插件进行各类通讯,所以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对插件独立运行的支持

为了便于调试,采用DL所开发的插件均可以独立运行,固然,这要分状况来讲:

对于模式1,若是插件想独立运行,只须要把external-jars下的jar包拷贝一份到插件的libs目录下便可

对于模式2,只须要提供一个宿主接口的默认实现便可

对于模式3,只须要apk打包时把所引用的宿主代码打包进去便可,具体方式能够参看sample/depend_on_host目录。

在开发过程当中,应该先开启插件的独立运行功能以便于调试,等功能开发完毕后再将其插件化。

DLIntent和DLPluginManager

这两项都属于增强功能,目前正在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。

相关文章
相关标签/搜索