DexKnifePlugin源碼解析

LeonBowie 8年前發布 | 33K 次閱讀 Gradle 項目構建

DexKnifePlugin 是一個簡單的將指定使用通配符包名分包到第二個dex中gradle插件。

DexKnifePlugin

一個簡單的將指定使用通配符包名分包到第二個dex中gradle插件。

同時支持 android gradle plugin 2.2.0 multidex.

使用

  1. 在 project 的 build.gradle 添加依賴:

    buildscript {
            ....
    
        dependencies {
            ....
            classpath 'com.android.tools.build:gradle:2.2.0-beta2'  // or other
            classpath 'com.ceabie.dextools:gradle-dexknife-plugin:1.5.6'
        }
    }
    

    注意,請確保使用的gradle版本和android gradle plugin兼容,否則會出現同步錯誤,例如:Gradle sync failed: Unable to load class ‘com.android.builder.core.EvaluationErrorReporter’.

  2. 在 app module 下創建 dexknife.txt ,并填寫要放到第二個 dex 中的包名路徑的通配符:

    Patterns may include:
    
    '*' to match any number of characters
    '?' to match any single character
    '**' to match any number of directories or files
    Either '.' or '/' may be used in a pattern to separate directories.
    Patterns ending with '.' or '/' will have '**' automatically appended.
    

    注意: 如果你要過濾內部類, 使用 $* ,例如: SomeClass$*.class

    其他配置:

    使用 # 進行注釋, 當行起始加上 #, 這行配置被禁用.
    
    # 全局過濾, 如果沒設置 -filter-suggest 并不會應用到 建議的maindexlist.
    # 如果你想要某個包路徑在maindex中,則使用 -keep 選項,即使他已經在分包的路徑中.
    -keep android.support.v4.view.**
    
    # 這條配置可以指定這個包下類在第二dex中.
    android.support.v?.**
    
    # 使用.class后綴,代表單個類.
    -keep android.support.v7.app.AppCompatDialogFragment.class
    
    # 不包含Android gradle 插件自動生成的miandex列表.
    -donot-use-suggest
    
    # 將 全局過濾配置應用到 建議的maindexlist中, 但 -donot-use-suggest 要關閉.
    -filter-suggest
    
    # 不進行dex分包, 直到 dex 的id數量超過 65536.
    -auto-maindex
    
    # dex 擴展參數, 例如 --set-max-idx-number=50000
    # 如果出現 DexException: Too many classes in --main-dex-list, main dex capacity exceeded,則需要調大數值
    -dex-param --set-max-idx-number=50000
    
    # 顯示miandex的日志.
    -log-mainlist
    
    # 如果你只想過濾 建議的maindexlist, 使用 -suggest-split 和 -suggest-keep.
    # 如果同時啟用 -filter-suggest, 全局過濾會合并到它們中.
    -suggest-split **.MainActivity2.class
    -suggest-keep android.support.multidex.**
    
  3. 在 app module 的 build.grade 增加:

    apply plugin: 'com.ceabie.dexnkife'
    
  4. 最后,在 app module 中設置:

    multiDexEnabled true
    

    注意:要在 defaultConfig 或者 buildTypes中打開 multiDexEnabled true,否則不起作用。

源碼解析

因為 DexKnifePlugin 這個工程是一個 gradle 的插件,所以在看源碼之前得對 gradle 有一些了解。

DexKnifePlugin

直接看 DexKnifePlugin.groovy 這個文件:

public class DexKnifePlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {

    }
}

這里就是該插件, apply 便是該插件的入口:

public class DexKnifePlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        //gradle配置階段完成后調用
        project.afterEvaluate {
            for (variant in project.android.applicationVariants) {
                if (isMultiDexEnabled(variant)) {
                    if (SplitToolsFor130.isCompat(variant)) {//1.3.0版本
                        System.err.println("DexKnife: Compat 1.3.0.");
                        SplitToolsFor130.processSplitDex(project, variant)
                    } else if (SplitToolsFor150.isCompat()) {//1.5.0及之后的版本
                        SplitToolsFor150.processSplitDex(project, variant)
                    } else {
                        System.err.println("DexKnife Error: DexKnife is not compatible your Android gradle plugin.");
                    }
                } else {
                    System.err.println("DexKnife : MultiDexEnabled is false, it's not work.");
                }
            }
        }
    }

    /**
 * 是否開啟的分包
 * @param variant
 * @return
 */
    private static boolean isMultiDexEnabled(variant) {
        def is = variant.buildType.multiDexEnabled
 if (is != null) {
            return is;
        }
        is = variant.mergedFlavor.multiDexEnabled
 if (is != null) {
            return is;
        }
        return false
    }
}

