MultiDex原理分析

MultiDex原理分析

一、MultiDex是什么,解决了什么问题

MultiDex 顾名思义就是对分包的Dex文件进行读取加载到ClassLoader的库

android 早期的版本中,Dex文件(android对java中字节码 class文件 进行了调整,把所有的class打包到一的Dex文件中)的方法数量是有上限的65536,当一个app中的方法数量超过这个值,在app安装的时候就会抛出错误,无法安装

为什么Dex的方法数量上限是65536呢?

android在安装一个应用的时候其中会有一步工作是使用DexOpt工具对Dex文件进行优化,会将Dex文件转成ODex文件(Optimised Dex 优化了的Dex文件)因为ODex速度更快,DexOpt处理Dex文件的时候会把Dex中的所有方法提取id索引,然后存在一个链表中,链表的长度用short来保存,这个就导致了这个长度不能超过65536

在android5.0以前是有这个问题的,在android5.0以后android系统解决了这个问题,所以MultiDex是针对android5.0以前的这个问题起作用的

解决这个问题要分成2步

1、将Dex分包,分成若干个不同的包

2、将这些不同的Dex加载进入app的ClassLoader中,从而正常加载

第一步:这一步其实用的android studio中的插件来解决的,

multiDexEnabled true

这个是在工程中的app下面的build.gradle文件中 配置的,就是告诉gradle 在编译打包app的时候将app打包成多个Dex的模式

第二步:在app工程中配置MultiDex库,来加载多个Dex文件,这个配置是在app的application中的

protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

或者直接让自己的application继承MultiDexApplication 就可以了

public class MultiDexApplication extends Application {
    public MultiDexApplication() {
    }

    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}

image

这个就是接入了MultiDex库的 打包app的资源目录图了

可以看到很多的Dex文件

二、MutiDex实现原理

接下来我们来分析下MutiDex如何实现加载多个Dex文件到app的ClassLoader中的

1、ClassLoader分析

classloader是java的类加载器,是用来把java类加载java虚拟器中的作用

在android中的虚拟器是Dalvik把java的虚拟器进行了调整修改,来更加在适配android的轻量化需求

但是这个是在android5.0以前,

android5.0之后变成了AndroidRuntime简称ART,简而言之将android的Dex一次基础性预编译,提前将字节码转为机器码,减少了Dalvik的运行时转机器码的工作量,速度更快了,

我们来看下ClassLoader的继承结构

image

  • ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。BootClassLoader是它的内部类。

  • SecureClassLoader类和JDK8中的SecureClassLoader类的代码是一样的,它继承了抽象类

  • ClassLoader。SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。

  • URLClassLoader类和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。

  • InMemoryDexClassLoader是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。

  • BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它。

2、MultiDex流程

我们用MultiDex2.0.0为例子

1、MultiDex.install(this);

这个是MultiDex的调用的其实位置,这个是在aplication的attachBaseContext中调用的,所以我们的代码不要在attachBaseContext的MultiDex.install(this) 之前调用,因为这个时候有些Dex还没有加载,会跑出来ClassNotFoundException

2、MultiDex.install内部逻辑

public static void install(Context context) {
    
    
        Log.i("MultiDex", "Installing application");
        if (IS_VM_MULTIDEX_CAPABLE) {
    
    
            Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
        } else if (VERSION.SDK_INT < 4) {
    
    
            throw new RuntimeException("MultiDex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
        } else {
    
    
            try {
    
    
                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;
                }

                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");
        }
    }

1、IS_VM_MULTIDEX_CAPABLE 这个是当前的虚拟器是否支持MutliDex的标志

private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version"));

static boolean isVMMultidexCapable(String versionString) {
        boolean isMultidexCapable = false;
        if (versionString != null) {
            Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
            if (matcher.matches()) {
                try {
                    int major = Integer.parseInt(matcher.group(1));
                    int minor = Integer.parseInt(matcher.group(2));
                    isMultidexCapable = major > 2 || major == 2 && minor >= 1;
                } catch (NumberFormatException var5) {
                }
            }
        }

        Log.i("MultiDex", "VM with version " + versionString + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
        return isMultidexCapable;
    }

版本是通过System.getProperty(“java.vm.version”) 来获取虚拟器的版本

然后根据版本号判断条件来识别,通过代码可以看出 要大于等2.1.0的版本才具备MutilDex的能力,这个是系统就具备了这个能力 不需要Mutildex这个库来实现了,所以低版本的android 是不具备这个能力的

如果是>=2.1.0 这个库就什么都不做 让系统来完成

2、else if (VERSION.SDK_INT < 4)

这个表示android4一下的不支持 会直接抛异常

3、获取application 获取dex 然后加载

try {
                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;
                }

                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() + ").");
            }

