I. ART and Dalvik VIRTUAL machine
1. Java Virtual Machine and Dalvik Virtual Machine
Dalvik VIRTUAL machine is an early version of Android virtual machine. Each application corresponds to a separate Dalvik VIRTUAL machine. The advantage of this design is that the failure of one process’s VIRTUAL machine does not affect other processes. Java virtual machines execute class files, while Dalvik virtual machines execute dex files. Java VIRTUAL machine is based on stack, while Dalvik virtual machine is based on register.
Stack-based virtual machines:
When each thread runs, it will create a thread exclusive stack space. When the method is called, the stack frame representing the method will be pushed into and out of this stack, and the operation in the stack frame is carried out in the operand stack.
Register-based virtual machines:
The biggest difference between register-based virtual machines and stack-based virtual machines is that operations are no longer performed on the operand stack, but in a virtual register, which also exists on the stack. These registers are essentially arrays used to temporarily store instructions, data, and addresses. Register-based virtual machine instruction operations are significantly less, eliminating a lot of movement operations, but its instructions are also relatively complex;
2. ART and Dalvik
The most obvious difference is that Dalvik supports JIT(Just in Time) just-in-time compilation, which will compile some hot codes in advance or compile them into machine code. Dalvik compiles odex files in advance when installing the application. ART supports AOT(AOT) ahead of time compilation. During application installation, ART uses the dex2OAT tool of the device to compile the application, and the bytecode in DEX will be compiled into the native machine code. The disadvantage of this method is that apK installation is slow, because it requires one more step to compile the native machine code.
From Android7, cancel the installation and compilation into machine code operation, JIT operation in the execution process, and the information stored in the configuration file, in the device idle and charging state, will record the information in the configuration file for AOT compilation, to be used directly next time run;
Android ClassLoader
The function of classloader is simply to load a class file, which is used by the program to run. Each class file has a classLoader field inside to identify which classloader it is loaded by.
The main class loader inheritance structure used in Android is as follows:
BootClassLoader an internal class of the ClassLoader that inherits from the ClassLoader and is mainly used to load classes in the AndroidFramework. BaseDexClassLoader Android application class loader, our project used in addition to the system in the class generally use this loader to load, can be used to load the specified dex, as well as JAR, ZIP, APK dex files; DexCLassLoader is used to load the specified dex and dex files in JAR, ZIP, and APK. It is an additional dynamic class loader.Copy the code
The common parent of both PathClassLoader and DexCLassLoader is BaseDexClassLoader, but creating DexCLassLoader requires passing an optimizedDirectory parameter, which is used to determine the directory of odex. However, this parameter has also been deprecated in API26; When the PathClassLoader is created, this parameter is passed as null, indicating that the default path is /data/dalvik-cache. There is no difference between using PathClassLoader and DexClassLoader;
Analysis of parent delegation mechanism and class loading process
When we load a class, we call the loadClass method of the CLassLoader:
protected Class<? > loadClass(String name, boolean resolve) { // First, check if the class has already been loaded Class<? > c = findLoadedClass(name); if (c == null) { try { if (parent ! = null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } if (c == null) { c = findClass(name); } } return c; }Copy the code
2. In the loadClass method, the first check is to see if it has been loaded. If c is not empty, it returns directly. If the loadClass method that calls the parent first is not loaded, then the parent is recursively called (the parent was passed in as an argument when the parent was created). If the parent is empty, the BootClassLoader is called. If the load fails in all parent loaders, it calls its own findClass method to load it itself;
protected Class<? > findClass(String name) throws ClassNotFoundException { Class c = pathList.findClass(name, suppressedExceptions); return c; }Copy the code
FindClass in the ClassLoader class is an empty implementation, the actual call is BaseDexClassLoader findClass method, we can see in the method implementation is very simple, is called DexPathList findClass method:
public Class<? > findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { Class<? > clazz = element.findClass(name, definingContext, suppressed); if (clazz ! = null) { return clazz; } } return null; }Copy the code
4. In this method, we loop through dexElements and call Element’s findClass method. So what is dexElements? It is assigned in the DexPathList constructor:
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
}
Copy the code
5, splitDexPath –> splitPaths, will return a List;
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) { List<File> result = new ArrayList<>(); if (searchPath ! = null) { for (String path : searchPath.split(File.pathSeparator)) { if (directoriesOnly) { try { StructStat sb = Libcore.os.stat(path); if (! S_ISDIR(sb.st_mode)) { continue; } } catch (ErrnoException ignored) { continue; } } result.add(new File(path)); } } return result; }Copy the code
The makeDexElements method returns an array of Elements. You can see that DexFile is built from File, Element is built from DexFile, and Elements is returned:
private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader) { 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(); if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { DexFile 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 { DexFile dex = null; 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); } if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } } else { System.logW("ClassLoader referenced unknown path: " + file); } } if (elementsPos ! = elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements; }Copy the code
7. Now that we know what dexElements is, we go back to Step 3 and call Element’s findClass
public Class<? > findClass(String name, ClassLoader definingContext, List<Throwable> suppressed) { return dexFile ! = null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null; }Copy the code
8. Then call DexFile loadClassBinaryName:
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
Copy the code
DefineClass is then called:
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; }Copy the code
Call Native defineClassNative
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile) throws ClassNotFoundException, NoClassDefFoundError;
Copy the code
4. Hot repair strategy
Element Elements are stored in the Elements array. The Element Element is constructed using DexFile, which is just a wrapper around the dex file. Therefore, we can summarize an idea of thermal repair:
For example, if we have a bug in a class, we can fix it separately, package it into a dex file, create an Element from the dex file, and insert the new Element into the Elements header. We can fix this by reflection. Loading elements from the front to the back will load our modified classes, so that we don’t load the buggy classes behind, thus completing the fix.
V. Introduction to Dex file
1, apK file decompilation
Decompression of APK refers to decompression of APK compressed file, and then convert the dex file inside into JAR package, and then view the class file in the JAR package through visual tools;
2. Means of APK file reinforcement:
Anti-emulators, code virtualization, and most commonly encryption;
3. Encryption framework process:
First, take out the source DEX file in our APK file, and then encrypt the new source DEX file; Then create shell DEX file, repackage shell DEX file, encrypted source DEX file and other files in APK package into a new APK file, and then sign the file into an executable APK file.
4. Dex file structure
Dex file is the executable file of the Android system, which contains all the operation instructions and runtime data of the application. It combines the information existing in each class file and reduces the class redundancy compared with multiple class files.
Dex file format: Header, Dex file header, record the attributes of the Dex file. Such as magic number, signature, file_size, header_size, etc. Index area string_IDS String data index, which records the offset of each string in the data area type_IDS type data index, which records the string index of each type Proto_IDS type data index, Method_ids class method index, method_ids class index, method_ids class index, Data area For method declaration and method name Class_defs Class defines the data index, which records all kinds of information about the specified class, including interface, superclass, and class data offset. Data data area, save the real data of each class link_data connection data areaCopy the code
5. Apk packaging process
1) Compile resource files into R.Java files by AAPT tool, then convert AIDL files into Java interface files by AIDL tool, and finally compile R files, project source code and AIDL interface files into class files by Java compiler;
2) Compile the compiled class file and third-party library file into dex file through dex file;
3) Package the compiled dex file, resource file and other files such as SDK file into APK file;
4) Finally, use apkSigner tool to sign the packaged APK file;
6. Parent entrustment mechanism
When loading a class, the class loader first looks for whether the class has been loaded. If not, the loading task is handed over to the parent loader and recurses in turn. Returns if the parent loader can complete the loading task; If the parent loader cannot complete the loading task or there is no parent loader to load; Parental delegation has two main benefits: one is to avoid reloading classes; Second, to prevent the core API from being tampered with;
7, symmetric encryption and asymmetric encryption:
Symmetric encryption means that encryption and decryption use the same key; Typical encryption algorithms include DES, RC4, AES, etc.
Asymmetric encryption refers to encryption and decryption using different keys, respectively public key and private key. Public key confidential files can be decrypted only by the corresponding private key. The same; Only the corresponding public key can decrypt the files encrypted by the private key.
Incremental update
The key to incremental update is the word incremental. The traditional way to update an APP is to download a new APK and install it. In fact, each update only changes a small part of the content on the basis of the previous version, so we can download a subpackage representing the difference between the new version and the old version, and then use this subpackage to synthesize the new version of APK with the old version, so that we only need to download one subpackage each time, compared with downloading the whole APK. The differential subcontracting is generally much smaller than the entire APK, which is the incremental update process;
Two of the more important tools for incremental updates are BSdiff and bSPatch:
Bsdiff is used to compare new and old APK generation subcontracting;
Bspatch is used to synthesize new APK from old APK and poor subcontracting;
Bsdiff. c and bspatch.c can be used to generate bsdiff.exe and bspatch.exe executables, which can be used directly in the Windows CMD window.
DexDiff
DexDiff is a Dex differential algorithm designed by Tinker combined with Dex file format. According to the file format of Dex, each item of data in the two Dex is recorded differentially.