在 gradle 配置階段完成之后,去判斷當前 gradle 插件版本,然后分配去做操作。

我們先來看 1.3.0 的吧

SplitToolsFor130

public class SplitToolsFor130 extends DexSplitTools {

    public static boolean isCompat(Object variant) {
        try {
            if (variant != null) {
                //看看是不是有這個dex的task
                variant.dex
                return true
            }
        } catch (RuntimeException e) {
// e.printStackTrace()
        }
        return false
    }

    public static void processSplitDex(Project project, Object variant) {
        def dex = variant.dex
        if (dex.multiDexEnabled) {//是否開啟的分包
            dex.inputs.file DEX_KNIFE_CFG_TXT
            dex.doFirst {
                //log,記錄當前時間
                startDexKnife()
                //通過解析dexknife.txt得到配置
                DexKnifeConfig dexKnifeConfig = getDexKnifeConfig(project)

                def scope = variant.getVariantData().getScope()
                File mergedJar = scope.jarMergingOutputFile//allclasses.jar
                File mappingFile = variant.mappingFile//mapping.txt
                File andMainDexList = scope.mainDexListFile//maindexlist.txt
                boolean minifyEnabled = variant.buildType.minifyEnabled//build.gradle中的『buildTypes』中的『release』或者『debug』中的minifyEnabled,debug和release的時候默認為false

                if (processMainDexList(project, minifyEnabled, mappingFile, mergedJar, andMainDexList, dexKnifeConfig)) {
                    if (dex.additionalParameters == null) {
                        dex.additionalParameters = []
                    }

                    dex.additionalParameters += '--main-dex-list=maindexlist.txt'
                    dex.additionalParameters += dexKnifeConfig.additionalParameters//其他通過dexknife.txt設置的dx參數
                }
                //log,打印花費時間
                endDexKnife()
            }
        }
    }
}

先找到 dex 這個 task ,然后主要的過程還是在 processMainDexList 中,進行完這個操作之后設置 additionalParameters 參數, processMainDexList 方法在父類 DexSplitTools 中:

DexSplitTools

public class DexSplitTools {

    public static final String DEX_KNIFE_CFG_TXT = "dexknife.txt";

    private static final String DEX_MINIMAL_MAIN_DEX = "--minimal-main-dex";

    private static final String DEX_KNIFE_CFG_DEX_PARAM = "-dex-param";
    private static final String DEX_KNIFE_CFG_SPLIT = "-split";
    private static final String DEX_KNIFE_CFG_KEEP = "-keep";
    private static final String DEX_KNIFE_CFG_AUTO_MAINDEX = "-auto-maindex";
    private static final String DEX_KNIFE_CFG_DONOT_USE_SUGGEST = "-donot-use-suggest";
    private static final String DEX_KNIFE_CFG_LOG_MAIN_DEX = "-log-mainlist";
    private static final String DEX_KNIFE_CFG_FILTER_SUGGEST = "-filter-suggest";
    private static final String DEX_KNIFE_CFG_SUGGEST_SPLIT = "-suggest-split";
    private static final String DEX_KNIFE_CFG_SUGGEST_KEEP = "-suggest-keep";
    private static final String DEX_KNIFE_CFG_LOG_FILTER_SUGGEST = "-log-filter-suggest";

