1、Multidex的产生

在android5.0之前,每一个android应用中只会含有一个dex文件,但是因为Android系统本身的BUG,使得这个dex的方法数量被限制在65535之内,这就是著名的"64K(64*1024)"事件。为了解决这个问题,Google官方推出了这个类似于补丁一样的support-library。关于这个库的详细使用,可以参考官方文档,当然使用起来也会有些坑的,美团填坑记或者这位老兄。使用这个库后,我们的APP不再只会仅有一个dex文件,可能会产生多个dex文件,这样就避免了64K问题。

2、使用方式

对于Multidex的使用,大致有以下几种方式
直接继承MultiDexApplication

public class MyApplication extends MultiDexApplication{// ...........
}

直接调用MultiDex.install(Context);

public class MyApplication extends Application{public void onCreate(){MultiDex.install(this);}
}

这两种使用方式,其实本质是一样的,都是通过MultiDex.install(this)来完成dex的加载,看看MultiDexApplication的实现:

public class MultiDexApplication extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);MultiDex.install(this);}
} 

3、android中的类加载机制

在分析MultiDex.install(Context)之前,先了解一下android中的类是如何加载的。在android中,类的加载可以分为DexClassLoader.和PathClassLoader,这里先看看他们各自的实现:
DexClassLoader

/*** A class loader that loads classes from {@code .jar} and {@code .apk} files* containing a {@code classes.dex} entry. This can be used to execute code not* installed as part of an application.* 从包含dex文件的jar或是apk中加载classes。该ClassLoader可以用来加载外部的classes,* 也就是可以加载没有预先安装的含有dex文件的jar或是apk。** <p>This class loader requires an application-private, writable directory to* cache optimized classes. Use {@code Context.getDir(String, int)} to create* such a directory: <pre>   {@code*   File dexOutputDir = context.getDir("dex", 0);* }</pre>** <p><strong>Do not cache optimized classes on external storage.</strong>* External storage does not provide access controls necessary to protect your* application from code injection attacks.*/
public class DexClassLoader extends BaseDexClassLoader {/*** Creates a {@code DexClassLoader} that finds interpreted and native* code.  Interpreted classes are found in a set of DEX files contained* in Jar or APK files.** <p>The path lists are separated using the character specified by the* {@code path.separator} system property, which defaults to {@code :}.** @param dexPath the list of jar/apk files containing classes and*     resources, delimited by {@code File.pathSeparator}, which*     defaults to {@code ":"} on Android*        含有classes和resources的文件(jar/apk)路径,多个文件以 File.pathSeparator(Linux为":"Windows为";")分割开来,比如:xx/xx/aa.apk:yy/yy/bb.apk* @param optimizedDirectory directory where optimized dex files*     should be written; must not be {@code null}*        dex文件所在的根路径,不能为null* @param libraryPath the list of directories containing native*     libraries, delimited by {@code File.pathSeparator}; may be*     {@code null}*        SO文件所在的路径* @param parent the parent class loader*/public DexClassLoader(String dexPath, String optimizedDirectory,String libraryPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), libraryPath, parent);}
}

PathClassLoader

/*** Provides a simple {@link ClassLoader} implementation that operates on a list* of files and directories in the local file system, but does not attempt to* load classes from the network. Android uses this class for its system class* loader and for its application class loader(s).* 不能加载从网络上获取的classes,Android用这个类来加载系统classes和已经安装的应用的classes*/
public class PathClassLoader extends BaseDexClassLoader {/*** Creates a {@code PathClassLoader} that operates on a given list of files* and directories. This method is equivalent to calling* {@link #PathClassLoader(String, String, ClassLoader)} with a* {@code null} value for the second argument (see description there).** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param parent the parent class loader*/public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}/*** Creates a {@code PathClassLoader} that operates on two given* lists of files and directories. The entries of the first list* should be one of the following:** <ul>* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as* well as arbitrary resources.* <li>Raw ".dex" files (not inside a zip file).* </ul>* dexPath只支持jar/zip/apk/dex四种文件** The entries of the second list should be directories containing* native library files.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param libraryPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public PathClassLoader(String dexPath, String libraryPath,ClassLoader parent) {super(dexPath, null, libraryPath, parent);}
}

从类的说明以及构造函数可以明显的知道DexClassLoader和PathClassLoader区别:DexClassLoader可以加载未安装的含有dex文件的jar或是apk,而PathClassLoader只能加载已安装好的含有dex文件的jar/apk/zip/dex。

BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {/** structured lists of path elements */private final DexPathList pathList;public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {super(parent);this.originalPath = dexPath;this.pathList =new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}
}

