1 Java 中的 ClassLoader
我们知道 DVM 和 ART 加载的是 dex 文件,而 JVM 加载的是 Class文件,因此它们的类加载器 ClassLoader 肯定是有区别的
1.1 ClassLoader 的类型
系统类加载器和自定义类加载器
- Bootstrap ClassLoader (引导类加载器)
- Extensions ClassLoader (拓展类加载器)
- Application ClassLoader ( 应用程序类加载器)
- Custom ClassLoader ( 自定义类加载器)
除了系统提供的类加载 器, 还可以自定义类加载器,自定义类加载器通过继承java.lang.ClassLoader 类的方式来实现自己的类加载器, Extensions ClassLoader 和 App ClassLoader 也继承了 java.lang.ClassLoader 类。
1.2 ClassLoader 的继承关系
- ClassLoader 是一个抽象类,其中定义了 ClassLoader 的主要功能。
- SecureClassLoader 继承了抽象类 ClassLoader ,但 SecureClassLoader 并不是
ClassLoader 的实现类,而是拓展了 ClassLoader 类加入了权限方面的功能,加强了
ClassLoader 的安全性。 - URLClassLoader 继承自 SecureClassLoader,可以通过 URL 路径从 jar 文件和文件夹
中加载类和资源。 - ExtClassLoader 和 AppClassLoader 都继承自 URLClassLoader,它们都是 Launcher 的内部类, Launcher 是 Java 虚拟机的人口应用, ExtClassLoader 和 AppClassLoader都是在 Launcher 中进行初始化的 。
1.3 双亲委托模式
类加载器查找 Class 所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class 是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的 Bootstrap ClassLoader,如果 Bootstrap ClassLoader 找到了该 Class ,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。
采取双亲委托模式主要有如下两点好处。
-
避免重复加载,如果已经加载过一次 Class ,就不需要再次加载,而是直接读取已经加载的 Class 。
-
更加安全 ,如果不使用双亲委托模式,就可以自定义一个 String 类来替代系统的String 类,这显然会造成安全隐患,采用双亲委托模式会使得系统的 String 类在 Java虚拟机启动时就被加载,也就无法自定义 String 类来替代系统的 String 类,除非我们修改类加载器搜索类的默认算住。还有一点, 只有两个类名一致并且被同一个类加载器加载的类,Java 虚拟机才会认为它们是同一个类 ,想要骗过Java 虚拟机显然不会那么容易。
1.4 自定义 ClassLoader
实现自定义ClassLoader 需要如下两个步骤:
- 定义一个自定义 ClassLoade 并继承抽象类 ClassLoader 。
- 覆写 findClass 方法,并在 findClass 方提中调用 defineClass 方法。
2 Android 中的 ClassLoader
来查看 Android 中的 ClassLoader ,Java中的ClassLoader有何不同 。
2.1 Classloader 的类型
我们知道 Java 中的 ClassLoader 可以加载 jar 文件和 Class 文件(本质是加载 Class 文件),这一点在 Android 中并不适用,因为无论是 DVM 还是 ART ,它们加载的不再是 Class文件,而是 dex 文件,这就需要重新设计 ClassLoader 相关类。
Android 中的 ClassLoader 类型和 Java 中的 ClassLoader 类型类似,也分为两种类型,分别是系统类加载器和自定义加载器。其中系统类加载器主要包括 3 种,分别是
- BootClassLoader
- PathClassLoader
- DexClassLoader
2.1.1 BootClassLoader
Android 系统启动时会使用 BootClassLoader 来预加载常用类,与 SDK 中的 Bootstrap ClassLoader不同,它并不是由 C/C++代码实现的,而是由 Java 实现BootClassLoader
的代码如下所示:
/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
...
}
BootClassLoader 是 ClassLoader 的内部类,并继承自 ClassLoader 。 BootClassLoader 是一个单例类,需要注意的是 BootClassLoader 的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无告直接调用的。
2.1.2 DexClassLoader
DexClassLoader 可以加载 dex 文件以及包含 dex 的压缩文件( apk 和 jar 文件 ),不管加载哪种文件,最终都要加载 dex 文件,在这一章为了方便理解和叙述,将 dex 文件以及包含 dex 的压缩文件统称为 dex 相关文件。查看 DexClassLoader 的代码,如下所示 :
/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
DexClassLoader 的构造方怯有如下 4 个参数。
- dexPath : dex 相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为“:”。
- optimizedDirectory :解压的 dex 文件存储路径,这个路径必须是一个内部存储路径,在一般情况下,使用当前应用程序的私有路径:/data/data/<Package Name>/…。
- librarySearchPath :包含 C/C++库的路径集合,多个路径用文件分隔符分隔,可以为 null 。
- parent:父加载器。
DexClassLoader 继承自 BaseDexClassLoader ,方法都在 BaseDexClassLoader中实现。
2.1.3 PathClassLoader
Android 系统使用 PathClassLoader 来加载系统类和应用程序的类,下面来查看它的代码:
/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
PathClassLoader 继承自 BaseDexClassLoader ,也都在 BaseDexClassLoader 中实现。
在 PathClassLoader 的构造方法中没有参数 optimizedDirectory。这是因为 PathClassLoader已经默认了参数 optimizedDirectory 的值为 /data/dalvik-cache ,很显然 PathClassLoader无法定义解压的 dex 文件存储路径,因此 PathClassLoader 通常用来加载已经安装的 apk 的 dex文件(安装的 apk 的 dex 文件会存储在 /data/dalvik-cache 中)。
2.2 ClassLoader 的继承关系
运行一个应用程序需要用到几种类型的类加载器呢?
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader loader = MainActivity.class.getClassLoader();
while (loader != null) {
Log.d("hongxue", loader.toString());
loader = loader.getParent();
}
}
}
07-05 08:30:06.570 3534-3534/com.hongx.hxclassloader D/hongxue: dalvik.system.PathClassLoader
[dexPath=/data/app/com.hongx.hxclassloader-1.apk,libraryPath=/data/app-lib/com.hongx.hxclassloader-1]
07-05 08:30:06.570 3534-3534/com.hongx.hxclassloader D/hongxue: java.lang.BootClassLoader@40d57f48
可以看到有两种类加载器,一种是 PathClassLoader,另一种则是 BootClassLoader 。DexPathList 中包 含了很多 apk 的路径,其中/data/app/com.hongx.hxclassloader-1.apk 就是示例 应 用安装在手机上的 位置。 DexPathList 是在BaseDexClassLoader 的构造方法中创建的 ,里面存储了 dex 相关文件的路径,在ClassLoader 执行双亲委托模式的查找流程时会从 DexPathList 中进行查找。
除了上面所讲的 3 种主要的类加载器外,Android 还提供了其他的类加载器和ClassLoader 相关类, ClassLoader 的继承关系如下图所示。
可以看到图 中一共有 8 个 ClassLoader 相关类,其中有一些和 Java 中的 ClassLoader
相关类十分类似,下面简单对它们进行介绍:
- ClassLoader 是一个抽象类,其中定义了 ClassLoader 的主要功能。
- BootClassLoader是ClassLoader的内部类。
- SecureClassLoader 类和 JDK8 中的 SecureClassLoader 类的代码是一样的,它继承了
抽象类 ClassLoader。 SecureClassLoader 并不是 ClassLoader 的实现类,而是拓展了
ClassLoader 类加入了权限方面的功能,加强了 ClassLoader 的安全性 。 - URLClassLoader 类和 JDK8 中的 URLClassLoader 类的代码是一样的,它继承自SecureClassLoader ,用来通过 URL 路径从 jar 文件和文件夹中加载类和资源。
- InMemoryDexClassLoader 是 Android 8.0 新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的 dex 文件。
- BaseDexClassLoader 继承自 ClassLoader ,是抽象类 ClassLoader 的具体实现类,PathClassLoader 、 DexClassLoader 和 InMemoryDexClassLoader 都继承自它 。
- PathClassLoader 只能加载已经安装apk的dex。
- DexClassLoader 支持加载apk、dex和jar,也可以从SD卡加载
2.3 Classloader 的加载过程
Android 的 ClassLoader 同样遵循了双亲委托模式, ClassLoader 的加载方法为 loadClass方法,这个方法被定义在抽象类 ClassLoader 中,如下所示:
2.3.1 ClassLoader.java
/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
Class<?> c = findLoadedClass(name);//1
if (c == null) {
try {
if (parent != null) {//2
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);//3
}
} catch (ClassNotFoundException e) {
}
if (c == null) {//4
c = findClass(name);//5
}
}
return c;
}
在注释 1 处用来检查传入的类是否已经加载,如果已经加载就返回该类,如果没有加载就在注释 2 处判断父加载器是否存在,存在就调用父加载器的 loadClass方法,如果不存在就调用注释 3 处的 findBootstrapClassOrNull 方法,这个方法会直接返回null 。如果注释 4 处的代码成立,说明向上委托流程没有检查出类已经被加载,就会执行注释 5 处的 findClass 方法来进行查找流程, findClass方法如下所示:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
2.3.2 BaseDexClassLoader.java
在 findClass 方法中直接抛出了异常,这说明 findClass方法需要子类来实现,BaseDexClassLoader 的代码如下所示:
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
2.3.3 DexPathList.java
在 BaseDexClassLoader的构造方法中创建了DexPathList ,并在findClass方法中调用了DexPathList 的findClass 方法:
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
遍历 Element 数组 dexElements ,并调用 Element 的 findClass 方法,Element 是 DexPathList 的静态内部类 :
private final File path;
private final DexFile dexFile;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
public Element(DexFile dexFile, File dexZipPath) {
this.dexFile = dexFile;
this.path = dexZipPath;
}
public Element(DexFile dexFile) {
this.dexFile = dexFile;
this.path = null;
}
public Element(File path) {
this.path = path;
this.dexFile = null;
}
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
2.3.4 DexFile.java
从 Element 的构造方战可以看出,其内部封装了 DexFile ,它用于加载 dex 。如果 DexFile 不为 null 就调用 DexFile 的 loadClassBinaryName 方法:
http://androidxref.com/9.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);//1
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
在注释1处调用了defineClassNative 方法来加载 dex 相关文件,这个方法是 Native方法 。
ClassLoader 的加载过程就是遵循着双亲委托模式 , 如果委托流程没有检查到此前加载过传入的类,就调用 ClassLoader的 findClass 方法, Java 层最终会调用 DexFile 的 defineClassNative 方法来执行查找流程,如下图所示:
2.4 BootClassloader的创建
BootClassLoader 是在 Zygote 进程的 Zygote入口方法中被创建的,用于加载 preloaded-classes 文件中存有的预加载类。
2.5 PathClassloader 的创建
http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
main方法中:
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
if (r != null) {
r.run();
return;
}
}
forkSystemServer方法中:
int pid;
try {
...
pid = Zygote.forkSystemServer(//1
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.runtimeFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
if (pid == 0) {//2
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
zygoteServer.closeServerSocket();
return handleSystemServerProcess(parsedArgs);//3
}
在注释 1处, Zygote 进程通过 forkSystemServer 方法 fork 自身创建子进程( SystemServer
进程)。在注释 2 处如果 forkSystemServer 方怯返回的 pid 等于 0 ,说明 当前代码是在新创
建的 SystemServer 进程中执行的,接着就会执行注释 3 处的 handleSystemServerProcess 方法:
private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
if (parsedArgs.invokeWith != null) {
...
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//1
Thread.currentThread().setContextClassLoader(cl);
}
return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
}
在注释 1处调用了 createPathC!assLoader 方法,如下所示:
static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
String libraryPath = System.getProperty("java.library.path");
return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
null /* classLoaderName */);
}
在 createPathClassLoader 方怯中又调用了 PathClassLoaderFactory 的 createClassLoader方法, 看来 PathClassLoader 是用 工厂来进行创建的:
http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName) {
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
}
throw new AssertionError("Invalid classLoaderName: " + classloaderName);
}
PathClassLoader 是在 SystemServer 进程中采用工厂模式创建的 。
3 实例:DexClassLoader 加载外部的dex
加载流程如下:
- 从服务器下载插件 apk 到手机 SDCard ,为此需要申请 SDCard 的读写权限。
- 读取插件 apk 中的 dex ,生成对应的 DexClassLoader 。
- 使用 DexClassLoader 的 loadClass 方法读取插件 dex 中的任何一个类 。
把插件 apk 放到主 App 的 assets 目录中, App 启动后,会把 asset 目录中的插件复制到内存,通过这种方式来模拟从服务器下载插件的办法 。
先建立一个Plugin1项目,在项目中只有一个实体类Bean.java
package com.hongxue.plugin1;
public class Bean {
private String name = "hongxue";
public String getName() {
return name;
}
public void setName(String paramString) {
this.name = paramString;
}
}
打包生成 app-debug.apk,将apk复制到HostApp的assert文件夹中
新建HostApp项目:
public class MainActivity extends AppCompatActivity {
private String dexpath = null; //apk文件地址
private File fileRelease = null; //释放目录
private DexClassLoader classLoader = null;
private String apkName = "app-debug.apk"; //apk名称
TextView tv;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
Utils.extractAssets(newBase, apkName);
} catch (Throwable e) {
e.printStackTrace();
}
}
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
File extractFile = this.getFileStreamPath(apkName);// /data/user/0/com.hongxue.hostapp/files/app-debug.apk
dexpath = extractFile.getPath();// /data/user/0/com.hongxue.hostapp/files/app-debug.apk
fileRelease = getDir("dex", 0); //0 表示Context.MODE_PRIVATE --> /data/user/0/com.hongxue.hostapp/app_dex
Log.d("DEMO", "dexpath:" + dexpath);
Log.d("DEMO", "fileRelease.getAbsolutePath():" + fileRelease.getAbsolutePath());
classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),
null, getClassLoader());
Button btn_1 = (Button) findViewById(R.id.btn_1);
tv = (TextView) findViewById(R.id.tv);
//普通调用,反射的方式
btn_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
Class mLoadClassBean;
try {
mLoadClassBean = classLoader.loadClass("com.hongxue.plugin1.Bean");
Object beanObject = mLoadClassBean.newInstance();
Method getNameMethod = mLoadClassBean.getMethod("getName");
getNameMethod.setAccessible(true);
String name = (String) getNameMethod.invoke(beanObject);
tv.setText(name);
Toast.makeText(getApplicationContext(), name, Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("DEMO", "msg:" + e.getMessage());
}
}
});
}
}
public class Utils {
/**
* 把Assets里面得文件复制到 /data/data/files 目录下
*
* @param context
* @param sourceName
*/
public static void extractAssets(Context context, String sourceName) {
AssetManager am = context.getAssets();
InputStream is = null;
FileOutputStream fos = null;
try {
is = am.open(sourceName);
File extractFile = context.getFileStreamPath(sourceName);// /data/user/0/com.hongxue.hostapp/files/app-debug.apk
fos = new FileOutputStream(extractFile);
byte[] buffer = new byte[1024];
int count = 0;
while ((count = is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeSilently(is);
closeSilently(fos);
}
}
// --------------------------------------------------------------------------
private static void closeSilently(Closeable closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (Throwable e) {
// ignore
}
}
}
点击按钮后,获取到Bean中的name。
4 小结
本文对 Java 和 Android 的 ClassLoader 进行解析,通过 Java 和 Android 的 ClassLoader
的类型和 ClassLoader 的继承关系,就可以很清楚地看出它们的差异, 主要有以下几点:
-
Java 的引导类加载器是由 C++编写的, Android 中的引导类加载器则是用 Java 编写的。
-
Android 的继承关系要比 Java 继承关系复杂一些,提供的功能也多 。由于 Android 中加载的不再是 Class 文件,因此 Android 中没有 ExtClassLoader 和AppClassLoader ,替代它们的是 PathClassLoader 和 DexClassLoader