   /**
 * get the config of dex knife
 */
    protected static DexKnifeConfig getDexKnifeConfig(Project project) throws Exception {
        //讀文件
        BufferedReader reader = new BufferedReader(new FileReader(project.file(DEX_KNIFE_CFG_TXT)));
        //申明變量,該變量存儲文件中的信息
        DexKnifeConfig dexKnifeConfig = new DexKnifeConfig();

        String line;
        boolean matchCmd;
        boolean minimalMainDex = true;
        Set<String> addParams = new HashSet<>();

        Set<String> splitToSecond = new HashSet<>();
        Set<String> keepMain = new HashSet<>();
        Set<String> splitSuggest = new HashSet<>();
        Set<String> keepSuggest = new HashSet<>();

        while ((line = reader.readLine()) != null) {
            line = line.trim();
            if (line.length() == 0) {
                continue;
            }

            int rem = line.indexOf('#');//查找注釋的地方
            if (rem != -1) {
                if (rem == 0) {//該段落為注釋
                    continue;
                } else {
                    line = line.substring(0, rem).trim();//獲取出內容
                }
            }

            String cmd = line.toLowerCase();
            matchCmd = true;

            if (DEX_KNIFE_CFG_AUTO_MAINDEX.equals(cmd)) {//-auto-maindex(不進行dex分包, 直到 dex 的id數量超過 65536.)
                minimalMainDex = false;
            } else if (matchCommand(cmd, DEX_KNIFE_CFG_DEX_PARAM)) {//-dex-param(dex 擴展參數, 例如 --set-max-idx-number=50000)
                String param = line.substring(DEX_KNIFE_CFG_DEX_PARAM.length()).trim();
                if (!param.toLowerCase().startsWith("--main-dex-list")) {
                    addParams.add(param);
                }

            } else if (matchCommand(cmd, DEX_KNIFE_CFG_SPLIT)) {//-split
                String sPattern = line.substring(DEX_KNIFE_CFG_SPLIT.length()).trim();
                addClassFilePath(sPattern, splitToSecond);

            } else if (matchCommand(cmd, DEX_KNIFE_CFG_KEEP)) {//-keep
                String sPattern = line.substring(DEX_KNIFE_CFG_KEEP.length()).trim();
                addClassFilePath(sPattern, keepMain);

            } else if (DEX_KNIFE_CFG_DONOT_USE_SUGGEST.equals(cmd)) {//-donot-use-suggest(不包含Android gradle 插件自動生成的miandex列表)
                dexKnifeConfig.useSuggest = false;

            } else if (DEX_KNIFE_CFG_FILTER_SUGGEST.equals(cmd)) {//-filter-suggest(將 全局過濾配置應用到 建議的maindexlist中, 但 -donot-use-suggest 要關閉)
                dexKnifeConfig.filterSuggest = true;

            } else if (DEX_KNIFE_CFG_LOG_MAIN_DEX.equals(cmd)) {//-log-mainlist(顯示miandex的日志)
                dexKnifeConfig.logMainList = true;

            } else if (DEX_KNIFE_CFG_LOG_FILTER_SUGGEST.equals(cmd)) {//-log-filter-suggest(顯示過濾的日志)
                dexKnifeConfig.logFilterSuggest = true;

            } else if (matchCommand(cmd, DEX_KNIFE_CFG_SUGGEST_SPLIT)) {//-suggest-split(要在主dex排除掉的類)
                String sPattern = line.substring(DEX_KNIFE_CFG_SUGGEST_SPLIT.length()).trim();
                addClassFilePath(sPattern, splitSuggest);

            } else if (matchCommand(cmd, DEX_KNIFE_CFG_SUGGEST_KEEP)) {//-suggest-keep(要在主dex保留的類)
                String sPattern = line.substring(DEX_KNIFE_CFG_SUGGEST_KEEP.length()).trim();
                addClassFilePath(sPattern, keepSuggest);

            } else if (!cmd.startsWith("-")) {
                addClassFilePath(line, splitToSecond);
            } else {
                matchCmd = false;
            }

            if (matchCmd) {
                System.out.println("DexKnife Config: " + line);
            }
        }

        reader.close();

        if (minimalMainDex) {//添加--minimal-main-dex參數
            addParams.add(DEX_MINIMAL_MAIN_DEX);
        }

        if (dexKnifeConfig.useSuggest) {
            if (dexKnifeConfig.filterSuggest) {
                splitSuggest.addAll(splitToSecond);
                keepSuggest.addAll(keepMain);
            }

// for (String s : splitSuggest) {
// System.out.println("Suggest: " + s);
// }

            if (!splitSuggest.isEmpty() || !keepSuggest.isEmpty()) {
                dexKnifeConfig.suggestPatternSet = new PatternSet()
                        .exclude(splitSuggest)
                        .include(keepSuggest);
            }
        }


        if (!splitToSecond.isEmpty() || !keepMain.isEmpty()) {
// for (String s : splitToSecond) {
// System.out.println(s);
// }
            dexKnifeConfig.patternSet = new PatternSet()
                    .exclude(splitToSecond)
                    .include(keepMain);
        } else {
            dexKnifeConfig.useSuggest = true;
            System.err.println("DexKnife Warning: NO SET split Or keep path, it will use Suggest!");
        }

        dexKnifeConfig.additionalParameters = addParams;

        return dexKnifeConfig;
    }