DexPathList

/*** A pair of lists of entries, associated with a {@code ClassLoader}.* One of the lists is a dex/resource path &mdash; typically referred* to as a "class path" &mdash; 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 {private static final String DEX_SUFFIX = ".dex";private static final String JAR_SUFFIX = ".jar";private static final String ZIP_SUFFIX = ".zip";private static final String APK_SUFFIX = ".apk";/** class definition context */private final ClassLoader definingContext;/** list of dex/resource (class path) elements */private final Element[] dexElements;/** list of native library directory elements */private final File[] nativeLibraryDirectories;public DexPathList(ClassLoader definingContext, String dexPath,String libraryPath, File optimizedDirectory) {if (definingContext == null) {throw new NullPointerException("definingContext == null");}if (dexPath == null) {throw new NullPointerException("dexPath == null");}if (optimizedDirectory != null) {// 对于PathClassloader来说optimizedDirectory是恒等于null的if (!optimizedDirectory.exists())  {throw new IllegalArgumentException("optimizedDirectory doesn't exist: "+ optimizedDirectory);}if (!(optimizedDirectory.canRead()&& optimizedDirectory.canWrite())) {throw new IllegalArgumentException("optimizedDirectory not readable/writable: "+ optimizedDirectory);}}// classLoaderthis.definingContext = definingContext;// 把每个dex文件的相关信息存储到Element这个对象中this.dexElements =makeDexElements(splitDexPath(dexPath), optimizedDirectory);// 本地so文件路径this.nativeLibraryDirectories = splitLibraryPath(libraryPath);}//......
}

首先是通过splitDexPath()得到含有dex文件的jar/zip/dex/apk集合:

/*** Splits the given dex path string into elements using the path* separator, pruning out any elements that do not refer to existing* and readable files. (That is, directories are not included in the* result.)*/private static ArrayList<File> splitDexPath(String path) {return splitPaths(path, null, false);}/*** Splits the given path strings into file elements using the path* separator, combining the results and filtering out elements* that don't exist, aren't readable, or aren't either a regular* file or a directory (as specified). Either string may be empty* or {@code null}, in which case it is ignored. If both strings* are empty or {@code null}, or all elements get pruned out, then* this returns a zero-element list.*/private static ArrayList<File> splitPaths(String path1, String path2,boolean wantDirectories) {ArrayList<File> result = new ArrayList<File>();splitAndAdd(path1, wantDirectories, result);splitAndAdd(path2, wantDirectories, result);return result;}/*** Helper for {@link #splitPaths}, which does the actual splitting* and filtering and adding to a result.*/private static void splitAndAdd(String path, boolean wantDirectories,ArrayList<File> resultList) {if (path == null) {return;}// 路径分离String[] strings = path.split(Pattern.quote(File.pathSeparator));for (String s : strings) {File file = new File(s);if (! (file.exists() && file.canRead())) {continue;}/** Note: There are other entities in filesystems than* regular files and directories.*/if (wantDirectories) {if (!file.isDirectory()) {continue;}} else {if (!file.isFile()) {continue;}}resultList.add(file);}}

然后通过makeDexElements()来把dex信息保存到Element对象中:

/*** Makes an array of dex/resource path elements, one per element of* the given array.*/private static Element[] makeDexElements(ArrayList<File> files,File optimizedDirectory) {ArrayList<Element> elements = new ArrayList<Element>();/** Open all files and load the (direct or contained) dex files* up front.*/for (File file : files) {ZipFile zip = null;DexFile dex = null;String name = file.getName();if (name.endsWith(DEX_SUFFIX)) {// 直接是.dex文件// Raw dex file (not inside a zip/jar).try {dex = loadDexFile(file, optimizedDirectory);} catch (IOException ex) {System.logE("Unable to load dex file: " + file, ex);}} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)|| name.endsWith(ZIP_SUFFIX)) {// 其他三种格式文件:都是压缩包啊try {zip = new ZipFile(file);} catch (IOException ex) {/** Note: ZipException (a subclass of IOException)* might get thrown by the ZipFile constructor* (e.g. if the file isn't actually a zip/jar* file).*/System.logE("Unable to open zip file: " + file, ex);}try {dex = loadDexFile(file, optimizedDirectory);} catch (IOException ignored) {/** 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). Safe to just ignore* the exception here, and let dex == null.*/}} else {System.logW("Unknown file type for: " + file);}// 格式符合要求,放到数组中if ((zip != null) || (dex != null)) {// 依靠DexFile创建Element对象elements.add(new Element(file, zip, dex));}}return elements.toArray(new Element[elements.size()]);}

/*** Constructs a {@code DexFile} instance, as appropriate depending* on whether {@code optimizedDirectory} is {@code null}.* .dex文件转成DexFile对象*/private static DexFile loadDexFile(File file, File optimizedDirectory)throws IOException {if (optimizedDirectory == null) {return new DexFile(file);} else {String optimizedPath = optimizedPathFor(file, optimizedDirectory);return DexFile.loadDex(file.getPath(), optimizedPath, 0);}}/*** Converts a dex/jar file path and an output directory to an* output file path for an associated optimized dex file.* 主要是对非.dex结尾的文件加上.dex后缀,统一为.dex后缀* 没有使用.odex为后缀,是因为构建系统会把.odex文件认为是只含有资源的文件*/private static String optimizedPathFor(File path,File optimizedDirectory) {/** Get the filename component of the path, and replace the* suffix with ".dex" if that's not already the suffix.** We don't want to use ".odex", because the build system uses* that for files that are paired with resource-only jar* files. If the VM can assume that there's no classes.dex in* the matching jar, it doesn't need to open the jar to check* for updated dependencies, providing a slight performance* boost at startup. The use of ".dex" here matches the use on* files in /data/dalvik-cache.*/String fileName = path.getName();if (!fileName.endsWith(DEX_SUFFIX)) {int lastDot = fileName.lastIndexOf(".");if (lastDot < 0) {fileName += DEX_SUFFIX;} else {StringBuilder sb = new StringBuilder(lastDot + 4);sb.append(fileName, 0, lastDot);sb.append(DEX_SUFFIX);fileName = sb.toString();}}File result = new File(optimizedDirectory, fileName);return result.getPath();}

以上,是android中类加载过程,从中可以发现,android中的classloader能加载的文件格式只能为zip/apk/dex/jar,加载目标是把dex文件转化成一个Element对象,以供后续查找使用。

4、MultiDex.install(Context)

/*** Patches the application context class loader by appending extra dex files* loaded from the application apk. This method should be called in the* attachBaseContext of your {@link Application}, see* {@link MultiDexApplication} for more explanation and an example.** @param context application context.* @throws RuntimeException if an error occurred preventing the classloader*         extension.*/public static void install(Context context) {Log.i(TAG, "install");if (IS_VM_MULTIDEX_CAPABLE) {Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");return;}if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {// 最低版本是4throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT+ " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");}try {// 获取到应用的相关信息ApplicationInfo applicationInfo = getApplicationInfo(context);if (applicationInfo == null) {// Looks like running on a test Context, so just return without patching.return;}synchronized (installedApk) {// dataDir = /data/data/com.example.android.animationsdemo// apkPath = sourceDir = /data/app/com.example.android.animationsdemo-1.apkString apkPath = applicationInfo.sourceDir;if (installedApk.contains(apkPath)) {return;}installedApk.add(apkPath);if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {Log.w(TAG, "MultiDex is not guaranteed to work in SDK version "+ Build.VERSION.SDK_INT + ": SDK version higher than "+ MAX_SUPPORTED_SDK_VERSION + " 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") + "\"");}/* The patched class loader is expected to be a descendant of* dalvik.system.BaseDexClassLoader. We modify its* dalvik.system.DexPathList pathList field to append additional DEX* file entries.*/ClassLoader loader;try {//dalvik.system.PathClassLoaderloader = context.getClassLoader();} catch (RuntimeException e) {/* Ignore those exceptions so that we don't break tests relying on Context like* a android.test.mock.MockContext or a android.content.ContextWrapper with a* null base Context.*/Log.w(TAG, "Failure while trying to obtain Context class loader. " +"Must be running in test mode. Skip patching.", e);return;}if (loader == null) {// Note, the context class loader is null when running Robolectric tests.
                    Log.e(TAG,"Context class loader is null. Must be running in test mode. "+ "Skip patching.");return;}try {// 清空放置dex文件的文件夹
                  clearOldDexDir(context);} catch (Throwable t) {Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, "+ "continuing without cleaning.", t);}// 创建放置dex文件的文件夹
// dexDir = /data/data/com.example.android.animationsdemo/code_cache/secondary-dexesFile dexDir = getDexDir(context, applicationInfo);// 得到APK中所有dex对应的zip文件List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);//
                if (checkValidZipFiles(files)) {// 加载dex到
                    installSecondaryDexes(loader, dexDir, files);} else {// 强制再转换一次Log.w(TAG, "Files were not valid zip files.  Forcing a reload.");// Try again, but this time force a reload of the zip file.files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);if (checkValidZipFiles(files)) {installSecondaryDexes(loader, dexDir, files);} else {// Second time didn't work, give upthrow new RuntimeException("Zip files were not valid.");}}}} catch (Exception e) {Log.e(TAG, "Multidex installation failure", e);throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");}Log.i(TAG, "install done");}

整个加载过程主要分为3步骤:clearOldDexDir():清理过期数据MultiDexExtractor.load:把dex转换成对应的zip文件installSecondaryDexes:dex转换成对应的Element对象清理过期文件 :删除/data/data/(pkg)/files/secondary-dexes路径下的所有文件和该文件夹作者:紫苓链接:http://www.jianshu.com/p/dd90d7e7c691來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

private static void clearOldDexDir(Context context) throws Exception {// dexDir = /data/data/com.example.android.animationsdemo/files/secondary-dexesFile dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME);if (dexDir.isDirectory()) {Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ").");File[] files = dexDir.listFiles();if (files == null) {Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");return;}for (File oldFile : files) {Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size "+ oldFile.length());if (!oldFile.delete()) {Log.w(TAG, "Failed to delete old file " + oldFile.getPath());} else {Log.i(TAG, "Deleted old file " + oldFile.getPath());}}if (!dexDir.delete()) {Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath());} else {Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath());}}}

把dex文件转换成对应的zip文件 :classesN.dex-->/data/data/(包名)/code_cache/secondary-dexes/(包名)-1.apk.classesN.zip

static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,boolean forceReload) throws IOException {Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");// sourceApk = /data/app/com.example.android.animationsdemo-1.apkfinal File sourceApk = new File(applicationInfo.sourceDir);long currentCrc = getZipCrc(sourceApk);// Validity check and extraction must be done only while the lock file has been taken.File lockFile = new File(dexDir, LOCK_FILENAME);RandomAccessFile lockRaf = new RandomAccessFile(lockFile, "rw");FileChannel lockChannel = null;FileLock cacheLock = null;List<File> files;IOException releaseLockException = null;try {lockChannel = lockRaf.getChannel();Log.i(TAG, "Blocking on lock " + lockFile.getPath());cacheLock = lockChannel.lock();Log.i(TAG, lockFile.getPath() + " locked");if (!forceReload && !isModified(context, sourceApk, currentCrc)) {// APK信息没有发生变化try {// dex对应的zip文件已经存在files = loadExistingExtractions(context, sourceApk, dexDir);} catch (IOException ioe) {Log.w(TAG, "Failed to reload existing extracted secondary dex files,"+ " falling back to fresh extraction", ioe);files = performExtractions(sourceApk, dexDir);putStoredApkInfo(context,getTimeStamp(sourceApk), currentCrc, files.size() + 1);}} else {// second dex 发生了改变Log.i(TAG, "Detected that extraction must be performed.");files = performExtractions(sourceApk, dexDir);// 保存APK相关信息:时间戳、总dex数等putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);}} finally {if (cacheLock != null) {try {cacheLock.release();} catch (IOException e) {Log.e(TAG, "Failed to release lock on " + lockFile.getPath());// Exception while releasing the lock is bad, we want to report it, but not at// the price of overriding any already pending exception.releaseLockException = e;}}if (lockChannel != null) {closeQuietly(lockChannel);}closeQuietly(lockRaf);}if (releaseLockException != null) {throw releaseLockException;}Log.i(TAG, "load found " + files.size() + " secondary dex files");return files;}/*** 把APK文件中的classesN.dex(N >= 2)转换成对应的zip文件,保存到集合中* zip文件:/data/data/(包名)/code_cache/secondary-dexes/(包名)-1.apk.classesN.zip*/private static List<File> performExtractions(File sourceApk, File dexDir)throws IOException {// extractedFilePrefix = com.example.android.animationsdemo-1.apk.classesfinal String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;// Ensure that whatever deletions happen in prepareDexDir only happen if the zip that// contains a secondary dex file in there is not consistent with the latest apk.  Otherwise,// multi-process race conditions can cause a crash loop where one process deletes the zip// while another had created it.
        prepareDexDir(dexDir, extractedFilePrefix);List<File> files = new ArrayList<File>();final ZipFile apk = new ZipFile(sourceApk);try {int secondaryNumber = 2;// dexFile = classes2.dexZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);while (dexFile != null) {// fileName = com.example.android.animationsdemo-1.apk.classes2.zipString fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;// extractedFile = /data/data/com.example.android.animationsdemo/code_cache/secondary-dexes/com.example.android.animationsdemo-1.apk.classes2.zipFile extractedFile = new File(dexDir, fileName);files.add(extractedFile);Log.i(TAG, "Extraction is needed for file " + extractedFile);int numAttempts = 0;boolean isExtractionSuccessful = false;while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) {// 最多尝试3次来把classesN.dex转成对应的zip文件numAttempts++;// Create a zip file (extractedFile) containing only the secondary dex file// (dexFile) from the apk.// 把classesN.dex文件转化为zip文件
                    extract(apk, dexFile, extractedFile, extractedFilePrefix);// Verify that the extracted file is indeed a zip file.isExtractionSuccessful = verifyZipFile(extractedFile);// Log the sha1 of the extracted zip fileLog.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +" - length " + extractedFile.getAbsolutePath() + ": " +extractedFile.length());if (!isExtractionSuccessful) {// Delete the extracted file
                        extractedFile.delete();if (extractedFile.exists()) {Log.w(TAG, "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++;dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);}} finally {try {apk.close();} catch (IOException e) {Log.w(TAG, "Failed to close resource", e);}}return files;}/*** apk = /data/app/com.example.android.animationsdemo-1.apk* dexFile = classesN.dex* extractTo = /data/data/com.example.android.animationsdemo/code_cache/secondary-dexes/com.example.android.animationsdemo-1.apk.classesN.zip* extractedFilePrefix = com.example.android.animationsdemo-1.apk.classes* 把classesN.dex文件转化为zip文件*/private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,String extractedFilePrefix) throws IOException, FileNotFoundException {InputStream in = apk.getInputStream(dexFile);ZipOutputStream out = null;// tmp = /data/data/com.example.android.animationsdemo/code_cache/secondary-dexes/com.example.android.animationsdemo-1.apk.classes.zipFile tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX,extractTo.getParentFile());Log.i(TAG, "Extracting " + tmp.getPath());try {// 把classesN.dex写到tmp这个zip文件中,然后指定这个zip文件的下一个ZipEntry为classes.dex// 最后把zip文件重命名为extractToout = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));try {ZipEntry classesDex = new ZipEntry("classes.dex");// keep zip entry time since it is the criteria used by Dalvik
                classesDex.setTime(dexFile.getTime());out.putNextEntry(classesDex);byte[] buffer = new byte[BUFFER_SIZE];int length = in.read(buffer);while (length != -1) {out.write(buffer, 0, length);length = in.read(buffer);}out.closeEntry();} finally {out.close();}Log.i(TAG, "Renaming to " + extractTo.getPath());if (!tmp.renameTo(extractTo)) {throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() +"\" to \"" + extractTo.getAbsolutePath() + "\"");}} finally {closeQuietly(in);tmp.delete(); // return status ignored
        }}

加载dex信息到内存中

private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,InvocationTargetException, NoSuchMethodException, IOException {if (!files.isEmpty()) {if (Build.VERSION.SDK_INT >= 19) {V19.install(loader, files, dexDir);} else if (Build.VERSION.SDK_INT >= 14) {V14.install(loader, files, dexDir);} else {V4.install(loader, files);}}}

/*** Installer for platform versions 19.*/private static final class V19 {private static void install(ClassLoader loader, List<File> additionalClassPathEntries,File optimizedDirectory)throws IllegalArgumentException, IllegalAccessException,NoSuchFieldException, InvocationTargetException, NoSuchMethodException {/* The patched class loader is expected to be a descendant of* dalvik.system.BaseDexClassLoader. We modify its* dalvik.system.DexPathList pathList field to append additional DEX* file entries.*/// 通过反射的方式获取PathClassLoader中的pathList属性Field pathListField = findField(loader, "pathList");Object dexPathList = pathListField.get(loader);// 通过反射的方式重新给DexPathList中的dexElements重新赋值ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,suppressedExceptions));if (suppressedExceptions.size() > 0) {for (IOException e : suppressedExceptions) {Log.w(TAG, "Exception in makeDexElement", e);}Field suppressedExceptionsField =findField(dexPathList, "dexElementsSuppressedExceptions");IOException[] dexElementsSuppressedExceptions =(IOException[]) suppressedExceptionsField.get(dexPathList);if (dexElementsSuppressedExceptions == null) {dexElementsSuppressedExceptions =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);}}/*** 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 {// 通过反射的方式调用dalvik.system.DexPathList.makeDexElements(),得到classesN.dex对应的ElementMethod makeDexElements =findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,ArrayList.class);return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,suppressedExceptions);}}

private static void expandFieldArray(Object instance, String fieldName,Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,IllegalAccessException {// 找到DexPathList类中的dexElements属性,它是Element[]Field jlrField = findField(instance, fieldName);// 目前内存具有的Elements,应该只有classes.dex对应的ElementObject[] original = (Object[]) jlrField.get(instance);// classesN.dex对应的ElementObject[] 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);// 给instance对象中的属性jlrField重新赋值为combinedjlrField.set(instance, combined);}

Multidex实现简要分析相关推荐

  1. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析...

    构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析 原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入 ...

  2. [Java] HashMap 源码简要分析

    特性 * 允许null作为key/value. * 不保证按照插入的顺序输出.使用hash构造的映射一般来讲是无序的. * 非线程安全. * 内部原理与Hashtable类似. 源码简要分析 publ ...

  3. Android Hal层简要分析

    Android Hal层简要分析 Android Hal层(即 Hardware Abstraction Layer)是Google开发的Android系统里上层应用对底层硬件操作屏蔽的一个软件层次, ...

  4. 【安全漏洞】简要分析复现了最近的ProxyShell利用链

    前言 近日,有研究员公布了自己针对微软的Exchange服务的攻击链的3种利用方式.微软官方虽然出了补丁,但是出于种种原因还是有较多用户不予理会,导致现在仍然有许多有漏洞的服务暴露在公网中,本文主要在 ...

  5. Android L Settings 简要分析

    1.本文说明 本文主要针对L平台上Settings模块正常启动流程做一个简要分析,并试着分析一下Settings下面Storage选项的实现过程. 2.Settings概览 在之前的KK平台上Sett ...

  6. Android 5.1 Settings源码简要分析

    概述: 先声明:本人工作快两年了,仍是菜鸟级别的,惭愧啊!以前遇到好多知识点都没有记录下来,感觉挺可惜的,现在有机会接触Android 源码.我们一个Android组的搞Setting,我觉得是得写得 ...

  7. PyTorch多卡分布式训练:DistributedDataParallel (DDP) 简要分析

    ©作者 | 伟大是熬出来的 单位 | 同济大学 研究方向 | 机器阅读理解 前言 因为课题组发的卡还没有下来,先向导师问了实验室的两张卡借用.之前都是单卡训练模型,正好在这个机会实践以下单机多卡训练模 ...

  8. oracle查询表实际大小,简要分析估算oracle表的大小

    查询oracle表的大小有几种方法,笔者简要分析下他们的异同 环境,newsadmin.newlog,原本有244,459,078条记录,delete后,现在只有51,109,919记录. 一.seg ...

  9. Android `AsyncTask`简要分析

    Android `AsyncTask`简要分析 AsyncTask简要分析 经典异步任务:AsyncTask,使用场景有:批量下载,批量拷贝等.官方文档就直接给出了一个批量下载的示例. private ...

最新文章

  1. winsock2之最简单的win socket编程
  2. python余弦相似度文本分类_Jaccard与cosine文本相似度的异同
  3. hdu4911 简单树状数组
  4. iOS 关于权限设置的问题
  5. Spring常见的十八中异常Exception
  6. 小腹下面是什么部位_为什么肚子上的肉最难减?说好的马甲线呢?
  7. Linux Ubuntu终端“@”前后的含义及修改(修改用户名及主机名)【试用办法,部分不可行】
  8. 千万别用树套树(线段树)
  9. ktor框架用到了netty吗_Ktor-构建异步服务器和客户端的 Kotlin 框架
  10. 当数学遇上古诗词,太妙了!
  11. 背景图片的位置(HTML、CSS)
  12. cholesky分解_Time Series Analysis-1.2 LDL分解
  13. 开源大数据周刊-第32期
  14. Velocity模板基本常用语法
  15. oracle数据库sqlloader,Oracle 的SQL*LOADER
  16. PLC浏览器端机械动画仿真(nodejs、vue)
  17. 2020中兴捧月算法大赛迪杰斯特拉赛道初赛题解
  18. CTFSHOW 愚人节欢乐赛WP
  19. 系统的设计一个指标体系
  20. mysql数据库中三张表的基本连接

热门文章

  1. Android RelativeLayout和LinearLayout性能分析
  2. android object比较大小
  3. java.lang包—对象基类Object
  4. JVM学习笔记之-StringTable String的基本特性,内存分配,基本操作,拼接操作,intern()的使用,垃圾回收 ,G1中的String去重操作
  5. RPi 树莓派 DSI 接口研究 MIPI raspberry pi
  6. Java项目出现的问题01----学习
  7. 博客园使用highlight.js对代码进行高亮,并实现自定义关键词高亮
  8. Linux文件查找命令具体解释-which whereis find locate
  9. Java和Python安装和编译器使用
  10. iOS开发——手机号,密码,邮箱,身份证号,中文判断