先提一下 (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
后面会讨论两个地方:
- AndroidBuilder.createMainDexList
- MultiDexTransform.transform
- 2. dalvik-dx (处理和 dex 相关逻辑)
- https://android.googlesource.com/platform/dalvik/+/6a8e552/dx
后面会讨论一个地方
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 函数做了三件事情:
拿到刚才传入的 jarOfRoots.jar
拿到刚才传入的所有 class 文件
开始干活了的 构建了一个 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());
- }
- }
- }
来源: http://www.jianshu.com/p/7d49fc56d234