Android知识总结
一、JVM 的类加载架构
- 1、Bootstrap ClassLoader(
启动类加载器
)
Bootstrp加载器是用C++语言
写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径及%JAVA_HOME%/jre/classes中的类。 - 2、Extension ClassLoader(
扩展类加载器
)
Bootstrp loader加载ExtClassLoader
,并且将ExtClassLoader的父加载器设Bootstrp loader.ExtClassLoader是用Java
写的,具体来说就是sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库 - 3、Application ClassLoader(
系统类加载器) Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将
AppClassLoader`的父加载器指定为 ExtClassLoader。AppClassLoader也是用Java
写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。 - 4、Custom ClassLoader(
自定义类加载器
java.lang.ClassLoader的子类)
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
二、类加载器的特性
每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 双亲委派的加载链
结构。
2.1、双亲委托模式(Android 中的类加载)
某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到BootClassLoader。
双亲委托机制特点:
因为这样可以
避免重复加载
,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到
安全因素
,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。(沙箱安全机制)。
防止核心类APi被篡改,通过委托的方式,保证核心.class不被篡改,即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的加载器加载同一个.class也不是同一个Class对象。只有 同一个类加载器加载的 同一个 class 文件才是同一个对象。 这样则保证了Class的执行安全。
ClassLoader.java
类
//parent 是父类 BootClassLoader
private final ClassLoader parent;
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException{
// 检查class是否有被加载
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果parent不为null,则调用parent的loadClass进行加载
c = parent.loadClass(name, false);
} else {
//parent为null,则调用BootClassLoader进行加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// 如果都找不到就自己查找
long t1 = System.nanoTime();
c = findClass(name);
}
}
return c;
}
因此我们自己创建的
ClassLoader: new PathClassLoader("/sdcard/xx.dex", getClassLoader())
; 并不仅仅只能加载 xx.dex中的class。
值得注意的是:c = findBootstrapClassOrNull(name)
;
按照方法名理解,应该是当parent为null时候,也能够加BootClassLoader
加载的类。
new PathClassLoader("/sdcard/xx.dex", null)
,能否加载Activity.class?
但是实际上,Android当中的实现为:(Java不同)
private Class findBootstrapClassOrNull(String name) {
return null;
}
2.1、findClass
可以看到在所有父ClassLoader无法加载Class时,则会调用自己的 findClass
方法。 findClass 在ClassLoader中的定义为:
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
其实任何ClassLoader子类,都可以重写loadClass 与 findClass 。一般如果你不想使用双亲委托,则重写loadClass 修改其实现。而重写 findClass 则表示在双亲委托下,父ClassLoader都找不到Class的情况下,定义自己如何去查找一个Class。而我们的PathClassLoader 会自己负责加载 MainActivity 这样的程序中自己编写的类,利用双亲委托父ClassLoader加载Framework中的 Activity 。说PathClassLoader 并没有重写 loadClass ,因此我们可以来看看PathClassLoader
中的 findClass 是如何实现的。最终在BaseDexClassLoader
中调用。
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String
librarySearchPath, ClassLoader parent) {
super(parent);
//创建DexPathList
this.pathList = new DexPathList(this, dexPath, librarySearchPath,
optimizedDirectory);
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
List suppressedExceptions = new ArrayList();
//查找指定的class
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;
}
实现非常简单,从 pathList 中查找class。继续查看 DexPathList
/*package*/ final class DexPathList {
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
//.........
// splitDexPath 实现为返回 List.add(dexPath)
// makeDexElements 会去 List.add(dexPath) 中使用DexFile加载dex文件返回 Element数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
//.........
}
public Class findClass(String name, List suppressed) {
//从element中获得代表Dex的 DexFile
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
//查找class
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
}
从上面可以得到 dexElements
数组里面保存的每个Element
就对应一个dex文件
,即把dex文件保存在dexElements数组中。最后便利dex文件数组,拿到对应dex文件的class文件。
public final class DexFile {
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
DexPathList.Element[] elements) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
mInternalCookie = mCookie;
mFileName = sourceName;
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
return loadDex(sourcePathName, outputPathName, flags, null, null);
}
static DexFile loadDex(String sourcePathName, String outputPathName,
int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
}
}
三、Android 中 CloassLoader示意图
任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。
class Class {
...
private transient ClassLoader classLoader;
...
}
从图中可以看出ClassLoader是一个抽象类,而它的具体实现类主要有:
- ClassLoader
ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能;- BootClassLoader
BootClassLoader是ClassLoader的内部类,用于预加载preload()常用类以及一些系统Framework层级需要的类;- BaseDexClassLoader
BaseDexClassLoader继承ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它- PathClassLoader
PathClassLoader用于Android应用程序类加载器(自定义的类)。如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及jar、zip、apk中 classes.dex。- DexClassLoader
DexClassLoader可以加载自定义的dex文件以及jar、zip、apk中的classes.dex,,也支持从SD卡进行加载。
四、类加载示意图
4.1、热修复流程
PathClassLoader 中存在一个Element数组,Element类中存在一个dexFile成员表示dex文件,即:APK中有X个dex,则Element数组就有X个元素。
在 PathClassLoader 中的Element数组为:[patch.dex , classes.dex , classes2.dex]。如果存在Key.class位于 patch.dex与classes2.dex中都存在一份,当进行类查找时,循环获得 dexElements 中的DexFile,查找到了 Key.class则立即返回,不会再管后续的element中的DexFile是否能加载到Key.class了。
因此实际上,一种热修复实现可以将出现Bug的class单独的制作一份fix.dex文件(补丁包),然后在程序启动时,从服务器下载fix.dex保存到某个路径,再通过fix.dex的文件路径,用其创建 Element 对象,然后将这个 Element 对象插入到我们程序的类加载器 PathClassLoader 的 pathList 中的 dexElements 数组头部。这样在加载出现Bug的class时会优先加载fix.dex中的修复类,从而解决Bug。
步骤
- 1、获取当前应用的
PathClassLoader
;- 2、反射获取
DexPathList
的属性对象pathList
;- 3、反射修改 pathList 的 dexElements;
3.1、把补丁包 path.dex 转化为 Element;
3.2、获取 pathList 的 dexElements 属性(old);
3.3、patch + old 合并,并反射赋值给 pathList 的 dexElements。
五、自定义类加载器
自定义类加载器,主要有两种方法:
-(1)遵守双亲委派模型:继承 ClassLoader,重写
findClass()
方法。
-(2)破坏双亲委派模型:继承 ClassLoader,重写loadClass()
方法。 通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型
我们看一下实现步骤:
(1)创建一个类继承 ClassLoader 抽象类
(2)重写 findClass()方法
(3)在 findClass()方法中调用 defineClass()
public class MyClassLoader extends ClassLoader {
private String libPath;
public DiskClassLoader(String path) {
libPath = path;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
String fileName = getFileName(name);
File file = new File(libPath, fileName);
try {
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
try {
while ((len = is.read()) != -1) {
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
//获取要加载的 class 文件名
private String getFileName(String name) {
int index = name.lastIndexOf('.');
if (index == -1) {
return name + ".class";
} else {
return name.substring(index + 1) + ".class";
}
}
}
接下来我们就可以自己去加载类了,使用方法大体就是两行
MyClassLoader diskLoader = new MyClassLoader("D:\\lib");// 加 载class 文 件
TestClass c = diskLoader.loadClass("com.fdd.Test");
小福利
在线源码阅读:
ANDROID社区
或
AndroidXRef