    private static boolean matchCommand(String text, String cmd) {
        Pattern pattern = Pattern.compile("^" + cmd + "\\s+");
        return pattern.matcher(text).find();
    }

    /**
 * add the class path to pattern list, and the single class pattern can work.
 */
    private static void addClassFilePath(String classPath, Set<String> patternList) {
        if (classPath != null && classPath.length() > 0) {
            if (classPath.endsWith(CLASS_SUFFIX)) {//以.class結尾
                classPath = classPath.substring(0, classPath.length() - CLASS_SUFFIX.length()).replace('.', '/') + CLASS_SUFFIX;//轉化成路徑形式
            } else {
                classPath = classPath.replace('.', '/');//轉化成路徑形式
            }
            //添加到patternList中
            patternList.add(classPath);
        }
    }

    public static boolean processMainDexList(Project project, boolean minifyEnabled, File mappingFile,
 File jarMergingOutputFile, File andMainDexList,
 DexKnifeConfig dexKnifeConfig) throws Exception {
        //當minifyEnabled為false的時候,那么jarMergingOutputFile必定存在
        //當minifyEnabled為true的時候,那么jarMergingOutputFile可能不存在,因為此時可能打的release包,就不是allclass.jar了
        if (!minifyEnabled && jarMergingOutputFile == null) {
            System.out.println("DexKnife Error: jarMerging is Null! Skip DexKnife. Please report All Gradle Log.");
            return false;
        }
        return genMainDexList(project, minifyEnabled, mappingFile, jarMergingOutputFile, andMainDexList, dexKnifeConfig);
    }

    private static boolean genMainDexList(Project project, boolean minifyEnabled,
 File mappingFile, File jarMergingOutputFile,
 File andMainDexList, DexKnifeConfig dexKnifeConfig) throws Exception {

        System.out.println(":" + project.getName() + ":genMainDexList");

        // get the adt's maindexlist
        HashSet<String> mainCls = null;
        if (dexKnifeConfig.useSuggest) {//使用gradle生成的maindexlist
            PatternSet patternSet = dexKnifeConfig.suggestPatternSet;//-suggest-split和-suggest-keep
            if (dexKnifeConfig.filterSuggest && patternSet == null) {
                patternSet = dexKnifeConfig.patternSet;//-split和-keep
            }
            mainCls = getAdtMainDexClasses(andMainDexList, patternSet, dexKnifeConfig.logFilterSuggest);
            System.out.println("DexKnife: use suggest");
        }

        File keepFile = project.file(MAINDEXLIST_TXT);
        keepFile.delete();

        ArrayList<String> mainClasses = null;
        if (minifyEnabled) {
            System.err.println("DexKnife: From Mapping");
            // get classes from mapping
            mainClasses = getMainClassesFromMapping(mappingFile, dexKnifeConfig.patternSet, mainCls);
        } else {
            System.out.println("DexKnife: From MergedJar: " + jarMergingOutputFile);
            if (jarMergingOutputFile != null) {
                // get classes from merged jar
                mainClasses = getMainClassesFromJar(jarMergingOutputFile, dexKnifeConfig.patternSet, mainCls);
            } else {
                System.err.println("DexKnife: The Merged Jar is not exist! Can't be processed!");
            }
        }

        if (mainClasses != null && mainClasses.size() > 0) {
            BufferedWriter writer = new BufferedWriter(new FileWriter(keepFile));//寫到app module的maindexlist.txt中
            for (String mainClass : mainClasses) {
                writer.write(mainClass);
                writer.newLine();
                if (dexKnifeConfig.logMainList) {
                    System.out.println(mainClass);
                }
            }
            writer.close();
            return true;
        }

        throw new Exception("DexKnife Warning: Main dex is EMPTY ! Check your config and project!");
    }

