Android加壳与脱壳(1)——深入理解类加载器和动态加载
一、前言
最近一直在学习Android 加壳和脱壳,在进行Android加壳和脱壳的学习中,第一步便是深入理解类加载器和动态加载二者之间的关系,本文详细的介绍了类加载器和动态加载之间的关系和原理,之所以详细的讲解两者之间的关系,一是学习脱壳和加壳的需要,二是为后面Android插件化漏洞挖掘的讲解做铺垫。
二、类加载器
Android中的类加载器机制与JVM一样遵循双亲委派模式
1.双亲委派模式
(1)双亲委派模式定义
1 2 | ( 1 )加载. class 文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍
( 2 )如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
/ / 1. 先检查是否已经加载过 - - findLoaded
Class<?> c = findLoadedClass(name);
if (c = = null) {
try {
/ / 2. 如果自己没加载过,存在父类,则委托父类
if (parent ! = null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c = = null) {
/ / 3. 如果父类也没加载过,则尝试本级classLoader加载
c = findClass(name);
}
}
return c;
}
|
1 2 3 4 | 代码解释:
( 1 )先检查自己是否已经加载过 class 文件,用findLoadedClass方法,如果已经加载了直接返回
( 2 )如果自己没有加载过,存在父类,则委派父类去加载,用parent.loadClass(name,false)方法,此时会向上传递,然后去父加载器中循环第 1 步,一直到顶级ClassLoader
( 3 ) 如果父类没有加载,则尝试本级classLoader加载,如果加载失败了就会向下传递,交给调用方式实现. class 文件的加载
|
(2)双亲委派模式加载流程
1 2 3 4 5 6 7 | 我们要加载一个 class 文件,我们定义了一个CustomerClassLoader类加载器:
( 1 )首先会判断自己的CustomerClassLoader否加载过,如果加载过直接返回,
( 2 )如果没有加载过则会调用父类PathClassLoader去加载,该父类同样会判断自己是否加载过,如果没有加载过则委托给父类BootClassLoader去加载,
( 3 )这个BootClassLoader是顶级classLoader,同样会去判断自己有没有加载过,如果也没有加载过则会调用自己的findClass(name)去加载,
( 4 )如果顶级BootClassLoader加载失败了,则会把加载这个动作向下交还给PathClassLoader,
( 5 )这个PathClassLoader也会尝试去调用findClass(name);去加载,如果加载失败了,则会继续向下交还给CustomClassLoader来完成加载,这整个过程感觉是一个递归的过程,逐渐往上然后有逐渐往下,直到加载成功
其实这个String. class 在系统启动的时候已经被加载了,我们自己定义一个CustomerClassLoader去加载,其实也是父类加载的
|
(3)双亲委派的作用
1 2 3 | ( 1 ) 防止同一个. class 文件重复加载
( 2 ) 对于任意一个类确保在虚拟机中的唯一性.由加载它的类加载器和这个类的全类名一同确立其在Java虚拟机中的唯一性
( 3 ) 保证. class 文件不被篡改,通过委派方式可以保证系统类的加载逻辑不被篡改
|
2. Android中类加载机制
(1)Android基本类预加载
我们了解Android基本类预加载,首先我们回顾上文的Dalvik虚拟机启动相关:
我们执行app_process程序,进入main函数里面,然后进行AndroidRuntime::start
:
1 2 3 4 | Zygote native 进程主要工作:
( 1 )创建虚拟机–startVM
( 2 )注册JNI函数–startReg
( 3 )通过JNI知道Java层的com.android.internal.os.ZygoteInit 类,调用main 函数,进入java 世界
|
然后进入Java层:
1 2 3 4 5 6 7 8 | Zygote总结:
( 1 )解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法
( 2 )调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数
( 3 )通过JNI方式调用ZygoteInit.main(),第一次进入Java世界
( 4 )registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求
( 5 )preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率
( 6 )通过startSystemServer(),fork得力帮手system_server进程,也是Java Framework的运行载体(下面讲到system server再详细讲解)
( 7 )调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作
|
Android的类加载机制和JVM一样遵循双亲委派模式,在dalvik/art启动时将所有Java基本类和Android系统框架的基本类加载进来,预加载的类记录在/frameworks/base/config/preloaded-classes
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | android.R$styleable
android.accessibilityservice.AccessibilityServiceInfo$ 1
android.accessibilityservice.AccessibilityServiceInfo
android.accessibilityservice.IAccessibilityServiceClient$Stub$Proxy
android.accessibilityservice.IAccessibilityServiceClient$Stub
android.accessibilityservice.IAccessibilityServiceClient
android.accounts.AbstractAccountAuthenticator$Transport
android.accounts.AbstractAccountAuthenticator
android.accounts.Account$ 1
android.accounts.Account
...
java.lang.Short
java.lang.StackOverflowError
java.lang.StackTraceElement
java.lang.StrictMath
java.lang.String$ 1
java.lang.String$CaseInsensitiveComparator
java.lang.String
java.lang.StringBuffer
java.lang.StringBuilder
java.lang.StringFactory
java.lang.StringIndexOutOfBoundsException
java.lang.System$PropertiesWithNonOverrideableDefaults
java.lang.System
java.lang.Thread$ 1
...
|
这些类只需要在Zygote进程启动时加载一遍就可以了,后续没一个APP或Android运行时环境的进程,都是从Zygote中fork出来,天然保留了加载过的类缓存
ZygoteInit.preload()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | static void preload(TimingsTraceLog bootTimingsTraceLog) {
/ / ...省略
preloadClasses();
/ / ...省略
}
private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
/ / 读取 preloaded_classes 文件
InputStream is ;
try {
is = new FileInputStream(PRELOADED_CLASSES);
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + "." );
return ;
}
/ / ...省略
try {
BufferedReader br =
new BufferedReader(new InputStreamReader( is ), Zygote.SOCKET_BUFFER_SIZE);
int count = 0 ;
String line;
while ((line = br.readLine()) ! = null) {
/ / Skip comments and blank lines.
line = line.trim();
if (line.startsWith( "#" ) || line.equals("")) {
continue ;
}
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
try {
/ / 逐行加载基本类
Class.forName(line, true, null);
count + + ;
/ / ...省略
} catch (Throwable t) {
/ / ...省略
}
}
/ / ...省略
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + "." , e);
} finally {
/ / ...省略
}
}
|
(2)Android类加载器层级关系及分析
1 2 3 4 5 6 7 8 | Android中的ClassLoader类型分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括 3 种是BootClassLoader、DexClassLoader、PathClassLoader
( 1 )BootClassLoader:Android平台上所有Android系统启动时会使用BootClassLoader来预加载常用的类
( 2 )BaseDexClassLoader:实际应用层类文件的加载,而真正的加载委托给pathList来完成
( 3 )DexClassLoader:可以加载dex文件以及包含dex的压缩文件(apk,dex,jar, zip ),可以安装一个未安装的apk文件,一般为自定义类加载器
( 4 )PathClassLoader:可以加载系统类和应用程序的类,通常用来加载已安装的apk的dex文件
补充:
Android 提供的原生加载器叫做基础类加载器,包括:BootClassLoader,PathClassLoader,DexClassLoader,InMemoryDexClassLoader(Android 8.0 引入),DelegateLastClassLoader(Android 8.1 引入)
|
<1> BootClassLoader
1 | 启动类加载器,用于加载 Zygote 进程已经预加载的基本类,可以推测它只需从缓存中加载。这是基类 ClassLoader 的一个内部类,是包访问权限,所以应用程序无权直接访问
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | public abstract class ClassLoader {
/ / ...省略
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
public static synchronized BootClassLoader getInstance() {
if (instance = = null) {
instance = new BootClassLoader();
}
return instance;
}
public BootClassLoader() {
super (null);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
/ / ...省略
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz = = null) {
clazz = findClass(className);
}
return clazz;
}
/ / ...省略
}
}
|
1 2 3 | 源码分析:
我们可以看见,BootClassLoader没有父加载器,在缓存取不到类是直接调用自己的findClass()方法
findClass()方法调用Class.classForName()方法,而ZygoteInit.preloadClasses()中,加载基本类是Class.forName()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | ublic final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type ,
AnnotatedElement {
/ / ...省略
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName(className, true, ClassLoader.getClassLoader(caller));
}
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader = = null) {
loader = BootClassLoader.getInstance();
}
Class<?> result;
try {
result = classForName(name, initialize, loader);
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}
/ / 本地方法
static native Class<?> classForName(String className, boolean shouldInitialize,
ClassLoader classLoader) throws ClassNotFoundException;
/ / ...省略
}
|
1 2 | 我们可以发现,预加载时,ZygoteInit.preloadClasses()中调用Class.forName(),实际是指定BootClassLoader为类加载器,且只需要在预加载的时候进行类初始化,只需要一次
总之,通过 Class.forName() 或者 Class.classForName() 可以且仅可以直接加载基本类,一旦基本类预加载后,对于应用程序而言,我们虽然不能直接访问BootClassLoader,但可以通过Class.forName / Class.classForName加载
|
无论是系统类加载器(PathClassLoader)还是自定义的类加载器(DexClassLoader),最顶层的祖先加载器默认是 BootClassLoader,与 JVM 一样,保证了基本类的类型安全
Class文件加载:
1 2 3 | 1. 通过Class.forName()方法动态加载
2. 通过ClassLoader.loadClass()方法动态加载
类的加载分为 3 个步骤: 1. 装载(Load), 2. 链接(Link), 3. 初始化(Intialize)
|
类加载时机:
1 2 3 4 5 6 7 8 9 | 1. 隐式加载:
( 1 )创建类的实例,也就是new一个对象
( 2 )访问某个类或接口的静态变量,或者对该静态变量赋值
( 3 )调用类的静态方法
( 4 )反射Class.forName( "android.app.ActivityThread" )
( 5 )初始化一个类的子类(会首先初始化子类的父类)
2. 显示加载:
( 1 )使用LoadClass()加载
( 2 )使用forName()加载
|
1 2 3 | Class.forName 和 ClassLoader.loadClass加载有何不同:
( 1 )ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)
( 2 )Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作
|
<2> PathClassLoader
主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
1 | PathClassLoader 是作为应用程序的系统类加载器,也是在 Zygote 进程启动的时候初始化的(基本流程为:ZygoteInit.main() - > ZygoteInit.forkSystemServer() - > ZygoteInit.handleSystemServerProcess() - > ZygoteInit.createPathClassLoader()。在预加载基本类之后执行),所以每一个 APP 进程从 Zygote 中 fork 出来之后都自动携带了一个 PathClassLoader,它通常用于加载 apk 里面的 .dex 文件
|
1 2 3 4 5 6 7 8 9 10 | 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);
}
}
|
<3> DexClassLoader
可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载, 但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多热修复和插件化方案都是采用DexClassLoader
1 2 3 4 5 6 7 8 | public class
DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super (dexPath, new File (optimizedDirectory), librarySearchPath, parent);
}
}
|
1 2 3 4 | 总结:
我们可以发现DexClassLoader与PathClassLoader都继承于BaseDexClassLoader,这两个类只是提供了自己的构造函数,没有额外的实现
区别:
DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有,optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载
|
<4> BaseDexClassLoader
1 2 3 4 5 6 7 8 | public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList; / / 记录dex文件路径信息
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super (parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
|
1 2 3 4 | dexPath: 包含目标类或资源的apk / jar列表;当有多个路径则采用:分割;
optimizedDirectory: 优化后dex文件存在的目录, 可以为null;
libraryPath: native库所在路径列表;当有多个路径则采用:分割;
ClassLoader:父类的类加载器
|
BaseDexClassLoader会初始化dexPathList,收集dex文件和Native文件动态库
初始化:
DexPathList:
该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | final class DexPathList {
private Element[] dexElements;
private final List < File > nativeLibraryDirectories;
private final List < File > systemNativeLibraryDirectories;
final class DexPathList {
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
/ / 记录所有的dexFile文件
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
/ / app目录的native库
this.nativeLibraryDirectories = splitPaths(libraryPath, false);
/ / 系统目录的native库
this.systemNativeLibraryDirectories = splitPaths(System.getProperty( "java.library.path" ), true);
List < File > allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
/ / 记录所有的Native动态库
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);
...
}
}
|
1 2 3 | DexPathList初始化过程,主要收集以下两个变量信息:
( 1 )dexElements: 根据多路径的分隔符“;”将dexPath转换成 File 列表,记录所有的dexFile
( 2 )nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库
|
makePathElements:
1 2 3 4 | private static Element[] makePathElements( List < File > files, File optimizedDirectory,
List <IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}
|
makeDexElements:
makeDexElements方法的作用是获取一个包含dex文件的元素集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | private static Element[] makeDexElements( List < File > files, File optimizedDirectory,
List <IOException> suppressedExceptions, ClassLoader loader) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
}
private static Element[] makeDexElements( List < File > files, File optimizedDirectory,
List <IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
Element[] elements = new Element[files.size()]; / / 获取文件个数
int elementsPos = 0 ;
for ( File file : files) {
if ( file .isDirectory()) {
elements[elementsPos + + ] = new Element( file );
} else if ( file .isFile()) {
String name = file .getName();
DexFile dex = null;
/ / 匹配以.dex为后缀的文件
if (name.endsWith(DEX_SUFFIX)) {
dex = loadDexFile( file , optimizedDirectory, loader, elements);
if (dex ! = null) {
elements[elementsPos + + ] = new Element(dex, null);
}
} else {
dex = loadDexFile( file , optimizedDirectory, loader, elements);
if (dex = = null) {
elements[elementsPos + + ] = new Element( file );
} else {
elements[elementsPos + + ] = new Element(dex, file );
}
}
if (dex ! = null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW( "ClassLoader referenced unknown path: " + file );
}
}
if (elementsPos ! = elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
|
该方法的主要功能是创建Element数组
loadDexFile:
加载DexFile文件,而且会把优化后的dex文件缓存到对应目录
1 2 3 4 5 6 7 8 9 10 | private static DexFile loadDexFile( File file , File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory = = null) {
return new DexFile( file , loader, elements); / / 创建DexFile对象
} else {
String optimizedPath = optimizedPathFor( file , optimizedDirectory);
return DexFile.loadDex( file .getPath(), optimizedPath, 0 , loader, elements);
}
}
|
DexFile:
用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的
1 2 3 4 5 6 7 8 9 10 | DexFile( File file , ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
this( file .getPath(), loader, elements);
}
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
mCookie = openDexFile(fileName, null, 0 , loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
}
|
openDexFile:
1 2 3 4 5 6 7 8 | private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
return openDexFileNative(new File (sourceName).getAbsolutePath(),
(outputName = = null) ? null : new File (outputName).getAbsolutePath(),
flags,
loader,
elements);
}
|
1 2 3 4 5 6 | 此时参数取值说明:
sourceName为PathClassLoader构造函数传递的dexPath中以分隔符划分之后的文件名;
outputName为null;
flags = 0
loader为null;
elements为makeDexElements()过程生成的Element数组;
|
openDexFileNative:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | static jobject DexFile_openDexFileNative(JNIEnv * env,
jclass,
jstring javaSourceName,
jstring javaOutputName ATTRIBUTE_UNUSED,
jint flags ATTRIBUTE_UNUSED,
jobject class_loader,
jobjectArray dex_elements) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() = = nullptr) {
return 0 ;
}
Runtime * const runtime = Runtime::Current();
ClassLinker * linker = runtime - >GetClassLinker();
std::vector<std::unique_ptr<const DexFile>> dex_files;
std::vector<std::string> error_msgs;
const OatFile * oat_file = nullptr;
dex_files = runtime - >GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
class_loader,
dex_elements,
/ * out * / &oat_file,
/ * out * / &error_msgs);
if (!dex_files.empty()) {
jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
...
return array;
} else {
...
return nullptr;
}
}
|
这样就完成了dex的加载过程,而BaseDexClassLoader
派生出两个子类加载器:PathClassLoader
和DexClassLoader
Android中如果parent类加载器加载不到类,最终还是会调用ClassLoader对象自己的findClass()方法
loadClass()加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public abstract class ClassLoader {
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
/ / 判断当前类加载器是否已经加载过指定类,若已加载则直接返回
Class<?> clazz = findLoadedClass(className);
if (clazz = = null) {
/ / 如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回
clazz = parent.loadClass(className, false);
if (clazz = = null) {
/ / 还没加载,则调用当前类加载器来加载
clazz = findClass(className);
}
}
return clazz;
}
}
|
1 2 3 4 | 该方法的加载流程如下:
( 1 )判断当前类加载器是否已经加载过指定类,若已加载则直接返回,否则继续执行;
( 2 )调用parent的类加载递归加载该类,检测是否加载,若已加载则直接返回,否则继续执行;
( 3 )调用当前类加载器,通过findClass加载。
|
findLoadedClass:
[-> ClassLoader.java]
1 2 3 4 5 6 7 8 | protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this = = BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, name);
}
|
findClass:
[-> BaseDexClassLoader.java]
1 2 3 4 5 6 7 | public class BaseDexClassLoader extends ClassLoader {
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class c = pathList.findClass(name, suppressedExceptions);
...
return c;
}
}
|
DexPathList.findClass:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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;
}
}
}
return null;
}
|
1 2 3 4 | 代码解释:
一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组 dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面
热修复原理:
现在很多热修复技术就是把修复的dex文件放在DexPathList中Element[]数组的前面,这样就实现了修复后的Class抢先加载了,达到了修改bug的目的
|
DexFile.loadClassBinaryName:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public final class DexFile {
public Class loadClassBinaryName(String name, ClassLoader loader, List <Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie, List <Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie);
} catch (NoClassDefFoundError e) {
if (suppressed ! = null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed ! = null) {
suppressed.add(e);
}
}
return result;
}
}
|
defineClassNative()这是native方法
defineClassNative:
[-> dalvik_system_DexFile.cc]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | static jclass DexFile_defineClassNative(JNIEnv * env, jclass, jstring javaName, jobject javaLoader,
jobject cookie) {
std::unique_ptr<std::vector<const DexFile * >> dex_files = ConvertJavaArrayToNative(env, cookie);
if (dex_files.get() = = nullptr) {
return nullptr; / / dex文件为空, 则直接返回
}
ScopedUtfChars class_name(env, javaName);
if (class_name.c_str() = = nullptr) {
return nullptr; / / 类名为空, 则直接返回
}
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash (ComputeModifiedUtf8Hash(descriptor.c_str())); / / 将类名转换为 hash 码
for (auto& dex_file : * dex_files) {
const DexFile::ClassDef * dex_class_def = dex_file - >FindClassDef(descriptor.c_str(), hash );
if (dex_class_def ! = nullptr) {
ScopedObjectAccess soa(env);
ClassLinker * class_linker = Runtime::Current() - >GetClassLinker();
class_linker - >RegisterDexFile( * dex_file);
StackHandleScope< 1 > hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader * >(javaLoader)));
/ / 获取目标类
mirror::Class * result = class_linker - >DefineClass(soa.Self(), descriptor.c_str(), hash ,
class_loader, * dex_file, * dex_class_def);
if (result ! = nullptr) {
/ / 找到目标对象
return soa.AddLocalReference<jclass>(result);
}
}
}
return nullptr; / / 没有找到目标类
}
|
在native层创建目标类的对象并添加到虚拟机列表
我们继续分析Native层可以发现:
1 2 | DexFile.defineClassNative() 的实现在 / art / runtime / native / dalvik_system_DexFile.cc,最终由 ClassLinker.DefineClass() 实现
Class.classForName() 的实现在 / art / runtime / native / java_lang_Class.cc,最终由 ClassLinker.FindClass() 实现
|
ClassLinker核心原理:
1 2 | 先从已加载类的 class_table 中查询,若找到则直接返回;若找不到则说明该类是第一次加载,则执行加载流程,其中可能需要穿插加载依赖的类,加载完成后将其缓存到 class_table 中
在 ClassLinker 中,会维护两类 class_table,一类针对基本类,一类针对其它的类。class_table 是作为缓存已经加载过的类的缓冲池。不管以什么样的方式去加载类,都需要先从 class_table 中先进行查询以提高加载性能
|
ClassLinker 在加载类的时候遇到该类依赖的类,进行穿插加载依赖类:
我们总结BaseDexClassLoader初始化和加载原理:
Android类加载详细流程:
3.案例
(1)验证类加载器
我们验证App中的MainActivity类加载器和系统类String类的类加载器:
1 2 3 4 | ClassLoader thisclassloader = MainActivity. class .getClassLoader();
ClassLoader StringClassloader = String. class .getClassLoader();
Log.e( "ClassLoader1" , "MainActivity is in" + thisclassloader.toString());
Log.e( "ClassLoader1" , "String is in" + StringClassloader.toString());
|
我们可以明显发现PathClassLoader
加载已安装的APK
类加载器,而BootClassLoader
加载系统预安装的类
(2)遍历父类加载器
1 2 3 4 5 6 7 8 | public static void printClassLoader(ClassLoader classLoader) {
Log.e( "printClassLoader" , "this->" + classLoader.toString());
ClassLoader parent = classLoader.getParent();
while (parent! = null){
Log.i( "printClassLoader" , "parent->" + parent.toString());
parent = parent.getParent();
}
}
|
(3)验证双亲委派机制
1 2 3 4 5 6 7 | try {
Class StringClass = thisclassloader.loadClass( "java.lang.String" );
Log.e( "ClassLoader1" , "load StringClass!" + thisclassloader.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.e( "ClassLoader1" , "load MainActivity fail!" + thisclassloader.toString());
}
|
我们使用PathClassLoader
去加载 String.class类,还是可以加载成功,因为双亲委派的机制
(4)动态加载
这里我借用网上寒冰大佬动态加载的案例,来进一步讲述使用DexClassLoader类实现简单的动态加载插件dex,并验证ClassLoader的继承关系
我们先编写一个测试类文件,然后生成dex文件
我们先将dex文件放到模拟器的sdcard/下
我们新建一个程序,然后编写主程序的代码,并授权sd读取权限
1 2 | Context appContext = this.getApplication();
testDexClassLoader(appContext, "/sdcard/classes.dex" );
|
1 2 | <uses - permission android:name = "android.permission.READ_EXTERNAL_STORAGE" / >
<uses - permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE" / >
|
然后我们编写类加载器代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | private void testDexClassLoader(Context context, String dexfilepath) {
/ / 构建文件路径: / data / data / com.emaxple.test02 / app_opt_dex,存放优化后的dex,lib库
File optfile = context.getDir( "opt_dex" , 0 );
File libfile = context.getDir( "lib_dex" , 0 );
ClassLoader parentclassloader = MainActivity. class .getClassLoader();
ClassLoader tmpclassloader = context.getClassLoader();
/ / 可以为DexClassLoader指定父类加载器
DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),parentclassloader);
Class clazz = null;
try {
clazz = dexClassLoader.loadClass( "com.example.test.TestClass" );
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (clazz! = null){
try {
Method testFuncMethod = clazz.getDeclaredMethod( "test02" );
Object obj = clazz.newInstance();
testFuncMethod.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
|
(5)获得类列表
我们通过getClassNameList来获取类列表
1 | private static native String[] getClassNameList( Object cookie);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public static void getClassListInClassLoader(ClassLoader classLoader){
/ / 先拿到BaseDexClassLoader
try {
/ / 拿到pathList
Class BaseDexClassLoader = Class.forName( "dalvik.system.BaseDexClassLoader" );
Field pathListField = BaseDexClassLoader.getDeclaredField( "pathList" );
pathListField.setAccessible(true);
Object pathListObj = pathListField.get(classLoader);
/ / 拿到dexElements
Class DexElementClass = Class.forName( "dalvik.system.DexPathList" );
Field DexElementFiled = DexElementClass.getDeclaredField( "dexElements" );
DexElementFiled.setAccessible(true);
Object [] dexElementObj = ( Object []) DexElementFiled.get(pathListObj);
/ / 拿到dexFile
Class Element = Class.forName( "dalvik.system.DexPathList$Element" );
Field dexFileField = Element.getDeclaredField( "dexFile" );
dexFileField.setAccessible(true);
Class DexFile = Class.forName( "dalvik.system.DexFile" );
Field mCookieField = DexFile.getDeclaredField( "mCookie" );
mCookieField.setAccessible(true);
Field mFiledNameField = DexFile.getDeclaredField( "mFileName" );
mFiledNameField.setAccessible(true);
/ / 拿到getClassNameList
Method getClassNameListMethod = DexFile.getDeclaredMethod( "getClassNameList" , Object . class );
getClassNameListMethod.setAccessible(true);
for ( Object dexElement:dexElementObj){
Object dexfileObj = dexFileField.get(dexElement);
Object mCookiedobj = mCookieField.get(dexfileObj);
String mFileNameobj = (String) mFiledNameField.get(dexfileObj);
String[] classlist = (String[]) getClassNameListMethod.invoke(null,mCookiedobj);
for (String classname:classlist){
Log.e( "classlist" ,classLoader.toString() + "-----" + mFileNameobj + "-----" + classname);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
|
三、实验总结
花了一段时间,断断续续总算把这篇类加载器和动态加载的帖子写完了,从中学习到了很多,这里如果有什么错误,就请各位大佬指正了。
四、参考文献
1 2 3 4 5 6 | http: / / gityuan.com / 2017 / 03 / 19 / android - classloader /
https: / / www.jianshu.com / p / 7193600024e7
https: / / www.jianshu.com / p / ff489696ada2
https: / / www.jianshu.com / p / 363a4ad0489d
https: / / github.com / huanzhiyazi / articles / issues / 30
https: / / juejin.cn / post / 6844903940094427150
|
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-2-18 19:51
被随风而行aa编辑
,原因: