Android MultiDex中一个疑问
Android MultiDex
使用过MultiDex都知道,AndroidStudio会在编译过程中划分多个dex,如class.dex,class2.dex。。。
这里有一个问题,主dex是如何划分的?
在构建完成后会在会生成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);
}
这个方法的作用是在编译过程中创建即将完成构建的task,从编译的.class文件转换成.dex文件,再加上额外的任务例如:proguard
这里面可以添加的自定义transform,如可以添加一个修改字节码的transform在编译过程中进行插桩的操作(具体不在这里详述了)
接着添加android自己的transform,例如:JavaCodeShrinkerTransform,ResourcesShrinkerTransform,
MainDexListTransform
最后再执行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时为DexingType.LEGACY_MULTIDEX,大于21时为:DexingType.NATIVE_MULTIDEX
这是因为大于等于21时,即android 5.0运行的是ART,ART默认会在内部进行multidex的操作。
当我们在 gradle 中将 multiDexEnabled 设为 true 后,编译 app 的过程中 Terminal 会多出一行: :app:transformClassesWithMultidexlistForDebug
显然 MultiDex 相关操作也是通过 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);
}
}
代码少,逻辑简单,可以猜出个大概来,通过proguard删除不必要的代码,然后执行computeList方法
-
MainDexListTransform.getByInputType(invocation)先将input中的DirectoryInputs和jarInput组合成单一集合,然后根据input的scope类型是否为PROVIDED_ONLY分离成ProguardInput.INPUT_JAR 和 ProguardInput.LIBRARY_JAR和对应的files存进map中。
-
input是个啥?Iterables.getOnlyElement拿到的是第一个INPUT_JAR,而这个对应的是DirectoryInputs,而DirectoryInputs又对应的app模块中的类。
所以我们大胆猜测下,computeList()生成了maindexlist.txt,并且是以app模块里的类加上它所依赖的类进行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的内容加进来
applyConfigurationFile(manifestKeepListProguardFile);
if (userMainDexKeepProguard != null) {
//如果用户设置了userMainDexKeep 也对其进行keep操作
applyConfigurationFile(userMainDexKeepProguard);
}
//multidex默认进行的keep
MainDexListTransform.getPlatformRules().forEach(this::keep);
//把tool目录下的shrinkedAndroid.jar,input 和 libraryJars加进classpath中
libraryJar(findShrinkedAndroidJar());
libraryJars.forEach(this::libraryJar);
inJar(input, null);
// 设置output,即中间产物中的componentClasses.jar
outJar(variantScope.getProguardComponentsJarFile());
printconfiguration(configFileOut);
// 执行proguard
runProguard();
}
当执行完shrinkWithProguard之后,接着执行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,参数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最终会调用project.javaexec执行一个外部的java进程(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的构造函数中拿到传入rootJar和pathString,然后构造了mainListBuilder
主要是调用了 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);
}
}
根据path找到jarOfRoot里面的类名,如果找到则加入到Dependencies,可以看到这个path其实就是我们一开始传进来的input目录,本质上是以app模块构建的jar文件的集合。
接下来我们回到开篇提到TaskManager.createPostCompilationTasks中,看看接下来将要执行的逻辑
public void createPostCompilationTasks(
@NonNull final VariantScope variantScope) {
....
....
//判断是否使用增量构建
if (usingIncrementalDexing(variantScope)) {
createNewDexTasks(variantScope, multiDexClassListTask.orElse(null), dexingType);
} else {
//在此以不使用增量进行分析
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);
});
}
}
只截取关键核心代码,可以看到DexTransform,二话不说,关键步骤肯定是在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会执行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会在当前进程开启一个固定数量为4的线程池
将class转成dex,具体操作在Main.runMultiDex()中。
此外,dexOutOfProcess则调用系统提供的dx工具进行转化。
至此,MultiDex过程全部捋清了