    private static HashSet<String> getAdtMainDexClasses(File outputDir, PatternSet mainDexPattern, boolean logFilter)
 throws Exception {
        if (outputDir == null || !outputDir.exists()) {
            System.err.println("DexKnife Warning: Android recommand Main dex is no exist, try run again!");
            return null;
        }

        HashSet<String> mainCls = new HashSet<>();
        BufferedReader reader = new BufferedReader(new FileReader(outputDir));

        ClassFileTreeElement treeElement = new ClassFileTreeElement();
        //將mainDexPattern轉成Spec<FileTreeElement>格式
        Spec<FileTreeElement> asSpec = mainDexPattern != null ? getMaindexSpec(mainDexPattern) : null;

        String line, clsPath;
        while ((line = reader.readLine()) != null) {
            line = line.trim();
            int clsPos = line.lastIndexOf(CLASS_SUFFIX);
            if (clsPos != -1) {
                if (asSpec != null) {//設置了過濾的情況
                    clsPath = line.substring(0, clsPos).replace('.', '/') + CLASS_SUFFIX;//轉路徑
                    treeElement.setClassPath(clsPath);//設置路徑
                    boolean satisfiedBy = asSpec.isSatisfiedBy(treeElement);
                    if (!satisfiedBy) {
                        if (logFilter) {
                            System.out.println("DexKnife-Suggest: [Split] " + clsPath);
                        }
                        continue;
                    }
                    if (logFilter) {
                        System.out.println("DexKnife-Suggest: [Keep] " + clsPath);
                    }
                }
                //滿足的加到mainCls中
                mainCls.add(line);
            }
        }
        reader.close();
        if (mainCls.size() == 0) {
            mainCls = null;
        }
        return mainCls;
    }

    private static Spec<FileTreeElement> getMaindexSpec(PatternSet patternSet) {
        Spec<FileTreeElement> maindexSpec = null;

        if (patternSet != null) {
            Spec<FileTreeElement> includeSpec = null;
            Spec<FileTreeElement> excludeSpec = null;

            if (!patternSet.getIncludes().isEmpty()) {
                includeSpec = patternSet.getAsIncludeSpec();
            }

            if (!patternSet.getExcludes().isEmpty()) {
                excludeSpec = patternSet.getAsExcludeSpec();
            }

            if (includeSpec != null && excludeSpec != null) {
                maindexSpec = Specs.or(includeSpec, Specs.not(excludeSpec));
            } else {
                maindexSpec = excludeSpec != null ? Specs.not(excludeSpec) : includeSpec;
            }
        }

        if (maindexSpec == null) {
            maindexSpec = Specs.satisfyNone();
        }

        return maindexSpec;
    }

    /**
 * Gets main classes from mapping.
 *
 * @param mapping the mapping file
 * @param mainDexPattern the main dex pattern
 * @param mainCls the main cls
 * @return the main classes from mapping
 * @throws Exception the exception
 * @author ceabie
 */
    private static ArrayList<String> getMainClassesFromMapping(
 File mapping,
 PatternSet mainDexPattern,
 HashSet<String> mainCls) throws Exception {

        String line;
        ArrayList<String> mainDexList = new ArrayList<>();
        BufferedReader reader = new BufferedReader(new FileReader(mapping));

        ClassFileTreeElement treeElement = new ClassFileTreeElement();
        Spec<FileTreeElement> asSpec = getMaindexSpec(mainDexPattern);

        while ((line = reader.readLine()) != null) {
            line = line.trim();

            if (line.endsWith(":")) {
                int flagPos = line.indexOf(MAPPING_FLAG);//找『 -> 』
                if (flagPos != -1) {
                    String sOrg = line.substring(0, flagPos).replace('.', '/') + CLASS_SUFFIX;//獲取前面的,是混淆前的
                    treeElement.setClassPath(sOrg);//設置路徑

                    if (asSpec.isSatisfiedBy(treeElement)
                            || (mainCls != null && mainCls.contains(sOrg))) {
                        String sMap = line.substring(flagPos + MAPPING_FLAG_LEN, line.length() - 1).replace('.', '/') + CLASS_SUFFIX;//得到混淆后的
                        mainDexList.add(sMap);//添加到mainDexList中
                    }
                }
            }
        }
        reader.close();
        return mainDexList;
    }

