稀土掘金地址:https://juejin.cn/post/7276412839585415187
前言
對(duì)matrix框架的分析衬衬,第一篇文章 Android性能優(yōu)化系列-騰訊matrix-IO監(jiān)控-IOCanaryPlugin 用來(lái)分析io監(jiān)控與優(yōu)化的方向奄喂。接下來(lái)準(zhǔn)備從卡頓優(yōu)化入手,卡頓是項(xiàng)目中最容易影響用戶體驗(yàn)的一個(gè)問(wèn)題剂府,所以也是至關(guān)重要的一個(gè)優(yōu)化點(diǎn)√甓埽卡頓優(yōu)化功能對(duì)應(yīng)于matrix中的matrix-trace-canary模塊腺占,包含了多方面的卡頓監(jiān)控,如啟動(dòng)監(jiān)控痒谴、慢方法監(jiān)控衰伯、Anr監(jiān)控等等,而這些都依賴于matrix底層的一個(gè)基礎(chǔ)能力-字節(jié)碼插樁积蔚,所以在進(jìn)行卡頓優(yōu)化的代碼分析前意鲸,有必要對(duì)這個(gè)基礎(chǔ)能力的實(shí)現(xiàn)有一個(gè)直觀的了解。
插件入口
在源碼中找到matrix-gradle-plugin這個(gè)模塊库倘,找到插件的入口临扮。
resources/META-INF/gradle-plugins/com.tencent.matrix-plugin.properties
implementation-class=com.tencent.matrix.plugin.MatrixPlugin
搜索MatrixPlugin,開始分析源碼教翩,今天的分析著重于matrix插樁原理杆勇,而不關(guān)注gradle插件的實(shí)現(xiàn),所以有些gradle插件相關(guān)的內(nèi)容會(huì)一筆帶過(guò)饱亿,讀者可以自行搜索相關(guān)內(nèi)容蚜退。
MatrixPlugin-apply
apply是插件執(zhí)行的入口,在這里會(huì)讀取到build.gradle文件中的配置彪笼,配置內(nèi)容包含兩個(gè)方面钻注,一是trace,一是removeUnusedResources, 本篇只分析trace任務(wù)配猫,removeUnusedResources會(huì)在后邊的文章中進(jìn)行分析幅恋。
override fun apply(project: Project) {
...
//進(jìn)入MatrixTasksManager
MatrixTasksManager().createMatrixTasks(
project.extensions.getByName("android") as AppExtension,
project,
traceExtension,
removeUnusedResourcesExtension
)
}
traceExtension和removeUnusedResourcesExtension對(duì)應(yīng)的正是build.gradle中的配置。
matrix {
trace {
}
removeUnusedResources {
}
}
createMatrixTasks
createMatrixTraceTask和createRemoveUnusedResourcesTask是插件的兩個(gè)核心點(diǎn)泵肄。
fun createMatrixTasks(android: AppExtension,
project: Project,
traceExtension: MatrixTraceExtension,
removeUnusedResourcesExtension: MatrixRemoveUnusedResExtension) {
createMatrixTraceTask(android, project, traceExtension)
createRemoveUnusedResourcesTask(android, project, traceExtension)
}
createMatrixTraceTask
方法中針對(duì)不同gradle版本創(chuàng)建了兩個(gè)不同的transform:
- MatrixTraceTransform
- MatrixTraceLegacyTransform
最終這兩個(gè)transform會(huì)匯集到一個(gè)入口捆交,那就是MatrixTrace.
MatrixTrace(
ignoreMethodMapFilePath = config.ignoreMethodMapFilePath,
methodMapFilePath = config.methodMapFilePath,
baseMethodMapPath = config.baseMethodMapPath,
blockListFilePath = config.blockListFilePath,
mappingDir = config.mappingDir,
project = project
).doTransform(
classInputs = inputFiles,
changedFiles = changedFiles,
isIncremental = isIncremental,
skipCheckClass = config.skipCheckClass,
traceClassDirectoryOutput = outputDirectory,
inputToOutput = inputToOutput,
legacyReplaceChangedFile = null,
legacyReplaceFile = null,
uniqueOutputName = true
)
來(lái)看doTransform方法淑翼,官方注釋很清楚,分為關(guān)鍵的三步品追,接下來(lái)我們一步一步來(lái)讀一下代碼玄括。
fun doTransform() {
...
/**
* step 1
*/
futures.add(executor.submit(ParseMappingTask(
mappingCollector, collectedMethodMap, methodId, config)))
for (file in classInputs) {
if (file.isDirectory) {
futures.add(executor.submit(CollectDirectoryInputTask()))
} else {
futures.add(executor.submit(CollectJarInputTask()))
}
}
/**
* step 2
*/
val methodCollector = MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap)
methodCollector.collect(dirInputOutMap.keys, jarInputOutMap.keys)
/**
* step 3
*/
val methodTracer = MethodTracer(executor, mappingCollector, config, methodCollector.collectedMethodMap, methodCollector.collectedClassExtendMap)
methodTracer.trace(dirInputOutMap, jarInputOutMap, traceClassLoader, skipCheckClass)
}
第一步
包含三項(xiàng)任務(wù)
ParseMappingTask
這個(gè)任務(wù)是用來(lái)解析mapping.txt文件的,通過(guò)調(diào)用一個(gè)名為MappingReader的類去解析文件肉瓦,解析的內(nèi)容又可以分為類解析和類成員解析遭京。mapping文件解析之后,我們就獲得了混淆前和混淆后類的映射關(guān)系以及混淆前和混淆后方法的映射關(guān)系泞莉。
val mappingFile = File(config.mappingDir, "mapping.txt")
if (mappingFile.isFile) {
val mappingReader = MappingReader(mappingFile)
mappingReader.read(mappingCollector)
}
mappingReader.read()
if (!line.startsWith("#")) {
// a class mapping
if (line.endsWith(":")) {
className = parseClassMapping(line, mappingProcessor);
} else if (className != null) {
// a class member mapping
parseClassMemberMapping(className, line, mappingProcessor);
}
}
parseClassMapping
解析出混淆前的類名和混淆后的類名哪雕,將映射關(guān)系保存在MappingProcessor(實(shí)現(xiàn)類MappingCollector)映射表中,對(duì)應(yīng)于下邊的三個(gè)map戒财。
private String parseClassMapping(String line, MappingProcessor mappingProcessor) {
...
boolean ret = mappingProcessor.processClassMapping(className, newClassName);
}
這兩個(gè)集合中的className可能包含包名
HashMap<String, String> mObfuscatedRawClassMap
key | value |
---|---|
混淆后的類名 | 原類名 |
HashMap<String, String> mRawObfuscatedClassMap
key | value |
---|---|
原類名 | 混淆后的類名 |
HashMap<String, String> mRawObfuscatedPackageMap
key | value |
---|---|
包名 | 混淆后的包名 |
parseClassMemberMapping
邏輯也是比較直接的热监,解析出每個(gè)類下的方法方法信息, 最終還是保存在了MappingProcessor中的映射表中,對(duì)應(yīng)于下邊的兩個(gè)map饮寞。
private void parseClassMemberMapping(String className, String line, MappingProcessor mappingProcessor) {
...
mappingProcessor.processMethodMapping(className, type, name, arguments, newClassName, newName);
}
Map<String, Map<String, Set<MethodInfo>>> mObfuscatedClassMethodMap
這個(gè)map用來(lái)記錄一個(gè)類中所有的方法信息
key | value |
---|---|
混淆后的類名為key | 一個(gè)以混淆后的方法名為key, 以MethodInfo集合為value的map(注意:MethodInfo中的類名方法名都是未混淆的) |
Map<String, Map<String, Set<MethodInfo>>> mOriginalClassMethodMap
key | value |
---|---|
未混淆的類名為key | 一個(gè)未混淆的方法名為key, 以MethodInfo集合為value的map(注意:MethodInfo中的類名方法名都是混淆后的) |
下面兩個(gè)任務(wù)針對(duì)directory和jar類型的文件分別處理
CollectDirectoryInputTask
此任務(wù)的輸入是一個(gè)map映射表, 記錄輸入到數(shù)據(jù)的映射關(guān)系孝扛,對(duì)于支持增量編譯的情況下,記錄的是所有發(fā)生改變的文件的映射幽崩,未改變的文件不做記錄苦始。
resultOfDirInputToOut: MutableMap<File, File>
CollectJarInputTask
這個(gè)類的作用也類似,只不過(guò)操作對(duì)象是一個(gè)jar包慌申,同樣輸入一個(gè)map集合記錄映射關(guān)系陌选。
第二步
MethodCollector
MethodCollector是用來(lái)收集所有需要被trace的方法的,它會(huì)過(guò)濾掉一些沒(méi)有trace價(jià)值的方法蹄溉,如構(gòu)造方法咨油、空方法,get柒爵、set方法等役电,還有一些指定不需要trace的類或者指定包下的類也會(huì)被過(guò)濾掉。
注意棉胀,methodId是一個(gè)比較關(guān)鍵的點(diǎn)法瑟,是一個(gè)從0開始遞增的值,每一個(gè)值表示一個(gè)方法名唁奢,用數(shù)字表示方法名霎挟,并記錄數(shù)字和方法名映射關(guān)系,用于后期分析時(shí)解析麻掸,是matrix內(nèi)很巧妙的一個(gè)做法酥夭,感興趣可以深入研究一下,這里不過(guò)多的解釋了。
val methodCollector = MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap)
methodCollector.collect(dirInputOutMap.keys, jarInputOutMap.keys)
collect方法
- 從文件夾中遍歷得到所有文件采郎,針對(duì)每個(gè)文件執(zhí)行CollectSrcTask任務(wù)
- 遍歷所有jar,針對(duì)每個(gè)jar執(zhí)行CollectJarTask
- 上邊兩個(gè)任務(wù)執(zhí)行完成后千所,再執(zhí)行saveIgnoreCollectedMethod和saveCollectedMethod,等待全部完成后返回蒜埋。
public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException {
...
futures.add(executor.submit(new CollectSrcTask(classFile)));
...
futures.add(executor.submit(new CollectJarTask(jarFile)));
...
saveIgnoreCollectedMethod(mappingCollector);
...
saveCollectedMethod(mappingCollector);
}
CollectSrcTask该贾、CollectJarTask
相同的邏輯别凤,只不過(guò)一個(gè)針對(duì)class杏节,一個(gè)針對(duì)jar包须鼎。
這里使用了Asm(一個(gè)字節(jié)碼插樁的庫(kù)低剔,自行百度了解其用法亲善,這里默認(rèn)讀者具備了相關(guān)知識(shí))向臀,所以關(guān)鍵操作在TraceClassAdapter中工三。
InputStream is = new FileInputStream(classFile);
ClassReader classReader = new ClassReader(is);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor = new TraceClassAdapter(AgpCompat.getAsmApi(), classWriter);
classReader.accept(visitor, 0);
TraceClassAdapter
private class TraceClassAdapter extends ClassVisitor {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.className = name;
//如果是接口或者抽象類犯建,則isABSClass為true
if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
this.isABSClass = true;
}
//又一個(gè)map記錄類和父類
collectedClassExtendMap.put(className, superName);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
//抽象類和接口被繞過(guò)讲冠,不做處理
if (isABSClass) {
return super.visitMethod(access, name, desc, signature, exceptions);
} else {
//這里是記錄類中是否含有onWindowFocusChanged,這是Activity的一個(gè)回調(diào)适瓦,matrix將此用于頁(yè)面可見的記錄時(shí)機(jī)竿开。
if (!hasWindowFocusMethod) {
hasWindowFocusMethod = isWindowFocusChangeMethod(name, desc);
}
//進(jìn)入CollectMethodNode中
return new CollectMethodNode(className, access, name, desc, signature, exceptions);
}
}
}
CollectMethodNode
只看它的visitEnd方法
@Override
public void visitEnd() {
super.visitEnd();
TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
if ("<init>".equals(name)) {
isConstructor = true;
}
//判斷方法是否需要插樁,哪些方法可以插樁呢玻熙,看下邊
boolean isNeedTrace = isNeedTrace(configuration, traceMethod.className, mappingCollector);
// 空方法否彩,get、set方法嗦随,single method都會(huì)被過(guò)濾掉列荔,并記錄數(shù)量,加入map中存儲(chǔ)
if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
&& isNeedTrace) {
ignoreCount.incrementAndGet();
collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
return;
}
if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
//需要插樁的方法記錄到collectedMethodMap中
traceMethod.id = methodId.incrementAndGet();
collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
incrementCount.incrementAndGet();
} else if (!isNeedTrace && !collectedIgnoreMethodMap.containsKey(traceMethod.className)) {
ignoreCount.incrementAndGet();
//不需要插樁的方法記錄到collectedIgnoreMethodMap中
collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
}
}
isNeedTrace
public static boolean isNeedTrace(Configuration configuration, String clsName, MappingCollector mappingCollector) {
boolean isNeed = true;
//指定不需要插樁的方法枚尼,過(guò)濾掉
if (configuration.blockSet.contains(clsName)) {
isNeed = false;
} else {
if (null != mappingCollector) {
//從上邊的分析贴浙,我們知道,這是從mObfuscatedRawClassMap中獲取未混淆的方法名
clsName = mappingCollector.originalClassName(clsName, clsName);
}
clsName = clsName.replaceAll("/", ".");
for (String packageName : configuration.blockSet) {
//指定包名下的類也被過(guò)濾掉
if (clsName.startsWith(packageName.replaceAll("/", "."))) {
isNeed = false;
break;
}
}
}
return isNeed;
}
最終還是產(chǎn)出了兩個(gè)map映射表:collectedMethodMap署恍、collectedIgnoreMethodMap崎溃,結(jié)構(gòu)相同,但是代表的含義不同锭汛,collectedMethodMap存儲(chǔ)需要被插樁的方法笨奠,collectedIgnoreMethodMap存儲(chǔ)不需要插樁的方法。
key | value |
---|---|
方法名 | TraceMethod |
此時(shí)再回頭看看MethodCollector這個(gè)類的確像它的命名一樣唤殴,只是方法的收集者般婆,并不做插樁,而真正執(zhí)行插樁的朵逝,是MethodTracer蔚袍。
saveIgnoreCollectedMethod
邏輯很簡(jiǎn)單,就是將上邊收集到的collectedIgnoreMethodMap中記錄的不需要插樁的方法信息寫入到指定的ignoreMethodMapFilePath文件中,方便查找啤咽,不屬于本次分析的核心晋辆,不做過(guò)多解釋。
private void saveIgnoreCollectedMethod(MappingCollector mappingCollector) {
...
}
saveCollectedMethod
和saveIgnoreCollectedMethod方法類似宇整,只不過(guò)保存的是collectedMethodMap中的方法信息瓶佳,保存在配置的methodMapFilePath文件路徑中。有些特殊的是鳞青,它主動(dòng)將Handler的dispatchMessage也加進(jìn)去了霸饲,目的是什么,暫不看了臂拓。
private void saveCollectedMethod(MappingCollector mappingCollector) {
...
TraceMethod extra = TraceMethod.create(TraceBuildConstants.METHOD_ID_DISPATCH, Opcodes.ACC_PUBLIC, "android.os.Handler",
"dispatchMessage", "(Landroid.os.Message;)V");
collectedMethodMap.put(extra.getMethodName(), extra);
...
}
第三步
接下來(lái)才是整個(gè)插件的重中之重厚脉,真正要開始插樁了
MethodTracer
trace
public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList, ClassLoader classLoader, boolean ignoreCheckClass) throws ExecutionException, InterruptedException {
...
traceMethodFromSrc(srcFolderList, futures, classLoader, ignoreCheckClass);
traceMethodFromJar(dependencyJarList, futures, classLoader, ignoreCheckClass);
...
}
traceMethodFromSrc
只保留核心代碼
private void innerTraceMethodFromSrc(File input, File output, ClassLoader classLoader, boolean ignoreCheckClass) {
...
is = new FileInputStream(classFile);
ClassReader classReader = new ClassReader(is);
ClassWriter classWriter = new TraceClassWriter(ClassWriter.COMPUTE_FRAMES, classLoader);
ClassVisitor classVisitor = new TraceClassAdapter(AgpCompat.getAsmApi(), classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
is.close();
...
if (!ignoreCheckClass) {
try {
ClassReader cr = new ClassReader(data);
ClassWriter cw = new ClassWriter(0);
ClassVisitor check = new CheckClassAdapter(cw);
cr.accept(check, ClassReader.EXPAND_FRAMES);
} catch (Throwable e) {
}
}
...
}
TraceClassAdapter
private class TraceClassAdapter extends ClassVisitor {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.className = name;
this.superName = superName;
this.isActivityOrSubClass = isActivityOrSubClass(className, collectedClassExtendMap);
this.isNeedTrace = MethodCollector.isNeedTrace(configuration, className, mappingCollector);
if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
this.isABSClass = true;
}
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
//類中是否包含onWindowFocusChanged方法,Activity中的方法胶惰,matrix將它作為頁(yè)面可見的時(shí)機(jī)傻工。
if (!hasWindowFocusMethod) {
hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc);
}
//是否是抽象類或接口,是則直接跳過(guò)孵滞,不插樁
if (isABSClass) {
return super.visitMethod(access, name, desc, signature, exceptions);
} else {
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
hasWindowFocusMethod, isActivityOrSubClass, isNeedTrace);
}
}
}
TraceMethodAdapter
private class TraceMethodAdapter extends AdviceAdapter {
...
@Override
protected void onMethodEnter() {
//在方法入口插入AppMethodBeat.i(timestamp)方法
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) {
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
if (checkNeedTraceWindowFocusChangeMethod(traceMethod)) {
//在onWindowFocusChanged方法插入AppMethodBeat.at(timestamp)方法中捆,
//用于記錄onWindowFocusChanged的執(zhí)行時(shí)間,分析啟動(dòng)耗時(shí)剃斧。
traceWindowFocusChangeMethod(mv, className);
}
}
}
@Override
protected void onMethodExit(int opcode) {
//在方法出口插入AppMethodBeat.o(timestamp)方法
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) {
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
}
}
}
總結(jié)
插件的核心內(nèi)容在于TraceMethodAdapter中的三項(xiàng)操作:
- 在符合條件的方法入口插入AppMethodBeat.i()
- 在符合條件的方法出口插入AppMethodBeat.o()
- 在Activity的onWindowFocusChanged方法中插入AppMethodBeat.at()
在編譯期間轨香,除被排除掉的方法外,大量方法的入口和出口處被插入了AppMethodBeat的方法幼东,意在能通過(guò)這兩個(gè)方法計(jì)算出方法執(zhí)行的耗時(shí)臂容,于是,每一個(gè)方法執(zhí)行的耗時(shí)情況就清晰的展現(xiàn)在我們開發(fā)者眼前根蟹,借助這些數(shù)據(jù)才能更好的發(fā)現(xiàn)卡頓問(wèn)題的原因脓杉。
有了這些基礎(chǔ),后邊進(jìn)行啟動(dòng)優(yōu)化或者anr分析的時(shí)候就有跡可循简逮,matrix會(huì)幫我們將方法按耗時(shí)時(shí)長(zhǎng)排列出來(lái)球散,方便我們有的放矢的去解決問(wèn)題。