ApplicationInfo 的获取没什么好说的,就是获取的app的相关资源和路径等信息,比如apk的安装路径 比如dex路径等

3、doInstallation

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 {
    
    
    	//installedApk 这个是判断apk是否已经安装,避免重复加载 节省时间
        synchronized(installedApk) {
    
    
            if (!installedApk.contains(sourceApk)) {
    
    
                installedApk.add(sourceApk);
                if (VERSION.SDK_INT > 20) {
    
    
                    //如果sdk版本高于20,MultiDex 不能保证可行,就是说sdk版本高于android5.0 这个库依然可以执行 但是不保证没问题
                    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") + "\"");
                }

                ClassLoader loader;
                try {
    
    
                    //获取app的ClassLoader 这个ClassLoader到底是什么呢?这个下面详解
                    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 {
    
    
                        //清理Dex的缓存目录?难道每次都清楚缓存,那每次进app都要加载 ?
                        //下面详解
                        clearOldDexDir(mainContext);
                    } catch (Throwable var24) {
    
    
                        Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", var24);
                    }
					//这个就是获取Dex的缓存目录,
                    File dexDir = getDexDir(mainContext, dataDir, secondaryFolderName);
                    //这个类的作用是提取apk中的dex 然后压缩成zip格式的文件 最后返回zip的list
                    MultiDexExtractor extractor = new MultiDexExtractor(sourceApk, dexDir);
                    IOException closeException = null;

                    try {
    
    
                        //根据apk里的dex文件 压缩成zip格式 然后返回
                        List files = extractor.load(mainContext, prefsKeyPrefix, false);

                        try {
    
    
                            //将zip的dex文件 添加到系统的app的ClassLoader的Dex列表前面
                            //为了能够提前加载
                            //具体过程稍后解析
                            installSecondaryDexes(loader, dexDir, files);
                        } catch (IOException var26) {
    
    
                            if (!reinstallOnPatchRecoverableException) {
    
    
                                throw var26;
                            }

                            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;
                    }
                }
            }
        }
    }

上面的已经写了相关注释,比较复杂的过程会有详细解释

1、loader = mainContext.getClassLoader();

这个到底是来自哪里

public abstract ClassLoader getClassLoader();
首先这个调用会走到这里 这个是Context里的抽象方法,他的实现是在ContextImpl
ContextImpl:

@Override
275      public ClassLoader getClassLoader() {
276          return mPackageInfo != null ?
277                  mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
278      }
如果mPackageInfo==null 就调用ClassLoader.getSystemClassLoader() mPackageInfo缓存作用
public abstract class ClassLoader {

    static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
    }
    
   private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");

        // String[] paths = classPath.split(":");
        // URL[] urls = new URL[paths.length];
        // for (int i = 0; i < paths.length; i++) {
        // try {
        // urls[i] = new URL("file://" + paths[i]);
        // }
        // catch (Exception ex) {
        // ex.printStackTrace();
        // }
        // }
        //
        // return new java.net.URLClassLoader(urls, null);

        // TODO Make this a java.net.URLClassLoader once we have those?
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    }

我们可以清楚的看到 Context 调用的ClassLoader是PathClassLoader,这个是加载我们自己写的类的加载器

同时我们可以看到PathClassLoader的父加载器是BootClassLoader,之前我们提过BootClassLoader是ClassLoader的内部类,这个BootClassLoader 是在虚拟器开始的时候就初始化 然后加载系统的一些类库。

app自定义的类 都是PathClassLoader来完成加载的

我们来看下ClassLoader的loadClass方法

  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) {
                try {
                    if (parent != null) {
                    	//从父类加载  加载class
                        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.
                    //上面都都没有了 找不到了 就从当前的类加载器 加载类信息
                    c = findClass(name);
                }
            }
            return c;
    }