    private static ArrayList<String> getMainClassesFromJar(
 File jarMergingOutputFile, PatternSet mainDexPattern, HashSet<String> mainCls) throws Exception {
        ZipFile clsFile = new ZipFile(jarMergingOutputFile);//allclass.jar
        Spec<FileTreeElement> asSpec = getMaindexSpec(mainDexPattern);
        ClassFileTreeElement treeElement = new ClassFileTreeElement();

        ArrayList<String> mainDexList = new ArrayList<>();
        Enumeration<? extends ZipEntry> entries = clsFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            String entryName = entry.getName();//得到全名稱

            if (entryName.endsWith(CLASS_SUFFIX)) {
                treeElement.setClassPath(entryName);
                if (asSpec.isSatisfiedBy(treeElement)
                        || (mainCls != null && mainCls.contains(entryName))) {
                    mainDexList.add(entryName);//寫到mainDexList中
                }
            }
        }
        clsFile.close();
        return mainDexList;
    }
}

先通過 getDexKnifeConfig() 來得到配置,然后通過 genMainDexList() 將配置中設置的一些類寫入到 maindexlist.txt 中。這里需要注意一下 buildType 的 minifyEnabled,一般在 debug 的時候都沒有設置這個參數,默認為 false,當 release 的時候設置該參數為 true,那么會進行混淆工作,所以這里如果該參數為 true 的話直接取讀的 mapping 文件。

SplitToolsFor150

public class SplitToolsFor150 extends DexSplitTools {

    public static boolean isCompat() {
// if (getAndroidPluginVersion() < 200) {
// return true;
// }

        return true;
    }

    public static void processSplitDex(Project project, ApplicationVariant variant) {
        //instantRun開啟的話就跳過了
        if (isInInstantRunMode(variant)) {
            System.err.println("DexKnife: Instant Run mode, DexKnife is auto disabled!")
            return
        }

        TransformTask dexTask
// TransformTask proGuardTask
        TransformTask jarMergingTask

        String name = variant.name.capitalize()//Debug或者Release
        boolean minifyEnabled = variant.buildType.minifyEnabled

        // find the task we want to process
        project.tasks.matching {
            ((it instanceof TransformTask) && it.name.endsWith(name)) // TransformTask
        }.each { TransformTask theTask ->
            Transform transform = theTask.transform
            String transformName = transform.name
// if (minifyEnabled && "proguard".equals(transformName)) { // ProGuardTransform
// proGuardTask = theTask
// } else
            if ("jarMerging".equalsIgnoreCase(transformName)) {//jarMerging
                jarMergingTask = theTask//transformClassWithJarMerging的時候執行
            } else if ("dex".equalsIgnoreCase(transformName)) { // DexTransform
                dexTask = theTask//transformClassWithDex的時候執行
            }
        }

        if (dexTask != null && ((DexTransform) dexTask.transform).multiDex) {
            dexTask.inputs.file DEX_KNIFE_CFG_TXT

            dexTask.doFirst {
                //記錄開始時間
                startDexKnife()

                File mergedJar = null
                File mappingFile = variant.mappingFile//mapping.txt
                DexTransform dexTransform = it.transform
                File fileAdtMainList = dexTransform.mainDexListFile//maindexlist文件

                println("DexKnife Adt Main: " + fileAdtMainList)

                DexKnifeConfig dexKnifeConfig = getDexKnifeConfig(project)//獲取配置

                // 非混淆的,從合并后的jar文件中提起maindexlist;
                // 混淆的,直接從mapping文件中提取
                if (minifyEnabled) {
                    println("DexKnife-From Mapping: " + mappingFile)
                } else {
                    if (jarMergingTask != null) {
                        Transform transform = jarMergingTask.transform
                        def outputProvider = jarMergingTask.outputStream.asOutput()
                        mergedJar = outputProvider.getContentLocation("combined",
                                transform.getOutputTypes(),
                                transform.getScopes(), Format.JAR)//得到jar
                    }


                    println("DexKnife-From MergedJar: " + mergedJar)
                }

                //與130一樣
                if (processMainDexList(project, minifyEnabled, mappingFile, mergedJar,
                        fileAdtMainList, dexKnifeConfig)) {

                    //得到version
                    int version = getAndroidPluginVersion(getAndroidGradlePluginVersion())
                    println("DexKnife: AndroidPluginVersion: " + version)

                    // after 2.2.0, it can additionalParameters, but it is a copy in task
// if (version >= 220) {
// DexOptions dexOptions = project.android.dexOptions;
// InjectAndroidBuilder.mergeParams(dexOptions.getAdditionalParameters(),
// dexKnifeConfig.additionalParameters)
// }

                    // 替換 AndroidBuilder
                    InjectAndroidBuilder.proxyAndroidBuilder(dexTransform,
                            dexKnifeConfig.additionalParameters)

                    // 替換這個文件
                    fileAdtMainList.delete()
                    project.copy {
                        from 'maindexlist.txt'
                        into fileAdtMainList.parentFile
                    }
                }
                //記錄并打印執行時間
                endDexKnife()
            }
        }
    }

