ClassLoader type
Java ClassLoader
can load jar files and Class files (essentially load Class files). This is not applicable in Android, because both DVM and ART are no longer Class files, but dex files.
The ClassLoader
types in Android are ClassLoader
similar to those in Java and are also divided into two types, namely 系统 ClassLoader
and 自定义 ClassLoader
. Among them, Android 系统 ClassLoader
includes three types, namely BootClassLoader
, PathClassLoader
and DexClassLoader
, and Java system class loader also includes three types, namely Bootstrap ClassLoader
, Extensions ClassLoader
and App ClassLoader
.
BootClassLoader
When the Android system starts, it will be used BootClassLoader
to pre-load common classes. Unlike in Java BootClassLoader
, it is not implemented by C/C++ code, but by Java. BootClassLoade
The code is as follows
// 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;
}
public BootClassLoader() {
super(null);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
...
}
BootClassLoader
Yes ClassLoader
inner class, and inherited from ClassLoader
. BootClassLoader
It is a singleton class. It should be noted that **BootClassLoader**
the access modifier is default and can only be accessed in the same package, so we cannot call it directly in the application .
PathClassLoader
The Android system uses PathClassLoader
to load system classes and application classes. If it is to load non-system application classes, data/app/$packagename
the dex file and the apk file or jar file containing dex will be loaded . No matter what kind of file is loaded, it will eventually be Load the dex file. In order to facilitate understanding, we will collectively refer to the dex file and the apk file or jar file containing dex as the dex related file. PathClassLoader is not recommended for development and direct use.
// 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
Inherited from BaseDexClassLoader
, the obvious PathClassLoader
method implementation is in BaseDexClassLoader
.
PathClassLoader
The construction method has three parameters:
- dexPath: The path collection of dex files and apk files or jar files containing dex. Multiple paths are separated by file separators. The default file separator is':'.
- librarySearchPath: A collection of paths containing C/C++ libraries, multiple paths are separated by file separators, which can be null
- parent:ClassLoader 的 parent
DexClassLoader
DexClassLoader
You can load dex files and apk files or jar files containing dex, and also support loading from an SD card, which means that DexClassLoader
dex related files can be loaded without the application being installed. Therefore, it is the basis of hot repair and plug-in technology.
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
DexClassLoader
The parameter of the construction method is more PathClassLoader
than one optimizedDirectory
parameter. optimizedDirectory
What does the parameter represent? When the application is loaded for the first time, in order to improve the startup speed and execution efficiency in the future, the Android system will optimize the dex-related files to a certain extent and generate a ODEX
file. Then when the application is run again, just load it The optimized ODEX
file will do, saving the time to optimize each time, and the parameter optimizedDirectory
represents ODEX
the path of the storage file, this path must be an internal storage path. PathClassLoader
No argument optimizedDirectory
, because PathClassLoader
already the default parameter optimizedDirectory
path is: /data/dalvik-cache
. DexClassLoader
Also inherited from BaseDexClassLoader
, the method implementation is also BaseDexClassLoader
in.
Regarding the above ClassLoader
creation process in the Android system, the process is involved here Zygote
, which is not the focus of this article, so I will not discuss it here.
ClassLoader inheritance relationship
ClassLoader
It is an abstract class in whichClassLoader
the main functions are defined .BootClassLoader
Is its internal class.SecureClassLoader
The code of the class andJDK8
theSecureClassLoader
class in is the same, it inherits the abstract classClassLoader
.SecureClassLoader
It is notClassLoader
the realization of the class, but the expandedClassLoader
class to join the function of permissions, and strengthen theClassLoader
security.URLClassLoader
The code of the class andJDK8
theURLClassLoader
class in is the same, it inherits from itSecureClassLoader
, and is used to load classes and resources from jar files and folders through the URL path.BaseDexClassLoader
Inherited fromClassLoader
, isClassLoader
the concrete implementation class of the abstract class,PathClassLoader
andDexClassLoader
both inherit it.
Let’s take a look at the several types of class loaders needed to run an Android program
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var classLoader = this.classLoader
// 打印 ClassLoader 继承关系
while (classLoader != null) {
Log.d("MainActivity", classLoader.toString())
classLoader = classLoader.parent
}
}
}
The MainActivity
class loader printed, and the printing of the current parent class loader loader, the loader until no parent, then the cycle terminates. The print result is as follows:
com.zhgqthomas.github.hotfixdemo D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk"],nativeLibraryDirectories=[/data/app/com.zhgqthomas.github.hotfixdemo-2/lib/arm64, /oem/lib64, /system/lib64, /vendor/lib64]]]
com.zhgqthomas.github.hotfixdemo D/MainActivity: java.lang.BootClassLoader@4d7e926
You can see that there are two kinds of class loaders, one is PathClassLoader
, the other is BootClassLoader
. DexPathList
There are many paths in it, including /data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk
the location where the sample application is installed on the phone.
Parent delegation model
The class loader uses the parental delegation mode to find a Class. The so-called parental delegation mode is to first determine whether the Class has been loaded. Entrusted to the top level BootstrapClassLoader
, if the Class is found, it will return directly, if it is not found, it will continue to look down one by one, and if it has not been found, it will finally be handed over to itself to search. This is the implementation logic in JDK, and there are differences in the logic processing of methods in Android .BootstrapClassLoader
ClassLoader
ClassLoader
findBootstrapClassOrNull
// ClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
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;
}
The above code is easy to understand. First, it will find out whether the loaded class has been loaded. If it is returned directly, otherwise it will be delegated to the parent loader to search, and the findBootstrapClassOrNull
method will be called until there is no parent loader .
Let's take a look at how it is implemented findBootstrapClassOrNull
in JDK
and Android
in respectively
// JDK ClassLoader.java
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
JDK
The Central findBootstrapClassOrNull
Committee will eventually be handed over BootstrapClassLoader
to find the Class
file. As mentioned above BootstrapClassLoader
, it findBootstrapClass
is implemented by C++, so it is a native method
// JDK ClassLoader.java private native Class<?> findBootstrapClass(String name);
findBootstrapClassOrNull
The implementation in Android JDK
is different
// Android
private Class<?> findBootstrapClassOrNull(String name)
{
return null;
}
Android
Because it does not need to be used, BootstrapClassLoader
this method returns directly null
It is the use of the class loader to find the parent delegation mode adopted by the Class, so the order in which the class loader loads the dex related files can be modified by reflection to achieve the purpose of hot repair
Class loading process
Through the above analysis, we can see
PathClassLoader
Can load dex files in Android systemDexClassLoader
You can loaddex/zip/apk/jar
files in any directory , but you must specify itoptimizedDirectory
.
These two classes known by the code just inherited BaseDexClassLoader
, the specific implementation is still a BaseDexClassLoader
complete come.
BaseDexClassLoader
// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public class BaseDexClassLoader extends ClassLoader {
...
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}
/**
* @hide
*/
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();
}
}
...
public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
// TODO We should support giving this a library search path maybe.
super(parent);
this.pathList = new DexPathList(this, dexFiles);
}
...
}
By BaseDexClassLoader
know constructor to initialize the most important thing is pathList
that is DexPathList
this class, which is mainly used to manage documents dex
// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions); // 查找逻辑交给 DexPathList
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;
}
BaseDexClassLoader
The most important thing is this findClass
method, which is used to load the corresponding file in the dex class
file. And in the end it is handed over to the DexPathList
class to handle the implementation findClass
DexPathList
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
final class DexPathList {
...
/** class definition context */
private final ClassLoader definingContext;
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
...
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
...
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
...
}
}
Looking DexPathList
at the code of the core constructor, we can see that the DexPathList
class Element
is stored dex 路径
through the makeDexElements
function , and the dex related file is loaded through the function, and the Element
collection is returned
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
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;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
DexFile dex = null;
if (name.endsWith(DEX_SUFFIX)) { // 判断是否是 dex 文件
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else { // 如果是 apk, jar, zip 等文件
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
// 将 dex 文件或压缩文件包装成 Element 对象,并添加到 Element 集合中
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;
}
Generally speaking, DexPathList
the constructor is to encapsulate dex related files (may be dex, apk, jar, zip, these types are defined at the beginning) into an Element
object, and finally add it to the Element
collection
In fact, Android's class loader either PathClassLoader, or DexClassLoader, they recognize only the last dex files, and loadDexFile
is the core method to load dex files can be extracted from dex jar, apk, zip in
// 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;
}
It DexPathList
has been initialized in the constructor dexElements
, so this method is easy to understand. It just traverses the Element array. Once a class with the same class name and name is found, this class is returned directly, and null is returned if it cannot be found.
Hot fix implementation
Through the above analysis, we can know that running a Android
program is used PathClassLoader
, that is BaseDexClassLoader
, the dex related files in the apk will be stored in the properties of BaseDexClassLoader
the pathList
object dexElements
.
Then the principle of hot fix is to put the dex related files of the corrected bug into
dexElements
the head of the collection, so that when traversing, it will first traverse the repaired dex and find the repaired class, because of the parent delegation mode of the class loader, in the old dex Classes with bugs have no chance to play. In this way, it is possible to fix the existing bug class without releasing a new version
Manually implement the hot repair function
According to the principle of hot repair above, the corresponding ideas can be summarized as follows
- Create a
BaseDexClassLoader
subclassDexClassLoader
loader - Load the repaired class.dex (the repair package downloaded by the server)
dexElements
Combine own and system , and set thedexElements
priority of freedom- Through reflection technology, assigned to the system
pathList