class, classloder, dex 详解
class与dex文件
什么是class文件
class文件是一种能够被JVM识别,加载并且执行的文件格式。
class文件的作用
class文件的作用是记录一个类文件的所有信息。
例如记住了当前类的引用this、父类super等等。class文件记录的信息往往比java文件多。
class文件的结构
- 8位字节的二进制流文件
- 各个数据紧密排列,无间隙,减少了文件体积,加快加载速度
- 每个类或者接口单独占据一个class文件,每个类单独管理,没有交叉
class文件的弊端
- 内存占用大,不适合于移动端
- 堆栈的加载模式导致加载速度慢
- 文件IO操作多,类查找慢
什么是dex文件
能够被DVM或者Art虚拟机执行并且加载的文件格式。
dex文件的作用
dex文件的作用是记录整个工程(通常是一个Android工程)的所有类文件的信息。
dex文件的结构
- 8位字节的二进制流文件
- 各个数据紧密排列,无间隙,减少了文件体积,加快加载速度
- 整个工程的类信息都存放在一个dex文件中(不考虑dex分包的情况下)
class文件与dex文件的比较
- 本质上都是一样的,都是二进制流文件格式,dex文件是从class文件演变而来的
- class文件存在冗余信息,dex文件则去掉了冗余,并且整合了整个工程的类信息。
结构对比图如下:

类加载机制是做热修复以及插件化的很重要的理论基础。
Java中的ClassLoader回顾
如下图所示:

ClassLoader的特性
ClassLoader的主要特性是双亲委托机制,即加载一个类的时候,先判断已经存在的类是否被加载过,如果没有,先去委托父亲去加载。如果父亲都没有加载成功的话,那么最终由自己加载。最终这个类最终没有合适的CLassLoader加载,那么就会抛出异常,相关的ClassLoader源码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 先判断这个类是否已经被加载过
Class c = findLoadedClass(name);
if (c == null) {
// 如果没有被加载过,那么委托父亲去加载
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 如果父亲没有加载,那么最终由自己(实现类)负责加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // this is the defining class loader; record the stats
}
}
return c;
}
其中CLassLoader的findClass方法是空实现,需要自己继承然后实现:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
这种双亲委托机制有两种好处:
实现类加载的共享功能,提升类加载的效率。
实现类加载的隔离功能,提升系统的安全性。比如,通过这种方式,系统的String类只能由系统的ClassLoader加载。
Android中的ClassLoader整体概述
Android中的ClassLoader的整体架构继承关系如下:

其中,ClassLoader分别为:
- BootClassLoader:与Java中的Bootstrap ClassLoader类似,主要加载Android Framework中的字节码文件。
- PathClassLoader:与Java中的App ClassLoader类似,主要加载已经安装到系统中的APK中的字节码文件。
- DexClassLoader:与Java中的Customer ClassLoader类似,主要加载自定义路径下的APK或者JAR中的字节码文件(Android中主要是指dex文件,即classes.dex)。
其中,BaseDexClassLoader是PathClassLoader以及DexClassLoader的父类,PathClassLoader以及DexClassLoader的逻辑都在这个父类中实现。
BootClassLoader和PathClassLoader是一个普通的APP所需要的(定制ROM另外说),最基本的ClassLoader,通过下面的代码可以证明:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); ClassLoader cl = this.getClassLoader();
Log.e(TAG, "onCreate: " + cl);
while (cl.getParent() != null) {
cl = cl.getParent();
Log.e(TAG, "onCreate: " + cl);
} }
打印结果只有BootClassLoader和PathClassLoader:
09-06 14:29:01.571 3165-3165/com.nan.loadapkdemo E/MainActivity: onCreate: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.nan.loadapkdemo-1/base.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_dependencies_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_0_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_1_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_2_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_3_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_4_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_5_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_6_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_7_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_8_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/data/app/com.nan.loadapkdemo-1/lib/x86, /system/lib, /vendor/lib]]]
09-06 14:29:01.571 3165-3165/com.nan.loadapkdemo E/MainActivity: onCreate: java.lang.BootClassLoader@b173b34
上面就分析完了classes.dex文件是如何通过ClassLoader的初始化装载进来的,下面继续分析一个类是如何通过PathClassLoader或者DexClassLoader进行加载的,这时候就需要看父类BaseDexClassLoader的findClass方法了:
@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;
}
这里面看到,实际上就是调用了PathList的findClass方法:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile; if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
这个方法就是遍历初始化创建好的内部Element[]数组里面的DexFile对象,最终是通过DexFile的loadClassBinaryName进行加载的:
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
defineClass的实现如下:
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);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
到最后,defineClassNative是一个native方法:
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)
defineClassNative 是通过C/C++实现,这个方法去dex文件中查找指定name的类,并且拼接成Class字节码,返回给Java层。通过native实现是为了提高效率。
整个加载流程一气呵成,如下图所示:

实现动态加载
如下面所示,演示了最简单的动态加载的方案:
public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String apkPath = getExternalCacheDir().getAbsolutePath() + "/bundle.apk";
loadApk(apkPath);
} private void loadApk(String apkPath) {
File optFile = getDir("opt", MODE_PRIVATE);
// 通过DexClassLoader加载制定的APK文件
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optFile.getAbsolutePath(), null, getClassLoader());
try {
// 通过反射去使用对象
Class clz = dexClassLoader.loadClass("com.loubinfeng.www.boundle.Printer");
if (clz != null) {
Object instance = clz.newInstance();
Method method = clz.getMethod("print");
method.invoke(instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
实现动态加载的难点
实现动态加载难点比较多:
- 资源的访问问题
- 四大组件的生命周期管理问题
- 插件ClassLoader的管理
- so库的动态加载等等
参考文章:
class, classloder, dex 详解的更多相关文章
- JAVA_Android APK反编译就这么简单 详解(附图)
在学习Android开发的过程你,你往往会去借鉴别人的应用是怎么开发的,那些漂亮的动画和精致的布局可能会让你爱不释手,作为一个开发者,你可能会很想知道这些效果界面是怎么去实现的,这时,你便可以对改应用 ...
- 【转】Android APK反编译就这么简单 详解(附图)
转载地址:http://blog.csdn.net/vipzjyno1/article/details/21039349 在学习Android开发的过程你,你往往会去借鉴别人的应用是怎么开发的,那些漂 ...
- Android APK反编译easy 详解
在学习Android开发的过程你,你往往会去借鉴别人的应用是怎么开发的,那些漂亮的动画和精致的布局可能会让你爱不释手,作为一个开发者,你可能会很想知道这些效果界面是怎么去实现的,这时,你便可以对改应用 ...
- Android APK反编译就这么简单 详解(附图)
在学习Android开发的过程你,你往往会去借鉴别人的应用是怎么开发的,那些漂亮的动画和精致的布局可能会让你爱不释手,作为一个开发者,你可能会很想知道这些效果界面是怎么去实现的,这时,你便可以对改应用 ...
- AVL树详解
AVL树 参考了:http://www.cppblog.com/cxiaojia/archive/2012/08/20/187776.html 修改了其中的错误,代码实现并亲自验证过. 平衡二叉树(B ...
- Android签名详解(debug和release)
Android签名详解(debug和release) 1. 为什么要签名 1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,以此保证签名不同的包 ...
- Android OS体系结构详解
Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统.中间件.用户界面和应用软件组成,号称是首个为移动终端打造的真正开放和完整的移动软件. 架构详解 下 ...
- (转)[置顶] Android APK反编译就这么简单 详解(附图) .
在学习Android开发的过程你,你往往会去借鉴别人的应用是怎么开发的,那些漂亮的动画和精致的布局可能会让你爱不释手,作为一个开发者,你可能会很想知道这些效果界面是怎么去实现的,这时,你便可以对改应用 ...
- Android系统目录结构详解
Android系统基于linux内核.JAVA应用,算是一个小巧精致的系统.虽是开源,但不像Linux一般庞大,娇小可亲,于是国内厂商纷纷开发出自己基于Android的操作系统.在此呼吁各大厂商眼光放 ...
随机推荐
- python学习之路day2
模块学习: 标准库: os: 第三方库:
- Kotlin 一个好用的新功能:Parcelize
在开发中,如果有需要用到序列化和反序列化的操作,就会用到 Serializable 或者 Parcelable,它们各有优缺点,会适用于不同的场景. Serializable 的优点是实现简单,你只需 ...
- SpringBoot运行原理
如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的 ...
- 关于Unicode,字符集,字符编码
基本概念 字符[character] 字符代表了字母表中的字符,标点符号和其他的一些符号.在计算机中,文本是由字符组成的. 字符集合[character set] 由一套用于特定用途的字符组成,例如支 ...
- Android 通知栏Notification的整合 全面学习 (一个DEMO让你全然了解它)
在android的应用层中,涉及到非常多应用框架.比如:Service框架,Activity管理机制,Broadcast机制,对话框框架,标题栏框架,状态栏框架.通知机制,ActionBar框架等等. ...
- Go语言核心之美-必读
Go语言核心之美开篇了!.不管你是新手还是一代高人,在这个系列文章中.总能找到你想要的! 博主是计算机领域资深专家并且是英语专8水平,翻译标准仅仅有三个:精确.专业.不晦涩,为此每篇文章可能都要耗费数 ...
- UML总结复习指南
用例图 1. 參与者(Actor) 表示与您的应用程序或系统进行交互的用户.组织或外部系统.用一个小人表示. 2. 用例(Use Case) 用例就是外部可见的系统功能,对系统提供的服务进行描 ...
- UI - Cocoa Touch框架
Cocoa Touch 层 Cocoa Touch层包括创建 iOS应用程序所需的关键框架. 上至实现应用程序可视界面,下至与高级系统服务交互.都须要该层技术提供底层基础.在开发应用程序的时候.请尽可 ...
- JAVA入门[3]—Spring依赖注入
Spring支持属性注入和构造器注入,它支持XML和注解两种方式.本文介绍Spring控制反转容器加载包含beans的XML文件,实现依赖注入. 一.创建bean实例 暂且抛开对象依赖,我们先看下如何 ...
- 中国版Office 365 应用程序注册
作者:陈希章 发表于 2017年3月23日 中国版Office 365是由世纪互联进行运营的一个云服务,单纯从技术角度来看的话,它基本保持了与国际版的同步.但是由于两个版本本质上是完全独立的,其中最关 ...