framework插件化技术-类加载

近两年,Android的热升级技术成为Android领域的一个热点。因为快速迭代的须要,热修复,插件化的技术已经深刻app及framework的各个研究领域。java

背景技术

简单的介绍一下热修复技术的背景: 以往的app项目管理流程以下:android

以上流程有版本周期长,用户安装成本高,bug修复不及时用户体验差等诸多缺点。 为了改变这样的现状,各大互联网公司为此投入了不少研究,热修复技术应运而生,把更新以补丁的方式上传到云端,app从云端直接下载补丁即时生效,流程以下:

可见使用热修复技术以后可以实现用户无感知的修复。 在Android的热修复主要分三大领域: 代码修复,资源修复,so修复。
代码修复有两大主要方案:

  1. 阿里系的底层替换方案
  2. 腾讯系的类加载方案

两类方案的优劣:
底层替换方案限制多,但时效性最好,加载轻快,当即见效。
类加载方案时效性差,须要app的冷启动才能见效,可是修复范广,限制少。安全

关于底层替换方案,比较出色的应该是阿里的Sophix了。核心原理是替换java方法对应的底层虚拟机的ArtMethod,达到即时修复的效果。这个不是本文介绍的重点,详情你们能够参看《深刻探索Android热修复技术原理》一书。 而冷启动的方式则是将要修改的代码打成dex经过插包或者是合并的方式打入dexElements里。这种方式可以突破底层的诸多限制,可是一样也会碰到一些Android原有校验规则的限制,好比:CLASS_ISPREVERIFIED问题。bash

framework特性插件化

一样的,在Android手机的framework层也遇到相似的问题。目前,各大手机厂商基本都会对Google的原生framework进行或多或少的定制。而若是framework的特性须要升级,以往的流程是:架构

而framework层有不少特性,在framework层的客户端,本质上是app依赖的一些系统级lib。为了缩短发布周期,让用户更快的体验到咱们的新特性,咱们也但愿可以使用热升级技术,将特性lib从framework层脱离出来,成为一个独立的个体存在:app

将系统的lib从系统中解耦,成为一个独立于平台的lib,将会带来如下好处:

  1. 特性更新快,热升级
  2. 跨平台,不依赖于系统rom
  3. 向后兼容

support包

Google的support包就是Google对framework向后兼容的一个实现。将framework的部分特性抽离,作成support包的形式,单独发布,让新特性得以向后兼容,不依赖于系统rom,能够横跨多个Android版本。主要的实现方式是将support包做为静态jar一块儿打包至app,特性跟着app走而不跟随系统:模块化

如上图所示是AndroidStudio里编译生成的一个demo apk的apk结构,从图中咱们能够看到在apk生成的classes.dex里已经包含了support包的各个类。support包已经成为了app的一部分。这样的方式带来的一个缺陷就是support包特性的更新必须依赖于app的更新。固然,咱们也能够采起以上介绍的各类热修复技术去更新support包特性。可是做为framework层,咱们更但愿去寻找一种更基础的方案,让特性以一种的新的形式去加载。为此,咱们须要看一下Android的类加载。

类加载

咱们都知道Java的类加载是经过ClassLoader来加载的。函数

而ClassLoader的类加载又是双亲代理模式,也就是树形结构。Android虽然对ClassLoader在具体的实现上有些改变,可是结构是不变的。

而通常app的class关系树如图:

预加载

BootClassLoader是全部classLoader的parent,加载的优先级最高,负责加载一些须要预加载的类。类定义在 /libcore/ojluni/src/main/java/java/lang/ClassLoader.javapost

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }
    ...
}
复制代码

从以上代码可见,BootClassLoader是ClassLoader的内部类,访问权限是包内可见,因此能够知道仅仅只对部分系统开放。那么BootClassLoader是在哪建立的,前面所说的预加载的类,又是在哪加载的?这个就要从系统启动时的zygote进程的初始化提及了。
zygote进程初始化在/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java执行,在该类的main函数里会执行一些系统的zygote进程的初始化操做,并预加载操做:ui

public static void main(String argv[]) {
    ...
    preload(bootTimingsTraceLog);
    ...
}

复制代码

继续看preload方法:

static void preload(TimingsTraceLog bootTimingsTraceLog) {
    ...
    preloadClasses();
    ...
}
复制代码

preload方法里会执行preloadClass()方法进行类的预加载:

