目录
  1. 1. 简介
    1. 1.1. 插件化实现思路
      1. 1.1.1. 如何加载插件的类
        1. 1.1.1.1. 类加载机制原理
        2. 1.1.1.2. 实现 类 的动态加载
      2. 1.1.2. 如何启动插件的四大组件
        1. 1.1.2.1. 四大组件的启动 Vs 普通类的调用
        2. 1.1.2.2. Hook技术
        3. 1.1.2.3. Activity 启动流程源码分析
        4. 1.1.2.4. 实现插件 Activity 的启动
      3. 1.1.3. 如何加载插件的资源
        1. 1.1.3.1. Hook插件 Activity 的版本适配
        2. 1.1.3.2. Resources 和 AssetManager
        3. 1.1.3.3. 资源加载流程源码分析
        4. 1.1.3.4. 实现插件 资源 的加载
重拾Android-深入理解插件化开发

简介

插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 就可以理解为 插件,而支持插件的 app 我们一般称之为 宿主

插件化解决的问题

  • APP的功能模块越来越多,体积越来越大

  • 模块之间的耦合度高,协同开发沟通成本越来越大

  • 方法数目可能超过65535,APP占用的内存过大

  • 应用之间的互相调用

插件化 Vs 组件化

组件化开发就是将一个app分成多个模块,每个模块都是一个组件,开发的过程中,我们可以让这些组件相互依赖,或者单独调式部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。

插件化开发和组件化略有不同,插件化开发是将整个app拆分成多个模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk,最终打包的时候宿主apk和插件apk分开打包。

各大插件化技术对比

特性 dynamic-load-apk DynamicAPK Small Replugin VirtualAPK
作者 任玉刚 携程 wequick 360 滴滴
支持四大组件 只支持Activity 只支持Activity 只支持Activity 全支持 全支持
组建无需在宿主manifest中预注册 ×
插件可以依赖宿主 ×
支持pendingIntent × × ×
Android特性支持 大部分 大部分 大部分 几乎全部 几乎全部
兼容性适配 一般 一般 中等
插件构建 部署aapt Gradle插件 Gradle插件

在选择上述几款插件化开发框架时,我们最好根据自身的需求来评定,如果加载的插件跟宿主没有任何关系,也不需要和宿主进行通信,比如加载第三方 App,那么推荐使用 Replugin,其他情形下,比较推荐使用 VirtualAPK。

插件化实现思路

那么该如何实现一个插件化功能呢?

首先我们面临要解决的问题是如何加载插件apk,加载完成之后就是要处理安装插件apk。而一个apk其实就是由 代码资源 组成。

基于此,处理插件的两个问题即变为:如何加载插件的类,如何加载插件的资源,这之后再去解决的问题:如何调用插件类

如何加载插件的类

类加载机制原理

首先要知道,一个完整的 Java 程序是由多个 .class 文件组成的,在程序运行过程中,需要将这些 .class 文件 加载到 JVM 中才可以使用。而在 Android 中,这些 .class 文件会被合并优化成一个或者多个的 classes.dex 文件,这是构成apk文件的其中之一的文件,而负责加载这些 .class 文件的 就是接下来要分析的类加载器(ClassLoader)。

类加载(ClassLoader)
在 Java 中,JVM 加载的是 class 文件,而在 Android 中,DVM 和 ART 加载的则是 dex 文件,虽然两者都是用的 ClassLoader 加载,但是因为加载的文件类型不同,因此还是有区别的。有关 Java 的类加载,请移步此篇 Java进阶之深入理解ClassLoader,这里着重介绍 Android的 ClassLoader 是如何加载 dex 文件。

ClassLoader 是一个抽象类,实现类主要分为两种类型:系统类加载器自定义加载器

其中系统类加载器主要包含三种:

  • BootClassLoader

    用于加载 Android Framework 层 class 文件

  • PathClassLoader

    用于 Android 应用程序类加载器,可以加载指定的 dex、以及 jar、zip、apk 中的classes.dex 文件

  • DexClassLoader

    用于加载指定的 dex、以及 jar、zip、apk 中的classes.dex 文件

类继承关系图如下:

ClassLoader类继承图.png

先看 PathClassLoader 和 DexClassLoader(Android Framework源码是基于9)

// API 28 /libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {

// optimizedDirectory 直接设为 null
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}

// optimizedDirectory 直接设为 null
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}


// API 28 /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {

public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
// 从26开始,super里面的参数就变了,看两个不同API对应的父类构造方法
super(dexPath, null, librarySearchPath, parent);
}
}

// API 28 /libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);

// DexPathList 的第四个参数是 optimizedDirectory,可以看到这儿为 null
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

if (reporter != null) {
reportClassLoaderChain();
}
}

// API 25 /libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}

根据源码可知,PathClassLoader 和 DexClassLoader 都是继承自 BaseDexClassLoader,且类中只有构造方法,它们的类加载逻辑全部由 BaseDexClassLoader 中。

这里需要注意的是,在8.0之前,二者的唯一区别是第二个参数 optimizedDirectory,这个参数代表的是 生成的 odex(优化后的dex)存放的路径,PathDexLoader 直接为 null,而 DexClassLoader 使用的则是用户传递进来的路径,而在8.0之后,二者就完全一样了。

加载原理

使用类加载器加载类 代码如下:

DexClassLoader dexClassLoader = new DexClassLoader(dexPath,
context.getCacheDir().getAbsolutePath(), null, context.getClassLoader());
try {
Class<?> clazz = dexClassLoader.loadClass("com.tufusi.kkplugin.XXX");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

因为需要将插件的 dex 文件加载到宿主里面,所以接下来分析 DexClassLoader 类加载器源码,理解其是如何加载一个 apk 的 dex 文件的。

但是我们在 DexClassLoader 及其父类 BaseDexClassLoader 均没有找到相关加载类的方法,只能继续向上查找,最终在 ClassLoader 类中找到 loadClass 方法,源码如下(源码是 API 28 Android 9.0):

// /libcore/ojluni/src/main/java/java/lang/ClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先,检测这个类是否已加载 ------------------------------ ①
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 如果parent不为null,则调用parent的loadClass进行检查加载
c = parent.loadClass(name, false);
} else {
// 正常情况下不会命中这里,因为 BootClassLoader 重写了 loadClass 方法,结束了递归
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// 如果仍然找不到,就调用 findClass 去查找 ------------------------------ ②
c = findClass(name);
}
}
return c;
}

// ------------------------------ ① 检测类是否已加载
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
// 最后通过 native 方法实现查找
return VMClassLoader.findLoadedClass(loader, name);
}

// ------------------------------ ② 加载器一般都会重写此方法,定义自己的加载规则
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

// /libcore/ojluni/src/main/java/java/lang/VMClassLoader.java
@FastNative
native static Class findLoadedClass(ClassLoader cl, String name);

类加载流程:首先检测这个类是否已经被加载了,如果已加载,则直接获取并返回;如果没有加载,parent不为null,则调用parent的loadClass方法进行加载,这样依次递归,如果找到了或者加载了就返回,如果没找到也加载不了,才自己去加载。整个过程就是常说的双亲委托机制

双亲委派模式

所谓双亲委派模式,当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器去加载,也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。

实现 的动态加载

如何启动插件的四大组件

四大组件的启动 Vs 普通类的调用

Hook技术

Activity 启动流程源码分析

Activity启动流程图.png

实现插件 Activity 的启动

如何加载插件的资源

Hook插件 Activity 的版本适配

Resources 和 AssetManager

资源加载流程源码分析

实现插件 资源 的加载

打赏
  • 微信
  • 支付宝

评论