    private static boolean isInInstantRunMode(Object variant) {
        try {
            def scope = variant.getVariantData().getScope()
            InstantRunBuildContext instantRunBuildContext = scope.getInstantRunBuildContext()
            return instantRunBuildContext.isInInstantRunMode()
        } catch (Throwable e) {
        }

        return false
    }
}

主要的 processMainDexList 與 130 的一樣,這里就不多說了,再來看看是怎么通過 maindexlist.txt 來實現分包的:

InjectAndroidBuilder

public class InjectAndroidBuilder extends AndroidBuilder {

    /**
 * addParams 就是130的dex的那些分包參數
 * @param transform
 * @param addParams
 */
    public static void proxyAndroidBuilder(DexTransform transform, Collection<String> addParams) {
        if (addParams != null && addParams.size() > 0) {
            //反射,替換成自己的
            accessibleField(DexTransform.class, "androidBuilder")
                    .set(transform, getProxyAndroidBuilder(transform.androidBuilder, addParams))
        }
    }

    /**
 * new一個自己的出來
 * @param orgAndroidBuilder
 * @param addParams
 * @return
 */
    private static AndroidBuilder getProxyAndroidBuilder(AndroidBuilder orgAndroidBuilder,
 Collection<String> addParams) {
        InjectAndroidBuilder myAndroidBuilder = new InjectAndroidBuilder(
                orgAndroidBuilder.mProjectId,
                orgAndroidBuilder.mCreatedBy,
                orgAndroidBuilder.getProcessExecutor(),
                orgAndroidBuilder.mJavaProcessExecutor,
                orgAndroidBuilder.getErrorReporter(),
                orgAndroidBuilder.getLogger(),
                orgAndroidBuilder.mVerboseExec)

        // if >= 2.2.0
        def to = myAndroidBuilder.respondsTo("setTargetInfo", TargetInfo.class)
        //分版本適配
        if (to.size() > 0) {
            myAndroidBuilder.setTargetInfo(orgAndroidBuilder.getTargetInfo())
            myAndroidBuilder.setSdkInfo(orgAndroidBuilder.getSdkInfo())
            myAndroidBuilder.setLibraryRequests(orgAndroidBuilder.mLibraryRequests)
        } else {
            myAndroidBuilder.setTargetInfo(
                    orgAndroidBuilder.getSdkInfo(),
                    orgAndroidBuilder.getTargetInfo(),
                    orgAndroidBuilder.mLibraryRequests)
        }
        //將參數傳入
        myAndroidBuilder.mAddParams = addParams
        myAndroidBuilder.mAndroidBuilder = orgAndroidBuilder
// myAndroidBuilder.mBootClasspathFiltered = orgAndroidBuilder.mBootClasspathFiltered
// myAndroidBuilder.mBootClasspathAll = orgAndroidBuilder.mBootClasspathAll

        return myAndroidBuilder
    }

