Github: DexKnifePlugin 分析版本:9d33ba9

DexKnifePlugin 是一个简单的将指定使用通配符包名分包到第二个dex中gradle插件。

DexKnifePlugin

一个简单的将指定使用通配符包名分包到第二个dex中gradle插件。

同时支持 android gradle plugin 2.2.0 multidex.

使用

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 中的包名路径的通配符:

    1
    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.

    更多参见: https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/util/PatternFilterable.html

    注意: 如果你要过滤内部类, 使用 $* ,例如: SomeClass$*.class

    其他配置:

    1
    使用 # 进行注释, 当行起始加上 #, 这行配置被禁用.
    
    # 全局过滤, 如果没设置 -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 增加:

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

    1
    multiDexEnabled true

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

源码解析

因为 DexKnifePlugin 这个工程是一个 gradle 的插件,所以在看源码之前得对 gradle 有一些了解。

DexKnifePlugin

直接看 DexKnifePlugin.groovy 这个文件:

1
2
3
4
5
6
public class DexKnifePlugin implements Plugin<Project> {
@Override
void apply(Project project) {

}
}

这里就是该插件, apply 便是该插件的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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 等,参数实在过多