双亲委托机制:

我们可以看到ClassLoader的加载是双亲委托机制的,先查找父加载器是否已经加载过,如果加载过了 就直接用父类的,如果没有就从自己的类加载中加载

优点:

1、提高加载性能

这个很容易理解,类加载的过程是比较耗时的,如果避免了重复加载 性能肯定会提高

2、安全性

防止内核的核心类被串改,比如String在系统一开始的时候就被加载了,如果用户自定一个String类,如果不先去parent去查询是否已经加载了,就会直接用用的String的自定义类,那么这个系统毫无安全可言了,核心api可以随意被替换,相反如果加载用户的自定义String类,就先去查询父ClassLoader,始终得到是系统String类,用户自定义的没有起作用了

2、clearOldDexDir

清除旧的dex文件,这里不是清除上次加载的dex文件缓存。
获取dex缓存目录是,会优先获取/data/data/ p a c k a g e N a m e / c o d e − c a c h e 作 为 缓 存 目 录 。 如 果 获 取 失 败 , 则 使 用 / d a t a / d a t a / {packageName}/code-cache作为缓存目录。 如果获取失败,则使用/data/data/ packageName/codecache使/data/data/{packageName}/files/code-cache目录。
这里清除的是第二个目录。

3、MultiDexExtractor#load 方法

    List<? extends File> load(Context context, String prefsKeyPrefix, boolean forceReload) throws IOException {
    
    
        Log.i("MultiDex", "MultiDexExtractor.load(" + this.sourceApk.getPath() + ", " + forceReload + ", " + prefsKeyPrefix + ")");
        //文件锁,防止多进程操作 MultiDex.lock 是锁的文件
        if (!this.cacheLock.isValid()) {
    
    
            throw new IllegalStateException("MultiDexExtractor was closed");
        } else {
    
    
            List files;
            //是否强制重新解压,第一次优先使用已经加载过的dex
            //根据crc和修改时间来判断是否需要重新解压,覆盖安装app后crc和时间会修改
            if (!forceReload && !isModified(context, this.sourceApk, this.sourceCrc, prefsKeyPrefix)) {
    
    
                try {
    
    
                    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);
                    files = this.performExtractions();
                    putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
                }
            } else {
    
    
                if (forceReload) {
    
    
                    Log.i("MultiDex", "Forced extraction must be performed.");
                } else {
    
    
                    Log.i("MultiDex", "Detected that extraction must be performed.");
                }
				
                files = this.performExtractions();
                putStoredApkInfo(context, prefsKeyPrefix, getTimeStamp(this.sourceApk), this.sourceCrc, files);
            }

            Log.i("MultiDex", "load found " + files.size() + " secondary dex files");
            return files;
        }
    }

这个方法的作用就是把apk中的dex文件打包压缩成zip的文件然后返回

下面来看下如果重新解压要走的方法

performExtractions()

private List<MultiDexExtractor.ExtractedDex> performExtractions() throws IOException {
    
    
        String extractedFilePrefix = this.sourceApk.getName() + ".classes";
        this.clearDexDir();
        //返回结果集合 实质是file文件集合
        List<MultiDexExtractor.ExtractedDex> files = new ArrayList();//要抽取的apk文件
        ZipFile apk = new ZipFile(this.sourceApk);

        try {
    
    
            //dex的序号名字一般都是classes2.dex  classes3.dex
            //第一个dex已经被系统加载了,所以是从第二个dex开始,
           
            int secondaryNumber = 2;

            for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
    
    
                String fileName = extractedFilePrefix + secondaryNumber + ".zip";
                //这个就是要添加进去的zip文件 这个zip里面后面会从apk里面提取对应的dex放进去
                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;
				//numAttempts :这个是重复次数,因为提取apk 有可能不成功,
                //isExtractionSuccessful 通过这个标志来判断是否提取成功
                while(numAttempts < 3 && !isExtractionSuccessful) {
    
    
                    ++numAttempts;
                    //这个方法的核心,从apk里面提取出dex文件 然后放进去extractedFile的zip文件中
                    //这个其实是一些io操作
                    extract(apk, dexFile, extractedFile, extractedFilePrefix);

                    try {
    
    
                        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;
    }

4、installSecondaryDexes

这个方法里面就是这个框架的核心了,顾名思义加载dex

这个方法里面会根据sdk的版本 使用不同的加载方案 以此来达到兼容的目的

rivate 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);
            }
        }

    }

