先提一下(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
后面会讨论两个地方:
- AndroidBuilder.createMainDexList
- MultiDexTransform.transform
2. dalvik-dx (处理和dex相关逻辑)
https://android.googlesource.com/platform/dalvik/+/6a8e552/dx
后面会讨论一个地方
- 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 ${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());
}
}
}