android multidex 3个dex,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 customTransforms = extension.getTransforms();

List> customTransformsDependencies = extension.getTransformsDependencies();

for (int i = 0, count = customTransforms.size(); i < count; i++) {

Transform transform = customTransforms.get(i);

List 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 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> 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 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 mainDexClasses = callDx(

_allClassesJarFile,

variantScope.getProguardComponentsJarFile());

if (userMainDexKeepFile != null) {

mainDexClasses = ImmutableSet.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 callDx(File allClassesJarFile, File jarOfRoots) throws ProcessException {

EnumSet 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 createMainDexList(

@NonNull File allClassesJarFile,

@NonNull File jarOfRoots,

@NonNull EnumSet 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 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 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 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过程全部捋清了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值