近两年,Android的热升级技术成为Android领域的一个热点。因为快速迭代的须要,热修复,插件化的技术已经深刻app及framework的各个研究领域。java
简单的介绍一下热修复技术的背景: 以往的app项目管理流程以下:android
两类方案的优劣:
底层替换方案限制多,但时效性最好,加载轻快,当即见效。
类加载方案时效性差,须要app的冷启动才能见效,可是修复范广,限制少。安全
关于底层替换方案,比较出色的应该是阿里的Sophix了。核心原理是替换java方法对应的底层虚拟机的ArtMethod,达到即时修复的效果。这个不是本文介绍的重点,详情你们能够参看《深刻探索Android热修复技术原理》一书。 而冷启动的方式则是将要修改的代码打成dex经过插包或者是合并的方式打入dexElements里。这种方式可以突破底层的诸多限制,可是一样也会碰到一些Android原有校验规则的限制,好比:CLASS_ISPREVERIFIED问题。bash
一样的,在Android手机的framework层也遇到相似的问题。目前,各大手机厂商基本都会对Google的原生framework进行或多或少的定制。而若是framework的特性须要升级,以往的流程是:架构
而framework层有不少特性,在framework层的客户端,本质上是app依赖的一些系统级lib。为了缩短发布周期,让用户更快的体验到咱们的新特性,咱们也但愿可以使用热升级技术,将特性lib从framework层脱离出来,成为一个独立的个体存在:app
Google的support包就是Google对framework向后兼容的一个实现。将framework的部分特性抽离,作成support包的形式,单独发布,让新特性得以向后兼容,不依赖于系统rom,能够横跨多个Android版本。主要的实现方式是将support包做为静态jar一块儿打包至app,特性跟着app走而不跟随系统:模块化
咱们都知道Java的类加载是经过ClassLoader来加载的。函数
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的树形关系则给了咱们启发:
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里有一个叫PluginFeature的类,若是classLoader已经重定向好,则根据ClassLoader的双亲代理特性,虽说interface里也存在一样的PluginFeature的类,可是interface是由app的ClassLoader加载的,因为plugin的ClassLoader优先级更高,会去加载plugin的PluginFeature类,这样,就能够达到接口与实现的分离。当咱们须要更新特性时,只须要更新plugin,而不须要更新app。以达到特性更好的模块化开发,下降特性与app的耦合度。
固然这种方式,咱们得保证对应用开放的接口不变,若是接口须要改变,应用仍是须要更新本身的apk。 与Google提供的的support方案相比,这种动态加载的插件化方案可以给咱们带来如下好处:
固然,Android的plugin特性里不只仅只是代码,还有资源。咱们知道已安装的apk资源是在apk之间互相访问的,可是咱们下载的plugin并不但愿在系统里安装,仅仅只是做为一个文件存在于手机系统中,那plugin里资源的加载如何实现呢?感兴趣的同窗能够看下下一篇:《framework插件化技术-资源加载(免安装)》