美文网首页
Android MultiDex中一个疑问

Android MultiDex中一个疑问

作者: zhllp | 来源:发表于2019-03-19 19:01 被阅读0次

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方法

  1. MainDexListTransform.getByInputType(invocation)先将input中的DirectoryInputs和jarInput组合成单一集合,然后根据input的scope类型是否为PROVIDED_ONLY分离成ProguardInput.INPUT_JAR 和 ProguardInput.LIBRARY_JAR和对应的files存进map中。

  2. 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过程全部捋清了

相关文章

网友评论

      本文标题:Android MultiDex中一个疑问

      本文链接:https://www.haomeiwen.com/subject/riwnmqtx.html