Android packaging related knowledge sorting (2)
This article mainly analyzes the principle of Android Multidex, through the Multidex to understand the Android class loader.
Multidex
Dalvik implements a. Dex file that is a combination of several. Class files. In this file, all methods in the dex package are indexed by method IDS and stored in a linked list whose length is stored as a short. The short type is two bytes, so the maximum value of a short should be 65536, so if we have too many methods in a package that the index length of the linked list exceeds 65546, we will have a problem. Google has fixed this in the new version, but in order to be compatible with older systems, we need to do something when the number of methods exceeds this limit. There are many solutions, one of which is to split the DEX, that is, if one DEX does not fit, divide several dex packages, so that there will be no problem. Google officially launched multidex, which can effectively subcontract dex. The principle of multidex is very simple. It is actively subcontracted during packaging, divided into a primary DEX package and multiple sub-dex packages, and then packaged together into APK. When the application starts, the primary dex package is loaded first as the entrance, and then the secondary dex package is loaded successively.
Configured to use
The configuration is simple and consists of two steps:
- Modify Gradle configuration to support Multidex.
android {
compileSdkVersion 27
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 27.// Enabling multidex support.
multiDexEnabled true}... }dependencies {
compile 'com. Android. Support: multidex: 1.0.3'
}
Copy the code
- Start MultiDex in your code.
Specifically, there are three ways. First, by specifying the use of MultidexApplication in the AndroidManifest file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.multidex.myapplication">
<application
.
android:name="android.support.multidex.MultiDexApplication">.</application>
</manifest>
Copy the code
Second, customize the Application and inherit the MultiDexApplication
public class DemoApp extends MultiDexApplication {
@Override
public void onCreate(a) {
super.onCreate();
// do nothing}}Copy the code
Third, customize the Application and then enable MultiDex in the attachBaseContext method
public class BaseApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(base);
}
@Override
public void onCreate(a) {
super.onCreate(); }}Copy the code
All three of these methods end up calling multidex.install ().
Dex package split
Dex unpacking steps:
-
The whole project code is scanned to obtain a main-dex-list that records the marked master and slave dex.
-
According to main-dex-list, the compiled. Class files of the project are separated into master and slave files.
-
Package the master and slave. Class files into the master and slave. Dex files.
The tool for generating mai-dex-list is a script file called mainDexClasses in the Build Tools of the Android Sdk. The core part of its first generates a jar package, and then as a parameter, along with all the documents called com. Android. Multidex. MainDexListBuilder:
java -cp "$jarpath" com.android.multidex.MainDexListBuilder ${disableKeepAnnotated} "${tmpOut}" The ${@} || exit 11
Copy the code
The main job of maindexListBuilder. Java is to add the parts of the main dex that comply with the Keep rule. Because the combined package after subcontracting must execute the main package first and then the sub-package, some necessary files must be installed in the main dex package. The keep rule here is to protect these files, so that when main-dex-list is generated, it will be put in the main DEX package by default.
Dex package merger
Initialize the
The MultiDex package will be installed one by one during the first installation run, according to the previous configuration of MultiDex.
MultiDex.install(this);
Copy the code
In the install:
public static void install(Context context) {
Log.i("MultiDex"."Installing application");
// Check whether the current system supports MultiDex. If so, do not merge MultiDex itself
if (IS_VM_MULTIDEX_CAPABLE) {
Log.i("MultiDex"."VM has multidex support, MultiDex support library is disabled.");
} else if (VERSION.SDK_INT < 4) {// The system version is too early and not supported
throw new RuntimeException("MultiDex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
} else {
try {
// Get a reference to the Application object
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
Log.i("MultiDex"."No ApplicationInfo available, i.e. running on a test Context: MultiDex support library is disabled.");
return;
}
// Specific installation process
doInstallation(context, new File(applicationInfo.sourceDir), new File(applicationInfo.dataDir), "secondary-dexes"."".true);
} catch (Exception var2) {
Log.e("MultiDex"."MultiDex installation failure", var2);
throw new RuntimeException("MultiDex installation failed (" + var2.getMessage() + ").");
}
Log.i("MultiDex"."install done"); }}Copy the code
So the installation is done in doInstallation(), and the main parameters passed in when this method is called are a context reference, a directory to hold the base APK, a data directory for the app, and a string secondary-dexes for the secondary dex directory.
The overall process
private static void doInstallation(Context mainContext, File sourceApk, File dataDir, String secondaryFolderName, String prefsKeyPrefix, boolean reinstallOnPatchRecoverableException) throws IOException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException {
// Here installedApk is a HashSet, should be to save the current and installed dex package
Set var6 = installedApk;
synchronized(installedApk) {
If souceApk does not exist, add it to the HashSet
if(! installedApk.contains(sourceApk)) { installedApk.add(sourceApk);// If the compiled version is larger than the currently supported maximum, it prompts
if (VERSION.SDK_INT > 20) {
Log.w("MultiDex"."MultiDex is not guaranteed to work in SDK version " + VERSION.SDK_INT + ": SDK version higher than " + 20 + " should be backed by " + "runtime with built-in multidex capabilty but it's not the " + "case here: java.vm.version=\"" + System.getProperty("java.vm.version") + "\" ");
}
// Get a reference to a ClassLoader object using the current Context
ClassLoader loader;
try {
loader = mainContext.getClassLoader();
} catch (RuntimeException var25) {
Log.w("MultiDex"."Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", var25);
return;
}
if (loader == null) {
Log.e("MultiDex"."Context class loader is null. Must be running in test mode. Skip patching.");
} else {
try {
// Delete the old directory
clearOldDexDir(mainContext);
} catch (Throwable var24) {
Log.w("MultiDex"."Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", var24);
}
// Create a dex directory
File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
// Create a MultiDexExtractor object
MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
IOException closeException = null;
try {
// Call the load() method of the MultiDexExtractor object to get a list of files that should be other files from the dex
List files = extractor.load(mainContext, prefsKeyPrefix, false);
try {
// Install these from dex packages
installSecondaryDexes(loader, dexDir, files);
} catch (IOException var26) {
if(! reinstallOnPatchRecoverableException) {throw var26;
}
// The installation failed and retry
Log.w("MultiDex"."Failed to install extracted secondary dex files, retrying with forced extraction", var26);
files = extractor.load(mainContext, prefsKeyPrefix, true); installSecondaryDexes(loader, dexDir, files); }}finally {
try {
extractor.close();
} catch(IOException var23) { closeException = var23; }}if(closeException ! =null) {
throw closeException;
}
}
}
}
}
Copy the code
First check to see if sourceApk is installed, then install it if not, then use the Context to get a ClasLoader, clear the old cache, get the slave dex, and then use the ClassLoader to load the slave dex. Then take a look at the important ones in turn. The first is clearOldDexDir () :
private static void clearOldDexDir(Context context) throws Exception {
// Get the file directory
File dexDir = new File(context.getFilesDir(), "secondary-dexes");
if (dexDir.isDirectory()) {
Log.i("MultiDex"."Clearing old secondary dex dir (" + dexDir.getPath() + ").");
// Get all the subfiles in the directory
File[] files = dexDir.listFiles();
if (files == null) {
Log.w("MultiDex"."Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
return;
}
File[] var3 = files;
int var4 = files.length;
// Iterate through all the subfiles, calling the delete() method in turn to delete them
for(int var5 = 0; var5 < var4; ++var5) {
File oldFile = var3[var5];
Log.i("MultiDex"."Trying to delete old file " + oldFile.getPath() + " of size " + oldFile.length());
if(! oldFile.delete()) { Log.w("MultiDex"."Failed to delete old file " + oldFile.getPath());
} else {
Log.i("MultiDex"."Deleted old file "+ oldFile.getPath()); }}if(! dexDir.delete()) { Log.w("MultiDex"."Failed to delete secondary dex dir " + dexDir.getPath());
} else {
Log.i("MultiDex"."Deleted old secondary dex dir "+ dexDir.getPath()); }}}Copy the code
So here is to clear the/data/data / / files/secondary – dexes the all files in the directory. And then the getDexDir () :
private static File getDexDir(Context context, File dataDir, String secondaryFolderName) throws IOException {
File cache = new File(dataDir, "code_cache");
try {// Create the code_cache directory
mkdirChecked(cache);
} catch (IOException var5) {
// If the creation fails, create the files/code_cache directory
cache = new File(context.getFilesDir(), "code_cache");
mkdirChecked(cache);
}
// Create a secondary-dexes directory under the files/code_cache directory you just created
File dexDir = new File(cache, secondaryFolderName);
mkdirChecked(dexDir);
return dexDir;
}
Copy the code
Create a/data/ data//code_cache directory. If this fails, create a/data/ data//files/code_cache directory. Then create a secondary directory under this directory. /data/data/ files/code_cache/secondary-dexes or /data/data//code_cache/secondary-dexes were created and returned. Clear is the directory that was created again when the creation failed. Look at creating an extractor.load() :
List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException {
Log.i("MultiDex"."MultiDexExtractor.load(" + this.sourceApk.getPath() + "," + forceReload + "," + prefsKeyPrefix + ")");
// Verify that the currently acquired file lock is valid
if (!this.cacheLock.isValid()) {
throw new IllegalStateException("MultiDexExtractor was closed");
} else {
List files;
// If not forcibly retrieved or not modified
if(! forceReload && ! isModified(context,this.sourceApk, this.sourceCrc, prefsKeyPrefix)) {
try {
// Extract preexisting files
files = this.loadExistingExtractions(context, prefsKeyPrefix);
} catch (IOException var6) {
Log.w("MultiDex"."Failed to reload existing extracted secondary dex files, falling back to fresh extraction", var6);
// If this fails, use the performExtractions() method
files = this.performExtractions();
// Store apK information.
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files); }}else {
// If it is mandatory or has already been modified
if (forceReload) {
Log.i("MultiDex"."Forced extraction must be performed.");
} else {
Log.i("MultiDex"."Detected that extraction must be performed.");
}
// Call performExtraction()
files = this.performExtractions();
putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
}
Log.i("MultiDex"."load found " + files.size() + " secondary dex files");
returnfiles; }}Copy the code
So the load() method first ensures that the file lock obtained by the current operation is valid, presumably to secure multi-process operations. It then decides how to extract the file based on the parameter forceReload passed in and whether it has been modified. If so, the Existing file, presumably a cache file, is fetched. Otherwise, it is retrieved through the performExtractions() method and saved through the putStoreApkInfo() method. Look at this. LoadExistingExtractions () method is in line with the speculation:
private List<MultiDexExtractor.ExtractedDex> loadExistingExtractions(Context context, String prefsKeyPrefix) throws IOException {
Log.i("MultiDex"."loading existing secondary dex files");
// Construct apkname.classes as a prefix
String extractedFilePrefix = this.sourceApk.getName() + ".classes";
// Get an SP object
SharedPreferences multiDexPreferences = getMultiDexPreferences(context);
// Get the total number of dex, i.e. TotalDexNumber
int totalDexNumber = multiDexPreferences.getInt(prefsKeyPrefix + "dex.number".1);
// The number of secondary dex files in a list is -1, i.e. the primary dex file is removed
List<MultiDexExtractor.ExtractedDex> files = new ArrayList(totalDexNumber - 1);
// Start with the second, i.e. skip the first primary dex
for(int secondaryNumber = 2; secondaryNumber <= totalDexNumber; ++secondaryNumber) {
// Use prefix + current number +.zip as the file name, for example xxx.classes2.zip
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
// Get this file
MultiDexExtractor.ExtractedDex extractedFile = new MultiDexExtractor.ExtractedDex(this.dexDir, fileName);
if(! extractedFile.isFile()) {// If it does not exist, an error will be reported
throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}
// There is some validation to be done
extractedFile.crc = getZipCrc(extractedFile);
long expectedCrc = multiDexPreferences.getLong(prefsKeyPrefix + "dex.crc." + secondaryNumber, -1L);
long expectedModTime = multiDexPreferences.getLong(prefsKeyPrefix + "dex.time." + secondaryNumber, -1L);
long lastModified = extractedFile.lastModified();
if(expectedModTime ! = lastModified || expectedCrc ! = extractedFile.crc) {// An error will be reported if the verification fails
throw new IOException("Invalid extracted dex: " + extractedFile + " (key \"" + prefsKeyPrefix + "\"), expected modification time: " + expectedModTime + ", modification time: " + lastModified + ", expected crc: " + expectedCrc + ", file crc: " + extractedFile.crc);
}
// Finally verify by adding this file to the list
files.add(extractedFile);
}
// Finally return the list of the built files from dex
return files;
}
Copy the code
First get an SP object, which holds data about the number of dex files. Then by traversing, get all the dex files, add them to a list clock and return. If not, an exception is thrown. In combination with the previous code, the exception needs to be reextracted via the performExtractions() method:
private List<MultiDexExtractor.ExtractedDex> performExtractions() throws IOException {
// Create a prefix for apkname.classes
String extractedFilePrefix = this.sourceApk.getName() + ".classes";
this.clearDexDir();
List<MultiDexExtractor.ExtractedDex> files = new ArrayList();
// Get a zipfile that is the original APK
ZipFile apk = new ZipFile(this.sourceApk);
try {
int secondaryNumber = 2;
// Get the dexFile file from classes+number+.dex
for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile ! =null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
// Create a filename apkName+.classes+number+.zip
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
// The dexDir is the cache directory XXX /secondary-dexs, which is used to obtain the dex file
MultiDexExtractor.ExtractedDex extractedFile = new MultiDexExtractor.ExtractedDex(this.dexDir, fileName);
files.add(extractedFile);
Log.i("MultiDex"."Extraction is needed for file " + extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
// Start extracting files, but only three chances are given
while(numAttempts < 3 && !isExtractionSuccessful) {
++numAttempts;
// The core method of extracting the dex file
extract(apk, dexFile, extractedFile, extractedFilePrefix);
try {
/ / for CRC
extractedFile.crc = getZipCrc(extractedFile);
isExtractionSuccessful = true;
} catch (IOException var18) {
isExtractionSuccessful = false;
Log.w("MultiDex"."Failed to read crc from " + extractedFile.getAbsolutePath(), var18);
}
Log.i("MultiDex"."Extraction " + (isExtractionSuccessful ? "succeeded" : "failed") + "'" + extractedFile.getAbsolutePath() + "': length " + extractedFile.length() + " - crc: " + extractedFile.crc);
if(! isExtractionSuccessful) { extractedFile.delete();if (extractedFile.exists()) {
Log.w("MultiDex"."Failed to delete corrupted secondary dex '" + extractedFile.getPath() + "'"); }}}if(! isExtractionSuccessful) {throw new IOException("Could not create zip file " + extractedFile.getAbsolutePath() + " for secondary dex (" + secondaryNumber + ")"); } ++secondaryNumber; }}finally {
try {
apk.close();
} catch (IOException var17) {
Log.w("MultiDex"."Failed to close resource", var17); }}return files;
}
Copy the code
As can be seen, starting from 2, a dex file is constructed continuously, and then the file is extracted into dexDir. The core method of extraction is extract():
private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) throws IOException, FileNotFoundException {
// The dexFile is the source dex file
InputStream in = apk.getInputStream(dexFile);
ZipOutputStream out = null;
// Use a TMP file as a swap file
File tmp = File.createTempFile("tmp-" + extractedFilePrefix, ".zip", extractTo.getParentFile());
Log.i("MultiDex"."Extracting " + tmp.getPath());
try {
// Compress the file stream
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
try {
ZipEntry classesDex = new ZipEntry("classes.dex");
classesDex.setTime(dexFile.getTime());
out.putNextEntry(classesDex);
byte[] buffer = new byte[16384];
// The process of writing files
for(intlength = in.read(buffer); length ! = -1; length = in.read(buffer)) {
out.write(buffer, 0, length);
}
out.closeEntry();
} finally {
out.close();
}
if(! tmp.setReadOnly()) {throw new IOException("Failed to mark readonly \"" + tmp.getAbsolutePath() + "\" (tmp of \"" + extractTo.getAbsolutePath() + "\")");
}
Log.i("MultiDex"."Renaming to " + extractTo.getPath());
// Convert the switch file to the target file, the corresponding file in the secondary-dexs directory
if(! tmp.renameTo(extractTo)) {throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + extractTo.getAbsolutePath() + "\" "); }}finally{ closeQuietly(in); tmp.delete(); }}Copy the code
To sum up, the load() method is used to write the source dex files one by one to the corresponding files in the new secondary-dexs directory. This process will decompress the source APK file and only start from the second dex file, because the first is the primary dex file. The main dex is already installed.
Install Specific process
Now that all the sub-dex packages have been extracted, it’s time to install, using the previous installSecondaryDexes() method:
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<? extends File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException, SecurityException, ClassNotFoundException, InstantiationException {
if(! files.isEmpty()) {if (VERSION.SDK_INT >= 19) {
MultiDex.V19.install(loader, files, dexDir);
} else if (VERSION.SDK_INT >= 14) {
MultiDex.V14.install(loader, files);
} else{ MultiDex.V4.install(loader, files); }}}Copy the code
The method for installing the DEX package varies according to the SDK version. For example, SDK >=19, his install() method is as follows:
static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
// Get the pathList field of the ClassLoader by reflection
Field pathListField = MultiDex.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList();
// Call makeDexElements to change the Dex files into Element objects
// Then call the expandFieldArray method and add the elements to the dexElements field of the dexPathList (which is an array).
MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
// Add some IO exception information
if (suppressedExceptions.size() > 0) {
Iterator var6 = suppressedExceptions.iterator();
while(var6.hasNext()) {
IOException e = (IOException)var6.next();
Log.w("MultiDex"."Exception in makeDexElement", e);
}
Field suppressedExceptionsField = MultiDex.findField(dexPathList, "dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions = (IOException[])((IOException[])suppressedExceptionsField.get(dexPathList));
if (dexElementsSuppressedExceptions == null) {
dexElementsSuppressedExceptions = (IOException[])suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
IOException[] combined = new IOException[suppressedExceptions.size() + dexElementsSuppressedExceptions.length];
suppressedExceptions.toArray(combined);
System.arraycopy(dexElementsSuppressedExceptions, 0, combined, suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
dexElementsSuppressedExceptions = combined;
}
suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
IOException exception = new IOException("I/O exception during makeDexElement");
exception.initCause((Throwable)suppressedExceptions.get(0));
throwexception; }}Copy the code
In Install, we get a Field called PathList from the ClassLoader, use the makeElements() method to generate an array of elements, and then call expandFieldArray(). An array is literally extended. Finally, check to see if any exceptions need to be thrown.
In Android, there are two types of classloaders, DexClassLoader and PathClassLoader, both of which are inherited from BaseClassLoader. The pathList is defined in the BaseClassLoader:
/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
}
Copy the code
Let’s see what this DexPathList is:
/**
* A pair of lists of entries, associated with a {@code ClassLoader}.
* One of the lists is a dex/resource path — typically referred
* to as a "class path" — list, and the other names directories
* containing native code libraries. Class path entries may be any of:
* a {@code .jar} or {@code .zip} file containing an optional
* top-level {@code classes.dex} file as well as arbitrary resources,
* or a plain {@code .dex} file (with no possibility of associated
* resources).
*
* <p>This class also contains methods to use these lists to look up
* classes and resources.</p>
*/
/*package*/ final class DexPathList {}Copy the code
So it essentially encapsulates a list of dex paths. Go back to the Install method and follow the steps. The first is makeElements () :
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makeDexElements}.
*/
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method makeDexElements =
findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
ArrayList.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
suppressedExceptions);
}
Copy the code
The makeDexElements() method of DexPathList is called by reflection as well, so take a look:
/** * Makes an array of dex/resource path elements, one per element of * the given array. */
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions,
ClassLoader loader) {
return makeElements(files, optimizedDirectory, suppressedExceptions, false, loader);
}
Copy the code
Create an array of dex/ resources:
private static Element[] makeElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions,
boolean ignoreDexFiles,
ClassLoader loader) {
// Create an Element array
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/* * Open all files and load the (direct or contained) dex files * up front. */
// Open all the files
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
// The zipSeparator value is! /
if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) {
// We support directories for looking up resources and native libraries.
// Looking up resources in directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file, true.null.null);
} else if (file.isFile()) {
// Where ignoreDexFiles is false, DEX_SUFFIX is.dex
if(! ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {// Raw dex file (not inside a zip/jar).
try {
// Find the dex file and try to load it
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: "+ file, suppressed); suppressedExceptions.add(suppressed); }}else {
zip = file;
if(! ignoreDexFiles) {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); }}}}else {
System.logW("ClassLoader referenced unknown path: " + file);
}
// Encapsulate the zip or dex as an Element object, and place the Element object in the previous Element array
if((zip ! =null) || (dex ! =null)) {
elements[elementsPos++] = new Element(dir, false, zip, dex); }}if(elementsPos ! = elements.length) { elements = Arrays.copyOf(elements, elementsPos); }return elements;
}
Copy the code
Then the expandFieldArray() method:
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
private static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(
original.getClass().getComponentType(), original.length + extraElements.length);
System.arraycopy(original, 0, combined, 0, original.length);
System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
jlrField.set(instance, combined);
}
Copy the code
Elemets is added to a new array containing elements. Here, too, is reflection, and what actually changes is the DexPathList array:
/** * 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;
Copy the code
To summarize, the installation process is to wrap the Dex packages as Element objects, and then insert these Element objects into the original Element array as new arrays, all by reflection, and the specific implementation is DexPathList
conclusion
The above are some utility methods, the main purpose is to use reflection, get objects, call the specified method. This concludes the DEX merge and summarizes the steps of the merge:
- Check whether the current system can automatically merge packages and if not, whether the system version supports Multidex.
- Clear the old standby cache directory, called standby because the standby directory is created when the creation fails and needs to be deleted.
- Create a new cache directory and extract the dex files into the cache directory.
- The installation cache directory interrupts the dex file. There are different installation methods depending on the current system version, but the logic is generally the same.