Android MultiDex
使用過MultiDex都知道,AndroidStudio會(huì)在編譯過程中劃分多個(gè)dex,如class.dex,class2.dex。。。
這里有一個(gè)問題,主dex是如何劃分的?
在構(gòu)建完成后會(huì)在會(huì)生成miandexlist.txt,如圖
1. TaskManager
/**
* Creates the post-compilation tasks for the given Variant.
*
* These tasks create the dex file from the .class files, plus optional intermediary steps like
* proguard and jacoco
*/
public void createPostCompilationTasks(
@NonNull final VariantScope variantScope) {
checkNotNull(variantScope.getJavacTask());
final BaseVariantData variantData = variantScope.getVariantData();
final GradleVariantConfiguration config = variantData.getVariantConfiguration();
TransformManager transformManager = variantScope.getTransformManager();
// ---- Code Coverage first -----
boolean isTestCoverageEnabled =
config.getBuildType().isTestCoverageEnabled()
&& !config.getType().isForTesting()
&& !variantScope.getInstantRunBuildContext().isInInstantRunMode();
if (isTestCoverageEnabled) {
createJacocoTransform(variantScope);
}
maybeCreateDesugarTask(variantScope, config.getMinSdkVersion(), transformManager);
AndroidConfig extension = variantScope.getGlobalScope().getExtension();
// Merge Java Resources.
createMergeJavaResTransform(variantScope);
// ----- External Transforms -----
// apply all the external transforms.
List<Transform> customTransforms = extension.getTransforms();
List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();
for (int i = 0, count = customTransforms.size(); i < count; i++) {
Transform transform = customTransforms.get(i);
List<Object> deps = customTransformsDependencies.get(i);
transformManager
.addTransform(taskFactory, variantScope, transform)
.ifPresent(
t -> {
if (!deps.isEmpty()) {
t.dependsOn(deps);
}
// if the task is a no-op then we make assemble task depend on it.
if (transform.getScopes().isEmpty()) {
variantScope.getAssembleTask().dependsOn(t);
}
});
}
// ----- Android studio profiling transforms
for (String jar : getAdvancedProfilingTransforms(projectOptions)) {
if (variantScope.getVariantConfiguration().getBuildType().isDebuggable()
&& variantData.getType().equals(VariantType.DEFAULT)
&& jar != null) {
transformManager.addTransform(
taskFactory, variantScope, new CustomClassTransform(jar));
}
}
// ----- Minify next -----
maybeCreateJavaCodeShrinkerTransform(variantScope);
maybeCreateResourcesShrinkerTransform(variantScope);
// ----- 10x support
PreColdSwapTask preColdSwapTask = null;
if (variantScope.getInstantRunBuildContext().isInInstantRunMode()) {
DefaultTask allActionsAnchorTask = createInstantRunAllActionsTasks(variantScope);
assert variantScope.getInstantRunTaskManager() != null;
preColdSwapTask =
variantScope.getInstantRunTaskManager().createPreColdswapTask(projectOptions);
preColdSwapTask.dependsOn(allActionsAnchorTask);
// force pre-dexing to be true as we rely on individual slices to be packaged
// separately.
extension.getDexOptions().setPreDexLibraries(true);
variantScope.getInstantRunTaskManager().createSlicerTask();
extension.getDexOptions().setJumboMode(true);
}
// ----- Multi-Dex support
DexingType dexingType = variantScope.getDexingType();
// Upgrade from legacy multi-dex to native multi-dex if possible when using with a device
if (dexingType == DexingType.LEGACY_MULTIDEX) {
if (variantScope.getVariantConfiguration().isMultiDexEnabled()
&& variantScope
.getVariantConfiguration()
.getMinSdkVersionWithTargetDeviceApi()
.getFeatureLevel()
>= 21) {
dexingType = DexingType.NATIVE_MULTIDEX;
}
}
Optional<TransformTask> multiDexClassListTask;
if (dexingType == DexingType.LEGACY_MULTIDEX) {
boolean proguardInPipeline = variantScope.getCodeShrinker() == CodeShrinker.PROGUARD;
// If ProGuard will be used, we'll end up with a "fat" jar anyway. If we're using the
// new dexing pipeline, we'll use the new MainDexListTransform below, so there's no need
// for merging all classes into a single jar.
if (!proguardInPipeline && !usingIncrementalDexing(variantScope)) {
// Create a transform to jar the inputs into a single jar. Merge the classes only,
// no need to package the resources since they are not used during the computation.
JarMergingTransform jarMergingTransform =
new JarMergingTransform(TransformManager.SCOPE_FULL_PROJECT);
transformManager
.addTransform(taskFactory, variantScope, jarMergingTransform)
.ifPresent(variantScope::addColdSwapBuildTask);
}
// ---------
// create the transform that's going to take the code and the proguard keep list
// from above and compute the main class list.
Transform multiDexTransform;
if (usingIncrementalDexing(variantScope)) {
if (projectOptions.get(BooleanOption.ENABLE_D8_MAIN_DEX_LIST)) {
multiDexTransform = new D8MainDexListTransform(variantScope);
} else {
multiDexTransform =
new MainDexListTransform(variantScope, extension.getDexOptions());
}
} else {
multiDexTransform = new MultiDexTransform(variantScope, extension.getDexOptions());
}
multiDexClassListTask =
transformManager.addTransform(taskFactory, variantScope, multiDexTransform);
multiDexClassListTask.ifPresent(variantScope::addColdSwapBuildTask);
} else {
multiDexClassListTask = Optional.empty();
}
if (usingIncrementalDexing(variantScope)) {
createNewDexTasks(variantScope, multiDexClassListTask.orElse(null), dexingType);
} else {
createDexTasks(variantScope, multiDexClassListTask.orElse(null), dexingType);
}
if (preColdSwapTask != null) {
for (DefaultTask task : variantScope.getColdSwapBuildTasks()) {
task.dependsOn(preColdSwapTask);
}
}
// ---- Create tasks to publish the pipeline output as needed.
final File intermediatesDir = variantScope.getGlobalScope().getIntermediatesDir();
createPipelineToPublishTask(
variantScope,
transformManager.getPipelineOutputAsFileCollection(StreamFilter.DEX),
FileUtils.join(intermediatesDir, "bundling", "dex"),
PUBLISHED_DEX);
createPipelineToPublishTask(
variantScope,
transformManager.getPipelineOutputAsFileCollection(StreamFilter.RESOURCES),
FileUtils.join(intermediatesDir, "bundling", "java-res"),
PUBLISHED_JAVA_RES);
createPipelineToPublishTask(
variantScope,
transformManager.getPipelineOutputAsFileCollection(StreamFilter.NATIVE_LIBS),
FileUtils.join(intermediatesDir, "bundling", "native-libs"),
PUBLISHED_NATIVE_LIBS);
}
這個(gè)方法的作用是在編譯過程中創(chuàng)建即將完成構(gòu)建的task,從編譯的.class文件轉(zhuǎn)換成.dex文件,再加上額外的任務(wù)例如:proguard
這里面可以添加的自定義transform,如可以添加一個(gè)修改字節(jié)碼的transform在編譯過程中進(jìn)行插樁的操作(具體不在這里詳述了)
接著添加android自己的transform,例如:JavaCodeShrinkerTransform,ResourcesShrinkerTransform,
MainDexListTransform
最后再執(zhí)行dex的transform。
2.MultiDexTransform
MultiDexTransform.transform()
if (dexingType == DexingType.LEGACY_MULTIDEX) {
boolean proguardInPipeline = variantScope.getCodeShrinker() == CodeShrinker.PROGUARD;
// If ProGuard will be used, we'll end up with a "fat" jar anyway. If we're using the
// new dexing pipeline, we'll use the new MainDexListTransform below, so there's no need
// for merging all classes into a single jar.
if (!proguardInPipeline && !usingIncrementalDexing(variantScope)) {
// Create a transform to jar the inputs into a single jar. Merge the classes only,
// no need to package the resources since they are not used during the computation.
JarMergingTransform jarMergingTransform =
new JarMergingTransform(TransformManager.SCOPE_FULL_PROJECT);
transformManager
.addTransform(taskFactory, variantScope, jarMergingTransform)
.ifPresent(variantScope::addColdSwapBuildTask);
}
// ---------
// create the transform that's going to take the code and the proguard keep list
// from above and compute the main class list.
Transform multiDexTransform;
if (usingIncrementalDexing(variantScope)) {
if (projectOptions.get(BooleanOption.ENABLE_D8_MAIN_DEX_LIST)) {
multiDexTransform = new D8MainDexListTransform(variantScope);
} else {
multiDexTransform =
new MainDexListTransform(variantScope, extension.getDexOptions());
}
} else {
multiDexTransform = new MultiDexTransform(variantScope, extension.getDexOptions());
}
multiDexClassListTask =
transformManager.addTransform(taskFactory, variantScope, multiDexTransform);
multiDexClassListTask.ifPresent(variantScope::addColdSwapBuildTask);
} else {
multiDexClassListTask = Optional.empty();
}
dexingType在minSdk低于21時(shí)為DexingType.LEGACY_MULTIDEX,大于21時(shí)為:DexingType.NATIVE_MULTIDEX
這是因?yàn)榇笥诘扔?1時(shí),即android 5.0運(yùn)行的是ART,ART默認(rèn)會(huì)在內(nèi)部進(jìn)行multidex的操作。
當(dāng)我們?cè)?gradle 中將 multiDexEnabled 設(shè)為 true 后,編譯 app 的過程中 Terminal 會(huì)多出一行: :app:transformClassesWithMultidexlistForDebug
顯然 MultiDex 相關(guān)操作也是通過 Transform Api 完成了,自然我們查看 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 {
Map<MainDexListTransform.ProguardInput, Set<File>> inputs =
MainDexListTransform.getByInputType(invocation);
File input =
Iterables.getOnlyElement(
inputs.get(MainDexListTransform.ProguardInput.INPUT_JAR));
shrinkWithProguard(input, inputs.get(MainDexListTransform.ProguardInput.LIBRARY_JAR));
computeList(input);
} catch (ParseException | ProcessException e) {
throw new TransformException(e);
}
}
代碼少,邏輯簡單,可以猜出個(gè)大概來,通過proguard刪除不必要的代碼,然后執(zhí)行computeList方法
MainDexListTransform.getByInputType(invocation)先將input中的DirectoryInputs和jarInput組合成單一集合,然后根據(jù)input的scope類型是否為PROVIDED_ONLY分離成ProguardInput.INPUT_JAR 和 ProguardInput.LIBRARY_JAR和對(duì)應(yīng)的files存進(jìn)map中。
input是個(gè)啥?Iterables.getOnlyElement拿到的是第一個(gè)INPUT_JAR,而這個(gè)對(duì)應(yīng)的是DirectoryInputs,而DirectoryInputs又對(duì)應(yīng)的app模塊中的類。
所以我們大膽猜測(cè)下,computeList()生成了maindexlist.txt,并且是以app模塊里的類加上它所依賴的類進(jìn)行maindex的劃分 。
shrinkWithProguard
接下來看看shrinkWithProguard
private void shrinkWithProguard(@NonNull File input, @NonNull Set<File> libraryJars)
throws IOException, ParseException {
configuration.obfuscate = false;
configuration.optimize = false;
configuration.preverify = false;
dontwarn();
dontnote();
forceprocessing();
//把manifest_keep.txt的內(nèi)容加進(jìn)來
applyConfigurationFile(manifestKeepListProguardFile);
if (userMainDexKeepProguard != null) {
//如果用戶設(shè)置了userMainDexKeep 也對(duì)其進(jìn)行keep操作
applyConfigurationFile(userMainDexKeepProguard);
}
//multidex默認(rèn)進(jìn)行的keep
MainDexListTransform.getPlatformRules().forEach(this::keep);
//把tool目錄下的shrinkedAndroid.jar,input 和 libraryJars加進(jìn)classpath中
libraryJar(findShrinkedAndroidJar());
libraryJars.forEach(this::libraryJar);
inJar(input, null);
// 設(shè)置output,即中間產(chǎn)物中的componentClasses.jar
outJar(variantScope.getProguardComponentsJarFile());
printconfiguration(configFileOut);
// 執(zhí)行proguard
runProguard();
}
當(dāng)執(zhí)行完shrinkWithProguard之后,接著執(zhí)行compute
computeList
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);
//在這里我們終于看到mainDexListFile,其位置在"multi-dex/$variant/maindexlist.txt
Files.write(fileContent, mainDexListFile, Charsets.UTF_8);
}
接下來看看callDx,參數(shù)jarOfRoots就是上述提到的componentClasses.jar
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.");
}
return variantScope.getGlobalScope().getAndroidBuilder().createMainDexList(
allClassesJarFile, jarOfRoots, mainDexListOptions);
}
真正工作的地方在createMainDexList()
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());
}
mJavaProcessExecutor.execute最終會(huì)調(diào)用project.javaexec執(zhí)行一個(gè)外部的java進(jìn)程(MainDexListBuilder)
3. MainDexListBuilder
接下來分析MainDexListBuilder.main方法
public static void main(String[] args) {
int argIndex = 0;
boolean keepAnnotated = true;
while (argIndex < args.length -2) {
if (args[argIndex].equals(DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) {
keepAnnotated = false;
} else {
System.err.println("Invalid option " + args[argIndex]);
printUsage();
System.exit(STATUS_ERROR);
}
argIndex++;
}
...
...
try {
MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex],
args[argIndex + 1]);
Set<String> toKeep = builder.getMainDexList();
printList(toKeep);
} catch (IOException e) {
System.err.println("A fatal error occured: " + e.getMessage());
System.exit(STATUS_ERROR);
return;
}
}
public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)
throws IOException {
ZipFile jarOfRoots = null;
Path path = null;
try {
try {
jarOfRoots = new ZipFile(rootJar);
} catch (IOException e) {
throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. ("
+ e.getMessage() + ")", e);
}
path = new Path(pathString);
//拿到傳入rootJar和pathString
ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);
mainListBuilder.addRoots(jarOfRoots);
for (String className : mainListBuilder.getClassNames()) {
filesToKeep.add(className + CLASS_EXTENSION);
}
if (keepAnnotated) {
keepAnnotated(path);
}
} 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.
}
}
}
}
}
MainDexListBuilder的構(gòu)造函數(shù)中拿到傳入rootJar和pathString,然后構(gòu)造了mainListBuilder
主要是調(diào)用了 mainListBuilder.addRoots(jarOfRoots);
// keep roots
for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
entries.hasMoreElements();) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.endsWith(CLASS_EXTENSION)) {
classNames.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);
}
}
根據(jù)path找到j(luò)arOfRoot里面的類名,如果找到則加入到Dependencies,可以看到這個(gè)path其實(shí)就是我們一開始傳進(jìn)來的input目錄,本質(zhì)上是以app模塊構(gòu)建的jar文件的集合。
接下來我們回到開篇提到TaskManager.createPostCompilationTasks中,看看接下來將要執(zhí)行的邏輯
public void createPostCompilationTasks(
@NonNull final VariantScope variantScope) {
....
....
//判斷是否使用增量構(gòu)建
if (usingIncrementalDexing(variantScope)) {
createNewDexTasks(variantScope, multiDexClassListTask.orElse(null), dexingType);
} else {
//在此以不使用增量進(jìn)行分析
createDexTasks(variantScope, multiDexClassListTask.orElse(null), dexingType);
}
...
}
4.createDexTasks
private void createDexTasks(
@NonNull VariantScope variantScope,
@Nullable TransformTask multiDexClassListTask,
@NonNull DexingType dexingType) {
TransformManager transformManager = variantScope.getTransformManager();
AndroidBuilder androidBuilder = variantScope.getGlobalScope().getAndroidBuilder();
...
...
if (!preDexEnabled || dexingType != DexingType.NATIVE_MULTIDEX) {
// run if non native multidex or no pre-dexing
DexTransform dexTransform =
new DexTransform(
dexOptions,
dexingType,
preDexEnabled,
project.files(variantScope.getMainDexListFile()),
checkNotNull(androidBuilder.getTargetInfo(), "Target Info not set."),
androidBuilder.getDexByteCodeConverter(),
variantScope.getGlobalScope().getMessageReceiver(),
variantScope.getMinSdkVersion().getFeatureLevel());
Optional<TransformTask> dexTask =
transformManager.addTransform(taskFactory, variantScope, dexTransform);
// need to manually make dex task depend on MultiDexTransform since there's no stream
// consumption making this automatic
dexTask.ifPresent(
t -> {
if (multiDexClassListTask != null) {
t.dependsOn(multiDexClassListTask);
}
variantScope.addColdSwapBuildTask(t);
});
}
}
只截取關(guān)鍵核心代碼,可以看到DexTransform,二話不說,關(guān)鍵步驟肯定是在DexTransform的transform中
5.DexTransform
@Override
public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, IOException, InterruptedException {
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
Preconditions.checkNotNull(outputProvider,
"Missing output object for transform " + getName());
if (!dexOptions.getKeepRuntimeAnnotatedClasses() && mainDexListFile == null) {
logger.info("DexOptions.keepRuntimeAnnotatedClasses has no affect in native multidex.");
}
ProcessOutputHandler outputHandler =
new ParsingProcessOutputHandler(
new ToolOutputParser(new DexParser(), Message.Kind.ERROR, logger),
new ToolOutputParser(new DexParser(), logger),
messageReceiver);
outputProvider.deleteAll();
try {
// these are either classes that should be converted directly to DEX, or DEX(s) to merge
Collection<File> transformInputs =
TransformInputUtil.getAllFiles(transformInvocation.getInputs());
File outputDir =
outputProvider.getContentLocation(
"main",
getOutputTypes(),
TransformManager.SCOPE_FULL_PROJECT,
Format.DIRECTORY);
// this deletes and creates the dir for the output
FileUtils.cleanOutputDir(outputDir);
File mainDexList = null;
if (mainDexListFile != null && dexingType == DexingType.LEGACY_MULTIDEX) {
mainDexList = mainDexListFile.getSingleFile();
}
dexByteCodeConverter.convertByteCode(
transformInputs,
outputDir,
dexingType.isMultiDex(),
mainDexList,
dexOptions,
outputHandler,
minSdkVersion);
} catch (Exception e) {
throw new TransformException(e);
}
}
接著 dexByteCodeConverter.convertByteCode會(huì)執(zhí)行runDexer方法
public void runDexer(
@NonNull final DexProcessBuilder builder,
@NonNull final DexOptions dexOptions,
@NonNull final ProcessOutputHandler processOutputHandler)
throws ProcessException, IOException, InterruptedException {
initDexExecutorService(dexOptions);
if (shouldDexInProcess(dexOptions)) {
dexInProcess(builder, dexOptions, processOutputHandler);
} else {
dexOutOfProcess(builder, dexOptions, processOutputHandler);
}
}
其中,dexInProcess會(huì)在當(dāng)前進(jìn)程開啟一個(gè)固定數(shù)量為4的線程池
將class轉(zhuǎn)成dex,具體操作在Main.runMultiDex()中。
此外,dexOutOfProcess則調(diào)用系統(tǒng)提供的dx工具進(jìn)行轉(zhuǎn)化。
至此,MultiDex過程全部捋清了