更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
本篇文章将从以下几个内容来阐述反射与类加载:
- [动态代理模式]
- [Android 中的Dalvik和ART]
- [ PathClassLoader 和 DexClassLoader]
- [双亲委派机制]
一、动态代理模式
动态代理
所谓静态和动态指的是,接口方法的调用方式.静态代理,是真实对象显式地方法调用,而动态代理则是通过反射的方式调用真实对象的方法.
public class DynamicProxy implements InvocationHandler {
// 这个就是我们要代理的真实对象
private Object subject;
// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(subject, args);
}
}
public class ProxyClient {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
InvocationHandler handler = new design.pattern.structure.proxy.demo.DynamicProxy(realSubject);
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
subject.operate1();
subject.operate2("hello proxy");
}
}
三大角色:接口类,真实对象,InvocationHandler的实现类
通过Java的Proxy.newProxyInstance的方法调用,给Subject生成了一个实例对象;接口方法的调用,会传递给handler中,handler通过invoke的执行,间接地调用了真实对象的方法.
二、Android 中的Dalvik和ART
2.1 什么是Dalvik?
Dalvik是Google公司自己设计用于Android平台的虚拟机。
Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。
它可以支持已转换为** .dex格式**的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。
Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
很长时间以来,Dalvik虚拟机一直被用户指责为拖慢安卓系统运行速度不如IOS的根源。
2014年6月25日,Android L 正式亮相于召开的谷歌I/O大会,Android L 改动幅度较大,谷歌将直接删除Dalvik,代替它的是传闻已久的ART。
2.2 Dalvik和JVM有啥关系?
主要区别:
Dalvik是基于寄存器的,而JVM是基于栈的。
Dalvik运行dex文件,而JVM运行java字节码
自Android 2.2开始,Dalvik支持JIT(just-in-time,即时编译技术)。
优化后的Dalvik较其他标准虚拟机存在一些不同特性:
1.占用更少空间
2.为简化翻译,常量池只使用32位索引
3.标准Java字节码实行8位堆栈指令,Dalvik使用16位指令集直接作用于局部变量。局部变量通常来自4位的“虚拟寄存器”区。这样减少了Dalvik的指令计数,提高了翻译速度。
当Android启动时,Dalvik VM 监视所有的程序(APK),并且创建依存关系树,为每个程序优化代码并存储在Dalvik缓存中。Dalvik第一次加载后会生成Cache文件,以提供下次快速加载,所以第一次会很慢。
Dalvik解释器采用预先算好的Goto地址,每个指令对内存的访问都在64字节边界上对齐。这样可以节省一个指令后进行查表的时间。为了强化功能, Dalvik还提供了快速翻译器(Fast Interpreter)。
一般来说,基于堆栈的机器必须使用指令才能从堆栈上的加载和操作数据,因此,相对基于寄存器的机器,它们需要更多的指令才能实现相同的性能。但是基于寄存器机器上的指令必须经过编码,因此,它们的指令往往更大。
Dalvik虚拟机既不支持Java SE 也不支持Java ME类库(如:Java类,AWT和Swing都不支持)。 相反,它使用自己建立的类库(Apache Harmony Java的一个子集)。
2.3 什么是ART?
即Android Runtime
ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。
2.4 ART有什么优缺点呢?
优点:
1、系统性能的显著提升。
2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。
3、更长的电池续航能力。
4、支持更低的硬件。
缺点:
1.机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20%(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
2.应用的安装时间会变长。
tips:现在智能手机大部分都可以让用户选择使用Dalvik还是ART模式。当然默认还是使用Dalvik模式。
用法:设置-辅助功能-开发者选项(开发人员工具)-选择运行环境(不同的手机设置的步骤可能不一样)。
三、ClassLoader 的构造函数
3.1 BaseDexClassLoader 构造函数
PathClassLoader 和 DexClassLoader 都是继承了 BaseDexClassLoader,这里先看一下。 BaseDexClassLoader 的构造函数。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
BaseDexClassLoader 构造函数有四个参数,含义如下:
- dexPath: 需要加载的文件列表,文件可以是包含了 classes.dex 的 JAR/APK/ZIP,也可以直接使用 classes.dex 文件,多个文件用 “:” 分割
- optimizedDirectory: 存放优化后的 dex,可以为空
- libraryPath: 存放需要加载的 native 库的目录
- parent: 父 ClassLoader
通过构造函数我们大概可以了解到 BaseDexClassLoader 的运行方式,传入 dex 文件,然后进行优化,保存优化后的 dex 文件到 optimizedDirectory 目录。
3.2 PathClassLoader 构造函数
接着我们再看 PathClassLoader 的构造函数。
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
/**
* Creates a {@code PathClassLoader} that operates on two given
* lists of files and directories. The entries of the first list
* should be one of the following:
*
*
* - JAR/ZIP/APK files, possibly containing a "classes.dex" file as
* well as arbitrary resources.
*
- Raw ".dex" files (not inside a zip file).
*
*
* The entries of the second list should be directories containing
* native library files.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
关于 PathClassLoader 有一点稍微注意一下,代码注释中对 PathClassLoader 的介绍是,用来操作文件系统上的一系列文件和目录 的 ClassLoader 实现。其中并没有提到只能加载安装后的 apk 文件。
PathClassLoader 有两个构造函数,区别在于传给 BaseDexClassLoader 的 libraryPath 是否为空。最终调用 BaseDexClassLoader 构造函数时,传入的 optimizedDirectory 为空。
3.3 DexClassLoader 构造函数
再来看看 DexClassLoader 的构造函数。和 BaseDexClassLoader 构造函数的参数是一样的。
public class DexClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code DexClassLoader} that finds interpreted and native
* code. Interpreted classes are found in a set of DEX files contained
* in Jar or APK files.
*
* The path lists are separated using the character specified by the
* {@code path.separator} system property, which defaults to {@code :}.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; must not be {@code null}
* @param librarySearchPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
通过上面对构造函数的分析,我们可以明白,PathClassLoader 和 DexClassLoader 关键不同点,在 optimizedDirectory 参数上,PathClassLoader 传入的是 null,而 DexClassLoader 传入的是用户指定的目录。
四、双亲委派机制
4.1 模型说明
4.2 工作流程讲解
- 双亲委派模型的工作流程代码实现在
java.lang.ClassLoader的loadClass()
中 - 具体如下
@Override
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class> c = findLoadedClass(name);
// 检查需要加载的类是否已经被加载过
if (c == null) {
try {
// 若没有加载,则调用父加载器的loadClass()方法
if (parent != null) {
c = parent.loadClass(name, false);
}else{
// 若父类加载器为空,则默认使用启动类加载器作为父加载器
c=findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 若父类加载器加载失败会抛出ClassNotFoundException,
//说明父类加载器无法完成加载请求
}
if(c==null){
// 在父类加载器无法加载时
// 再调用本身的findClass方法进行类加载
c=findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
步骤总结:若一个类加载器收到了类加载请求
- 把 该类加载请求 委派给 父类加载器去完成,而不会自己去加载该类
每层的类加载器都是如此,因此所有的加载请求最终都应传送到顶层的启动类加载器中
- 只有当 父类加载器 反馈 自己无法完成该加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会自己去加载
4.3 优点
Java
类随着它的类加载器一起具备了一种带优先级的层次关系
- 如:类
java.lang.Object
(存放在rt.jar
中)在加载过程中,无论哪一个类加载器要加载这个类,最终需委派给模型顶端的启动类加载器进行加载,因此Object
类在程序的各种类加载器环境中都是同一个类。- 若没有使用双亲委派模型(即由各个类加载器自行去加载)、用户编写了一个
java.lang.Object
的类(放在ClassPath
中),那系统中将出现多个不同的Object
类,Java体系中最基础的行为就无法保证
在讲完系统的类加载器后,下面我将讲解如何根据需求自定义类加载器。
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
推荐阅读:转自“腾讯高级工程师写给全国移动互联网工作者的一封公开信”
参考:https://www.jianshu.com/p/8743d8062bb6
https://blog.csdn.net/m_sdn/article/details/84856273
https://www.jianshu.com/p/58f817d176b7
https://segmentfault.com/a/1190000020254261