美文网首页Android开发经验谈程序员android
Android multidex 主dex是怎么来的?

Android multidex 主dex是怎么来的?

作者: IMSk | 来源:发表于2018-05-25 14:08 被阅读88次

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


image.png

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

  • MultiDexTransform -> transform :

核心代码入口

     @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 把Android的jar引入进来放入path中
        // 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

其实在我们的SDK环境中的build-tools下能找到一个脚本 mainDexClasses ,比如build-tools/26.0.2。里面最后一行就有调用,调用方式和刚才的gradle插件类似。
java -cp "$jarpath" com.android.multidex.MainDexListBuilder ${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());
            }
        }
    }

相关文章

网友评论

  • 我才是孙悟空:比如我自己有个apk包,也知道秘钥什么的。 我想往里面加java逻辑代码, 是反编译,往dex里面加.class吗,有没有类似的博文或者解决思路推荐一下 ,大神
    我才是孙悟空:@IMSk apk内应该会预留一些接口。如果需要这个功能,拿到apk,反编译,加入资源文件等,然后就是java逻辑代码
    我才是孙悟空:@IMSk 不能说是篡改吧,因为是自己的apk,有些部分是公共的,我想提取出来,做个一键加入这个功能,使功能脱离apk。之后需要这个功能apk,一键操作一下 ,差不多是这个意思,
    IMSk:@我才是孙悟空 听着意思是反编译之后篡改apk?
  • IT人故事会:太感谢了,写的不错!
    IMSk:@IT人故事会 后面还会补充一些 这个比较大概的流程

本文标题:Android multidex 主dex是怎么来的?

本文链接:https://www.haomeiwen.com/subject/stbyjftx.html