DexKnifePlugin源碼解析
DexKnifePlugin 是一個簡單的將指定使用通配符包名分包到第二個dex中gradle插件。
DexKnifePlugin
一個簡單的將指定使用通配符包名分包到第二個dex中gradle插件。
同時支持 android gradle plugin 2.2.0 multidex.
使用
-
在 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’.
-
在 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.**
-
在 app module 的 build.grade 增加:
apply plugin: 'com.ceabie.dexnkife'
-
最后,在 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/