    @CompileStatic
    private static Field accessibleField(Class cls, String field) {
        Field f = cls.getDeclaredField(field)
        f.setAccessible(true)
        return f
    }

    Collection<String> mAddParams;
    AndroidBuilder mAndroidBuilder;

    public InjectAndroidBuilder(String projectId,
 String createdBy,
 ProcessExecutor processExecutor,
 JavaProcessExecutor javaProcessExecutor,
 ErrorReporter errorReporter,
 ILogger logger,
 boolean verboseExec) {
        super(projectId, createdBy, processExecutor, javaProcessExecutor, errorReporter, logger, verboseExec)
    }

// @Override // for < 2.2.0
    public void convertByteCode(Collection<File> inputs,
 File outDexFolder,
 boolean multidex,
 File mainDexList,
 DexOptions dexOptions,
 List<String> additionalParameters,
 boolean incremental,
 boolean optimize,
 ProcessOutputHandler processOutputHandler)
 throws IOException, InterruptedException, ProcessException {

        println("DexKnife: convertByteCode before 2.2.0")
        if (mAddParams != null) {
            if (additionalParameters == null) {
                additionalParameters = new ArrayList<>()
            }
            //將參數添加到additionalParameters中
            mergeParams(additionalParameters, mAddParams)
        }

        // groovy call super has bug
        mAndroidBuilder.convertByteCode(inputs, outDexFolder, multidex, mainDexList, dexOptions,
                additionalParameters, incremental, optimize, processOutputHandler);
    }

// @Override for >= 2.2.0
    public void convertByteCode(Collection<File> inputs,
 File outDexFolder,
 boolean multidex,
 File mainDexList,
 final DexOptions dexOptions,
 boolean optimize,
 ProcessOutputHandler processOutputHandler)
 throws IOException, InterruptedException, ProcessException {

        println("DexKnife:convertByteCode after 2.2.0")

        DexOptions dexOptionsProxy = dexOptions

 if (mAddParams != null) {
            List<String> additionalParameters = dexOptions.getAdditionalParameters()
            if (additionalParameters == null) {
                additionalParameters = new ArrayList<>()
            }

            mergeParams(additionalParameters, mAddParams)
        }

        mAndroidBuilder.convertByteCode(inputs, outDexFolder, multidex, mainDexList, dexOptionsProxy,
                optimize, processOutputHandler);
    }

    @CompileStatic
    @Override
    List<File> getBootClasspath(boolean includeOptionalLibraries) {
        return mAndroidBuilder.getBootClasspath(includeOptionalLibraries)
    }

    @CompileStatic
    @Override
    List<String> getBootClasspathAsStrings(boolean includeOptionalLibraries) {
        return mAndroidBuilder.getBootClasspathAsStrings(includeOptionalLibraries)
    }


    @CompileStatic
    static void mergeParams(List<String> additionalParameters, Collection<String> addParams) {
        List<String> mergeParam = new ArrayList<>()
        for (String param : addParams) {
            if (!additionalParameters.contains(param)) {
                mergeParam.add(param)
            }
        }

        if (mergeParam.size() > 0) {
            additionalParameters.addAll(mergeParam)
        }
    }
}

通過反射將 AndroidBuilder 替換成自己的,將分包的參數加上,最終是通過 AndroidBuilder#convertByteCode() 寫進去的。

總結

  • 通過插件的方式很贊,使用的人不需要做太多配置,只需要將插件設置進來便可
  • 通過 dexknife.txt 方式來配置也很贊
  • 1.5.0 之后的方式很贊
  • -split 和 -keep 以及 -suggest-split 和 -suggest-keep 等,參數實在過多

 

來自:http://yydcdut.com/2016/09/25/dexknifeplugin-analyse/

 

 本文由用戶 LeonBowie 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!