Android multidex 主dex是怎么来的?

先提一下(gradle assembleDebug)编译过程中关键产物
build/intermediates/multi-dex/debug 目录中,可以看到如下几个文件

componentClasses.jar   —→ 经过shrinkWithProguard得到(jarOfRoots.jar)
components.flags
maindexlist.txt   —→ 通过一些列操作,计算出来的一个列表,记录放入主dex中的所有class (说个不好听的如果你能hook掉这个文件的写入,那么想让谁在主dex, 谁就在主dex)
manifest_keep.txt

一: 依赖两个核心模块

1.  gradle 插件 (本文基于3.0.0)(编译项目)

https://android.googlesource.com/platform/tools/gradle/+/gradle_3.0.0

后面会讨论两个地方:


  1. AndroidBuilder.createMainDexList


  1. MultiDexTransform.transform

2.  dalvik-dx (处理和dex相关逻辑)

https://android.googlesource.com/platform/dalvik/+/6a8e552/dx

后面会讨论一个地方


  1. ClassReferenceListBuilder

二: 生成主dex的核心流程

1.  gradle 插件篇

  • AndroidBuilder

    收集我们项目配置的build.gradle等基本编译信息,以及后续的 createMainDexList
    路径在:gradle_3.0.0_base-5d179ada33c355edb5c7da0f5f98321116476ce0/build-system/builder/src/main/java/com/android/builder/core/AndroidBuilder.java

  • MultiDexTransform -> transform :

    TaskManager会去构建Android编译需要的所需task,这里会初始化MultiDexTransform,路径在:gradle_3.0.0_base-5d179ada33c355edb5c7da0f5f98321116476ce0/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/transforms/MultiDexTransform.java
    核心代码入口

     @Override
    public void transform(@NonNull TransformInvocation invocation)
            throws IOException, TransformException, InterruptedException {
        // Re-direct the output to appropriate log levels, just like the official ProGuard task.
        LoggingManager loggingManager = invocation.getContext().getLogging();
        loggingManager.captureStandardOutput(LogLevel.INFO);
        loggingManager.captureStandardError(LogLevel.WARN);

        try {
            File input = verifyInputs(invocation.getReferencedInputs());
            //-->1 把所有的class文件经过 Proguard (runProguard)处理之后,得到 jarOfRoots.jar 
            shrinkWithProguard(input);
           //-->2 通过上一步生成的 rootJars.jar, 计算出mainDexList
            computeList(input);
        } catch (ParseException | ProcessException e) {
            throw new TransformException(e);
        }
    }
  • MultiDexTransform -> transform -> shrinkWithProguard

    把所有的class文件经过 Proguard (runProguard)处理之后,得到 jarOfRoots.jar ,即:variantScope.getProguardComponentsJarFile()

    private void shrinkWithProguard(@NonNull File input) throws IOException, ParseException {
         //-->1 一大堆混淆的配置。。。
        configuration.obfuscate = false;
        configuration.optimize = false;
        configuration.preverify = false;
        dontwarn();
        dontnote();
        forceprocessing();

       //-->2 把manifest_keep.txt中的内容加过来
        applyConfigurationFile(manifestKeepListProguardFile);
        if (userMainDexKeepProguard != null) {
             //-->3 如果在项目中有自定义想放入主dex的keep(multiDexKeepProguard file('./maindex-rules.pro')),也追加进来
            applyConfigurationFile(userMainDexKeepProguard);
        }
        //-->4  3.0.0的插件默认就帮我们keep了一些
        // add a couple of rules that cannot be easily parsed from the manifest.
        keep("public class * extends android.app.Instrumentation { <init>(); }");
        keep("public class * extends android.app.Application { "
                + "  <init>(); "
                + "  void attachBaseContext(android.content.Context);"
                + "}");
        keep("public class * extends android.app.backup.BackupAgent { <init>(); }");
        keep("public class * extends java.lang.annotation.Annotation { *;}");
        keep("class com.android.tools.ir.** {*;}"); // Instant run.

        //-->5 把 shrinkedAndroid.jar 和刚刚的 input 文件都加入 classpath 里。
        // handle inputs
        libraryJar(findShrinkedAndroidJar());
        //-->6 把所有的class引入进来放入path中
        inJar(input, null);
        //-->7 设置产物的路径
        // outputs.
        outJar(variantScope.getProguardComponentsJarFile());
        printconfiguration(configFileOut);

         //-->8 最终执行混淆
        // run proguard
        runProguard();
    }
  • MultiDexTransform -> transform -> computeList

    把上一步生成 jarOfRoots.jar 以及所有的class 通过 callDx 处理之后,计算出mainDexClasses, 如果项目中自己配置了需要放在主dex的类(MainDexKeepFile),这里会读取出来追加到mainDexClasses中, 最终写到一个mainDexListFile文件中

    private void computeList(File _allClassesJarFile) throws ProcessException, IOException {
        // manifest components plus immediate dependencies must be in the main dex.
        Set<String> mainDexClasses = callDx(
                _allClassesJarFile,
                variantScope.getProguardComponentsJarFile());

        if (userMainDexKeepFile != null) {
            mainDexClasses = ImmutableSet.<String>builder()
                    .addAll(mainDexClasses)
                    .addAll(Files.readLines(userMainDexKeepFile, Charsets.UTF_8))
                    .build();
        }

        String fileContent = Joiner.on(System.getProperty("line.separator")).join(mainDexClasses);

        Files.write(fileContent, mainDexListFile, Charsets.UTF_8);

    }
  • MultiDexTransform -> transform -> computeList -> callDx

    callDex 顾名思义就是调用 Dex 返回一个需要放在主dex的列表, 其实最终又会调用刚才提到的AndroidBuilder -> createMainDexList

  private Set<String> callDx(File allClassesJarFile, File jarOfRoots) throws ProcessException {
        EnumSet<AndroidBuilder.MainDexListOption> mainDexListOptions =
                EnumSet.noneOf(AndroidBuilder.MainDexListOption.class);
        if (!keepRuntimeAnnotatedClasses) {
            mainDexListOptions.add(
                    AndroidBuilder.MainDexListOption.DISABLE_ANNOTATION_RESOLUTION_WORKAROUND);
            Logging.getLogger(MultiDexTransform.class).warn(
                    "Not including classes with runtime retention annotations in the main dex.\n"
                            + "This can cause issues with reflection in older platforms.");
        }
      //这里 最终又会调用刚才提到的`AndroidBuilder -> createMainDexList`
        return variantScope.getGlobalScope().getAndroidBuilder().createMainDexList(
                allClassesJarFile, jarOfRoots, mainDexListOptions);
    }
  • AndroidBuilder -> createMainDexList

    调用 dex.jar 中的 ClassReferenceListBuilder ,找出哪些需要放在主dex中的class,需要传入的参数是所有的class文件、通过 shrinkWithProguard之后得到的jarOfRoots.jar 以及一个MainDexListOption配置

    public Set<String> createMainDexList(
            @NonNull File allClassesJarFile,
            @NonNull File jarOfRoots,
            @NonNull EnumSet<MainDexListOption> options) throws ProcessException {

        BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
        ProcessInfoBuilder builder = new ProcessInfoBuilder();

        String dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
        if (dx == null || !new File(dx).isFile()) {
            throw new IllegalStateException("dx.jar is missing");
        }

        builder.setClasspath(dx);
        builder.setMain("com.android.multidex.ClassReferenceListBuilder");

        if (options.contains(MainDexListOption.DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) {
            builder.addArgs("--disable-annotation-resolution-workaround");
        }

        builder.addArgs(jarOfRoots.getAbsolutePath());
        builder.addArgs(allClassesJarFile.getAbsolutePath());

        CachedProcessOutputHandler processOutputHandler = new CachedProcessOutputHandler();

        mJavaProcessExecutor.execute(builder.createJavaProcess(), processOutputHandler)
                .rethrowFailure()
                .assertNormalExitValue();

        LineCollector lineCollector = new LineCollector();
        processOutputHandler.getProcessOutput().processStandardOutputLines(lineCollector);
        return ImmutableSet.copyOf(lineCollector.getResult());
    }

2.  dalvik-dx 篇

gradle 插件中已经准备好了jarOfRoots和所有class文件,现在该dex上场了的。

  • ClassReferenceListBuilder

    路径在: dalvik-6a8e552-dx/src/com/android/multidex/ClassReferenceListBuilder.java 其实在我们的SDK环境中的build-tools下能找到一个脚本 mainDexClasses ,比如build-tools/26.0.2。里面最后一行就有调用,调用方式和刚才的gradle插件类似。
    java -cp “$jarpath” com.android.multidex.MainDexListBuilder <img src="https://chart.googleapis.com/chart?cht=tx&chl=%7BdisableKeepAnnotated%7D%20%22" alt="{disableKeepAnnotated} " "="">{tmpOut}” ${@} ||  exit 11

这个类的作用是什么呢?顾名思义找出class的引用。那么怎么做到的呢?

  • ClassReferenceListBuilder -> main
    先来看主入口,main函数做了三件事情:

  1. 拿到刚才传入的 jarOfRoots.jar

  2. 拿到刚才传入的所有class文件

  3. 开始干活了的 构建了一个ClassReferenceListBuilder,调用addRoots

代码如下:

   public static void main(String[] args) {
         ......
      // 1. 拿到刚才传入的 jarOfRoots.jar
        ZipFile jarOfRoots;
        try {
            jarOfRoots = new ZipFile(args[0]);
        } catch (IOException e) {
            System.err.println("\"" + args[0] + "\" can not be read as a zip archive. ("
                    + e.getMessage() + ")");
            System.exit(STATUS_ERROR);
            return;
        }


        Path path = null;
        try {
          // 2. 拿到刚才传入的所有class文件
            path = new Path(args[1]);
          // 3. 开始干活了的 构建了一个ClassReferenceListBuilder
            ClassReferenceListBuilder builder = new ClassReferenceListBuilder(path);
            builder.addRoots(jarOfRoots);

            printList(builder.toKeep);
        } catch (IOException e) {
            System.err.println("A fatal error occured: " + e.getMessage());
            System.exit(STATUS_ERROR);
            return;
        } finally {
            try {
                jarOfRoots.close();
            } catch (IOException e) {
                // ignore
            }
            if (path != null) {
                for (ClassPathElement element : path.elements) {
                    try {
                        element.close();
                    } catch (IOException e) {
                        // keep going, lets do our best.
                    }
                }
            }
        }
    }
  • ClassReferenceListBuilder -> main -> addRoots

    把 jarOfRoots中的class文件都keep住,以及这些class直接依赖的class文件从刚才传入的所有class文件中找出来并且也keep住。这样就构成了一个主的需要keep的class列表用以生成主dex.

    public void addRoots(ZipFile jarOfRoots) throws IOException {

        // keep roots
        for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
                entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (name.endsWith(CLASS_EXTENSION)) {
                toKeep.add(name.substring(0, name.length() - CLASS_EXTENSION.length()));
            }
        }

        // keep direct references of roots (+ direct references hierarchy)
        for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
                entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (name.endsWith(CLASS_EXTENSION)) {
                DirectClassFile classFile;
                try {
                    classFile = path.getClass(name);
                } catch (FileNotFoundException e) {
                    throw new IOException("Class " + name +
                            " is missing form original class path " + path, e);
                }

                addDependencies(classFile.getConstantPool());
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_42600182/article/details/81016476