private static void preloadClasses() {
    ...
    InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
    ...
    try {
            BufferedReader br
                = new BufferedReader(new InputStreamReader(is), 256);

            int count = 0;
            String line;
            while ((line = br.readLine()) != null) {
                // Skip comments and blank lines.
                line = line.trim();
                ...
                try {
                    ...
                    Class.forName(line, true, null);
                    ...
                } catch (ClassNotFoundException e) {
                    Log.w(TAG, "Class not found for preloading: " + line);
                } catch (UnsatisfiedLinkError e) {
                    Log.w(TAG, "Problem preloading " + line + ": " + e);
                } catch (Throwable t) {
                    ...
                }
            }
    ...
}
复制代码

能够看到,在preloadClass方法中,首先会去逐行读取PRELOADED_CLASSES文件,看下该文件指向的路径:

private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
复制代码

咱们看下preload-classes文件:

...
android.app.INotificationManager
android.app.INotificationManager$Stub
android.app.INotificationManager$Stub$Proxy
android.app.IProcessObserver
android.app.IProcessObserver$Stub
android.app.ISearchManager
android.app.ISearchManager$Stub
android.app.IServiceConnection
android.app.IServiceConnection$Stub
android.app.ITransientNotification
android.app.ITransientNotification$Stub
android.app.IUiAutomationConnection
...
复制代码

能够看到该文件每一行基本都是framework的类,则Zygote进程经过BufferedReader逐行读取文件里的每个类,经过Class.forName方法加载到Zygote进程的内存中。这里咱们注意到,Class.forName的第三个传参为null

Class.forName(line, true, null);
复制代码

那么咱们再来看/libcore/ojluni/src/main/java/java/lang/Class.java文件:

public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)throws ClassNotFoundException {
        if (loader == null) {
            loader = BootClassLoader.getInstance();
        }
        ...
}
复制代码

能够看到第三个参数是ClassLoader,而且当传参为null时,会构造BootClassLoader。到这里咱们能够看到,预加载的类,最终是会交给BootClassLoader来加载。当app运行时,若是须要用到系统的类时,则能够经过访问他们父进程内存空间中在系统初始化时就已加载的类定义来访问framework的类了,而不须要在使用到时才从新进行加载。而且系统公共的类定义只存在与zygote进程的内存当中,而不须要每一个app进程加载一份,能够同时达到空间和时间上的节省。

插件加载

从上述过程咱们能够知道,framework里的特性,都是在系统启动时就经过BootClassLoader加载到zygote进程当中了,那么若是咱们须要更新那些特性,则须要更新系统配置,系统jar包,而且重启系统。整个过程很是麻烦。若是咱们但愿将framework里的特性抽取出来,做为一种可插拔式的插件存在如何作到呢?上述ClassLoader的树形关系则给了咱们启发:

将app的classloader与BootClassLoader的直接父子关系切断,中间加入为咱们抽离出来的特性构建的CloassLoader做为app的父ClassLoader,而BootClassLoader则做为插件CloassLoader的parent,当插件不存在时,BootClassLoader仍然是app classloader的直接parent。这样咱们就能够实现插件可插拔了,代码实现也很简单:

public void addPluginLoader(Application app) {
        if(!checkToDownloadPlugin(app)) {
            return;
        }

        PathClassLoader parent = new PathClassLoader(mDexPath, app.getClassLoader().getParent());

        try {
            Field parentField = ClassLoader.class.getDeclaredField("parent");
            parentField.setAccessible(true);
            parentField.set(app.getClassLoader(), parent);
        } catch (NoSuchFieldException e) {
            ...
        } catch (IllegalAccessException e) {
            ...
        }
}
复制代码

其中的mDexPath则是插件特性的路径,此方法可放在Application的onCreate方法里执行,实现插件的加载。 通常来讲,卸载插件,不能影响到app的其余特性,因此在总体架构设计上咱们还须要有一个接口做为没有plugin的默认实现打包到apk当中。以下图所示:

咱们能够将plugin的默认实现(在系统上没有下载plugin实现时)以静态lib的方式做为plugin的interface与app一块儿编译打包,一样的,plugin加载器(plugin loader)也做为静态lib打包至app内部,其中的PluginApplication是一个继承自Application的类,实现plugin包的加载。加载流程:

  1. 检查当前是否存在plugin或plugin是不是最新,若是不是则从云端下载,并校验其安全性。
  2. 若是plugin已经ready,则构建plugin的ClassLoader并进行app classLoader的parent重定向。

例如plugin里有一个叫PluginFeature的类,若是classLoader已经重定向好,则根据ClassLoader的双亲代理特性,虽说interface里也存在一样的PluginFeature的类,可是interface是由app的ClassLoader加载的,因为plugin的ClassLoader优先级更高,会去加载plugin的PluginFeature类,这样,就能够达到接口与实现的分离。当咱们须要更新特性时,只须要更新plugin,而不须要更新app。以达到特性更好的模块化开发,下降特性与app的耦合度。
固然这种方式,咱们得保证对应用开放的接口不变,若是接口须要改变,应用仍是须要更新本身的apk。 与Google提供的的support方案相比,这种动态加载的插件化方案可以给咱们带来如下好处:

  1. 在接口不变的状况下实现热更新,不须要从新安装apk。
  2. 系统能够只存在一份插件,而不须要每一个apk一份,节省rom空间。

固然,Android的plugin特性里不只仅只是代码,还有资源。咱们知道已安装的apk资源是在apk之间互相访问的,可是咱们下载的plugin并不但愿在系统里安装,仅仅只是做为一个文件存在于手机系统中,那plugin里资源的加载如何实现呢?感兴趣的同窗能够看下下一篇:《framework插件化技术-资源加载(免安装)》

相关文章
相关标签/搜索