字节面试官:熟悉插件化?通过源码聊聊Android插件化原理

前言

插件化技术最初源于免安装运行 apk 的想法,这个免安装的 apk 就可以理解为插件,而支持插件的 app 我们一般叫宿主。宿主可以在运行时加载和运行插件,这样便可以将 app 中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现 app 功能的动态扩展。

插件化的开源框架

插件化发展到现在,已经出现了非常多的框架,下表列出部分框架:

特性 DynamicAPK dynamic- load-apk Small DroidPlugin RePlugin VirtualAPK
支持四大组件 只支持Activity 只支持Activity 只支持Activity 全支持 全支持 全支持
组件无需在宿主manifest 中预注册 ×
插件可以依赖宿主 ×
支持PendingIntent × × ×
Android特性支持 大部分 大部分 大部分 几乎全部 几乎全部 几乎全部
兼容性适配 一般 一般 中等
插件构建 部署aapt Gradle插件 Gradle插件 Gradle插件

我们在选择开源框架的时候,需要根据自身的需求来,如果加载的插件不需要和宿主有任何耦合,也无须和宿主进 行通信,比如加载第三方 App,那么推荐使用 RePlugin,其他的情况推荐使用 VirtualApk。

插件化的实现

我们如何去实现一个插件化呢?首先我们要知道,插件apk是没有安装的,那我们怎么加载它呢?不知道。。。

没关系,这儿我们还可以细分下,一个 apk 主要就是由代码和资源组成,所以上面的问题我们可以变为:**如何加载插件的类?如何加载插件的资源?**这样的话是不是就有眉目了。然后我们还需要解决类的调用的问题,这个地方主要是四大组件的调用问题。我们都知道,四大组件是需要注册 的,而插件的四大组件显然没有注册,那我们怎么去调用呢?

所以我们接下来就是解决这三个问题,从而实现插件化

  1. 如何加载插件的类?
  2. 如何加载插件的资源?
  3. 如何调用插件类?

类加载(ClassLoader)

我们在学 java 的时候知道,java 源码文件编译后会生成一个 class 文件,而在 Android 中,将代码编译后会生成一个 apk 文件,将 apk 文件解压后就可以看到其中有一个或多个 classes.dex 文件,它就是安卓把所有 class 文件进行合并,优化后生成的。

java 中 JVM 加载的是 class 文件,而安卓中 DVM 和 ART 加载的是 dex 文件,虽然二者都是用的 ClassLoader 加载的,但因为加载的文件类型不同,还是有些区别的,所以接下来我们主要介绍安卓的 ClassLoader 是如何加载dex 文件的。

ClassLoader的实现类

ClassLoader是一个抽象类,实现类主要分为两种类型:系统类加载器和自定义加载器。其中系统类加载器主要包括三种:

  • BootClassLoader:用于加载Android Framework层class文件。
  • PathClassLoader:用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex
  • DexClassLoader:用于加载指定的dex,以及jar、zip、apk中的classes.dex

类继承关系如下图:

我们先来看下 PathClassLoader 和 DexClassLoader。

// /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 小于等于 26/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里面改变了,看下面两个构造方法
		super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
	}
}
// API 26/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java 
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
   
   
	super(parent);
	// DexPathList 的第四个参数是 optimizedDirectory,可以看到这儿为 null 
	this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
}
// 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)存放的路径,PathClassLoader 直接为null,而 DexClassLoader 是使用用户传进来的路径,而在8.0之后,二者就完全一样了。

下面我们再来了解下 BootClassLoader 和 PathClassLoader 之间的关系。

// 在 onCreate 中执行下面代码
ClassLoader classLoader = getClassLoader();
while (classLoader != null) {
   
   
	Log.e("leo", "classLoader:" + classLoader);
	classLoader = classLoader.getParent();
}
Log.e("leo", "classLoader:" + Activity.class.getClassLoader());

打印结果:

classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/user/0/com.enjoy.pluginactivity/cache/plugin-debug.apk", zip file "/data/app/com.enjoy.pluginactivity-T4YwTh- 
8gHWWDDS19IkHRg==/base.apk"],nativeLibraryDirectories=[/data/app/com.enjoy.pluginactivity- T4YwTh-8gHWWDDS19IkHRg==/lib/x86_64, /system/lib64, /vendor/lib64]]] 
classLoader:java.lang.BootClassLoader@a26e88d 
classLoader:java.lang.BootClassLoader@a26e88d

通过打印结果可知,应用程序类是由 PathClassLoader 加载的,Activity 类是 BootClassLoader 加载的,并且BootClassLoader 是 PathClassLoader 的 parent,这里要注意 parent 与父类的区别。这个打印结果我们下面还会提到。

加载原理

那我们如何使用类加载器去加载一个类呢? 非常的简单,例如:我们有一个apk文件,路径是 apkPath,然后里面有个类 com.enjoy.plugin.Test,那么我们可以通过如下方式去加载 Test 类:

DexClassLoader dexClassLoader = new DexClassLoader(dexPath,context.getCacheDir().getAbsolutePath(),	null, context.getClassLoader());
Class<?> clazz = dexClassLoader.loadClass("com.enjoy.plugin.Test");

因为我们需要将插件的 dex 文件加载到宿主里面,所以我们接下来分析源码,看 DexClassLoader 类加载器到底是怎么加载一个 apk 的 dex 文件的。

通过查找发现,DexClassLoader 类中没有 loadClass 方法,一路向上查找,最后在 ClassLoader 类中找到了改方法,源码如下:(后续源码如无标明,都是 API 26 Android 8.0)

// /libcore/ojluni/src/main/java/java/lang/ClassLoader.java 
protected Class<?> loadClass(String name, Boolean resolve)
throws ClassNotFoundException{
   
   
	// 检测这个类是否已经被加载 --> 1 
	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) {
   
   
		}
		if (c == null) {
   
   
			// 如果仍然找不到,就调用 findClass 去查找 --> 2 
			c = findClass(name);
		}
	}
	return c;
}
// -->1 检测这个类是否已经被加载
protected final Class<?> findLoadedClass(String name) {
   
   
	ClassLoader loader;
	if (this == BootClassLoader.getInstance()) loader = null; else
	loader = this;
	// 最后通过 native 方法实现查找
	return VMClassLoader.findLoadedClass(loader, name);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值