不同版本的sdk使用不同的加载方案

我们用V19的方案解读,其他版本都类似

private static final class V19 {
    
    
        private V19() {
    
    
        }

        static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
    
    
            /*
            *MultiDex.findField(loader, "pathList");
            *loader:这个就是之前的Context.getClassLoader获取的PathClassLoader
            *pathList 这个是PathClassLoader的父类BaseDexClassLoader的局部属性,一个存储了dex的集			 *合类DexPathList
            
          
            */
            
            Field pathListField = MultiDex.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList();
            //expandFieldArray 这个是把两个数组合并到一起
            //把dexPathList(DexPathList)的dexElements的集合和classes2.dex 等的合并
            //classes2.dex 会把这个追加到系统的classloader的dexlist的后面
            //然后把最后的结果设置到DexPathList中
            //下面会说下makeDexElements
            //
            
            MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
            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));
                throw exception;
            }
        }

        private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    
    
            Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
            return (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
        }
    }
1、BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {
    
    
30      private final DexPathList pathList;
31  
32      /**
33       * Constructs an instance.
34       *
35       * @param dexPath the list of jar/apk files containing classes and
36       * resources, delimited by {@code File.pathSeparator}, which
37       * defaults to {@code ":"} on Android
38       * @param optimizedDirectory directory where optimized dex files
39       * should be written; may be {@code null}
40       * @param librarySearchPath the list of directories containing native
41       * libraries, delimited by {@code File.pathSeparator}; may be
42       * {@code null}
43       * @param parent the parent class loader
44       */
45      public BaseDexClassLoader(String dexPath, File optimizedDirectory,
46              String librarySearchPath, ClassLoader parent) {
    
    
47          super(parent);
48          this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
49      }

BaseDexClassLoader 里面有个pathList属性类,里面存储了已经加载了的dex列表

2、makeDexElements
private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
    return (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
}

这个方法其实就是反射调用DexPathList的makeDexElements 生成Element[] 数组集合,

那我们看下Element

static class Element {
    
    
			//这个element对应的file
494          private final File dir;
			//这个file是否是目录
495          private final boolean isDirectory;
			//如果这个file是以zip结尾,那么这个文件会被初始化为DexFile文件,zip会被初始化
496          private final File zip;
			//如果这个文件是以dex结尾 那么这个文件会被初始化为DexFile文件,zip=null
497          private final DexFile dexFile;
498  
499          private ClassPathURLStreamHandler urlHandler;
500          private boolean initialized;
501  
502          public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
    
    
503              this.dir = dir;
504              this.isDirectory = isDirectory;
505              this.zip = zip;
506              this.dexFile = dexFile;
507          }
508  
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
134              DexPathList.Element[] elements) throws IOException {
    
    
135          if (outputName != null) {
    
    
136              try {
    
    
137                  String parent = new File(outputName).getParent();
138                  if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
    
    
139                      throw new IllegalArgumentException("Optimized data directory " + parent
140                              + " is not owned by the current user. Shared storage cannot protect"
141                              + " your application from code injection attacks.");
142                  }
143              } catch (ErrnoException ignored) {
    
    
144                  // assume we'll fail with a more contextual error later
145              }
146          }
147  		//这个方法最终会调用到native的方法 并返回dex的native索引,
    		//native中其实这个主要是验证dex的结构合理行,初始化是否正确
148          mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
149          mFileName = sourceName;
150          //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
151      }
 private static native Object openDexFileNative(String sourceName, String outputName, int flags,
397              ClassLoader loader, DexPathList.Element[] elements);

三、总结

MutliDex框架的核心思想是把其他的dex文件添加进了app的Classloader(PathCloassLoader)中的pathlist(可以理解为dex列表中,其实是DexFile的数组)其实是一种提前往系统注入dex的方法策略 来实分多次加载dex文件的目的

市面上很多的热修复框架都是基于这个思想来做的

猜你喜欢

转载自blog.csdn.net/fagawee/article/details/120956919