版本
v0.6.5
溫馨提示
配合 推薦 Matrix 源碼完整注釋
可能會有更好的效果
概述
本篇文章是 騰訊開源的 APM 框架 Matrix 系列文章的開篇循捺,將對 matrix-trace-canary
這個模塊的代碼進行閱讀顿苇。我們知道 gradle plugin 的入口肯定是繼承了 Plugin 的類舱馅,在 matrix-trace-canary
中就對應的是 MatrixPlugin
,下面我們就從這個類開始閱讀。
1. MatrixPlugin
MatrixPlugin
只有apply
一個方法片择,該方法做了 創(chuàng)建Extension 和 注冊 Task兩件事
void apply(Project project) {
//創(chuàng)建 extension
project.extensions.create("matrix", MatrixExtension)
project.matrix.extensions.create("trace", MatrixTraceExtension)
project.matrix.extensions.create("removeUnusedResources", MatrixDelUnusedResConfiguration)
....
project.afterEvaluate {
....
android.applicationVariants.all { variant ->
if (configuration.trace.enable) {
//注入MatrixTraceTransform 【見2.1】
com.tencent.matrix.trace.transform.MatrixTraceTransform.inject(project, configuration.trace, variant.getVariantData().getScope())
}
//移除無用資源 可用 【見7.1】
if (configuration.removeUnusedResources.enable) {
if (Util.isNullOrNil(configuration.removeUnusedResources.variant) || variant.name.equalsIgnoreCase(configuration.removeUnusedResources.variant)) {
Log.i(TAG, "removeUnusedResources %s", configuration.removeUnusedResources)
RemoveUnusedResourcesTask removeUnusedResourcesTask = project.tasks.create("remove" + variant.name.capitalize() + "UnusedResources", RemoveUnusedResourcesTask)
removeUnusedResourcesTask.inputs.property(RemoveUnusedResourcesTask.BUILD_VARIANT, variant.name)
project.tasks.add(removeUnusedResourcesTask)
removeUnusedResourcesTask.dependsOn variant.packageApplication
variant.assemble.dependsOn removeUnusedResourcesTask
}
}
}
}
}
2. MatrixTraceTransform
MatrixTraceTransform
繼承了 Transform
却盘, 該類中hook了 系統(tǒng)構(gòu)建Dex 的 Transform 并配合 ASM 框架 ,插入方法執(zhí)行時間記錄的字節(jié)碼邦蜜,這一系列內(nèi)容將是本文的重點依鸥。
2.1 MatrixTraceTransform.inject
public static void inject(Project project, MatrixTraceExtension extension, VariantScope variantScope) {
...
//收集配置信息
Configuration config = new Configuration.Builder()
.setPackageName(variant.getApplicationId())//包名
.setBaseMethodMap(extension.getBaseMethodMapFile())//build.gradle 中配置的 baseMethodMapFile ,保存的是 我們指定需要被 插樁的方法
.setBlackListFile(extension.getBlackListFile())//build.gradle 中配置的 blackListFile ,保存的是 不需要插樁的文件
.setMethodMapFilePath(mappingOut + "/methodMapping.txt")// 記錄插樁 methodId 和 method的 關(guān)系
.setIgnoreMethodMapFilePath(mappingOut + "/ignoreMethodMapping.txt")// 記錄 沒有被 插樁的方法
.setMappingPath(mappingOut) //mapping文件存儲目錄
.setTraceClassOut(traceClassOut)//插樁后的 class存儲目錄
.build();
try {
// 獲取 TransformTask.. 具體名稱 如:transformClassesWithDexBuilderForDebug 和 transformClassesWithDexForDebug
// 具體是哪一個 應該和 gradle的版本有關(guān)
// 在該 task之前 proguard 操作 已經(jīng)完成
String[] hardTask = getTransformTaskName(extension.getCustomDexTransformName(), variant.getName());
for (Task task : project.getTasks()) {
for (String str : hardTask) {
// 找到 task 并進行 hook
if (task.getName().equalsIgnoreCase(str) && task instanceof TransformTask) {
TransformTask transformTask = (TransformTask) task;
Log.i(TAG, "successfully inject task:" + transformTask.getName());
Field field = TransformTask.class.getDeclaredField("transform");
field.setAccessible(true);
// 將 系統(tǒng)的 "transformClassesWithDexBuilderFor.."和"transformClassesWithDexFor.."
// 中的 transform 替換為 MatrixTraceTransform(也就是當前類) 【見2.2】
field.set(task, new MatrixTraceTransform(config, transformTask.getTransform()));
break;
}
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
2.2 MatrixTraceTransform 構(gòu)造方法
MatrixTraceTransform
中保存了悼沈,被hook的 Transform 贱迟,因為需要執(zhí)行完 MatrixTraceTransform
的內(nèi)容后,再恢復原流程絮供。
public MatrixTraceTransform(Configuration config, Transform origTransform) {
//配置
this.config = config;
//原始Transform 也就是被 hook的 Transform
this.origTransform = origTransform;
}
3. MatrixTraceTransform.transform
上面將的MatrixTraceTransform.inject()
發(fā)生在gradle 的評估期 衣吠,也就是說在評估期已經(jīng)確認了 整個 gradle Task的執(zhí)行順序,在運行期的話 gradle 會回調(diào) Transform 的 transform
方法壤靶,下面我么一起來看看缚俏。
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
...
try {
// 【見3.1】
doTransform(transformInvocation);
} catch (ExecutionException e) {
e.printStackTrace();
}
...
//執(zhí)行原來應該執(zhí)行的 Transform 的 transform 方法
origTransform.transform(transformInvocation);
...
}
3.1 MatrixTraceTransform.doTransform
doTransform
方法的功能可被分為三步
- 解析mapping 文件記錄 混淆前后方法的對應關(guān)系 并 替換文件目錄
- 收集需要插樁和不需要插樁的方法記錄在 mapping文件中 并 收集類之間的繼承關(guān)系
- 進行字節(jié)碼插樁
private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
//是否增量編譯
final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental();
/**
* step 1
* 1. 解析mapping 文件混淆后方法對應關(guān)系
* 2. 替換文件目錄
*/
long start = System.currentTimeMillis();
List<Future> futures = new LinkedList<>();
// 存儲 混淆前方法、混淆后方法的映射關(guān)系
final MappingCollector mappingCollector = new MappingCollector();
// methodId 計數(shù)器
final AtomicInteger methodId = new AtomicInteger(0);
// 存儲 需要插樁的 方法名 和 方法的封裝對象TraceMethod
final ConcurrentHashMap<String, TraceMethod> collectedMethodMap = new ConcurrentHashMap<>();
// 將 ParseMappingTask 放入線程池
futures.add(executor.submit(new ParseMappingTask(mappingCollector, collectedMethodMap, methodId)));
//存放原始 源文件 和 輸出 源文件的 對應關(guān)系
Map<File, File> dirInputOutMap = new ConcurrentHashMap<>();
//存放原始jar文件和 輸出jar文件 對應關(guān)系
Map<File, File> jarInputOutMap = new ConcurrentHashMap<>();
Collection<TransformInput> inputs = transformInvocation.getInputs();
for (TransformInput input : inputs) {
for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
//【見4.1】
futures.add(executor.submit(new CollectDirectoryInputTask(dirInputOutMap, directoryInput, isIncremental)));
}
for (JarInput inputJar : input.getJarInputs()) {
//【見4.3】
futures.add(executor.submit(new CollectJarInputTask(inputJar, isIncremental, jarInputOutMap, dirInputOutMap)));
}
}
for (Future future : futures) {
// 等待所有線程 運行完畢
future.get();
}
//清空任務
futures.clear();
Log.i(TAG, "[doTransform] Step(1)[Parse]... cost:%sms", System.currentTimeMillis() - start);
/**
* step 2
* 1. 收集需要插樁和不需要插樁的方法,并記錄在 mapping文件中
* 2. 收集類之間的繼承關(guān)系
*/
start = System.currentTimeMillis();
//收集需要插樁的方法信息忧换,每個插樁信息封裝成TraceMethod對象
MethodCollector methodCollector = new MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap);
//【見5.1】
methodCollector.collect(dirInputOutMap.keySet(), jarInputOutMap.keySet());
Log.i(TAG, "[doTransform] Step(2)[Collection]... cost:%sms", System.currentTimeMillis() - start);
/**
* step 3 插樁字節(jié)碼
*/
start = System.currentTimeMillis();
//執(zhí)行插樁邏輯恬惯,在需要插樁方法的入口、出口添加MethodBeat的i/o邏輯
MethodTracer methodTracer = new MethodTracer(executor, mappingCollector, config, methodCollector.getCollectedMethodMap(), methodCollector.getCollectedClassExtendMap());
//【見6.1】
methodTracer.trace(dirInputOutMap, jarInputOutMap);
Log.i(TAG, "[doTransform] Step(3)[Trace]... cost:%sms", System.currentTimeMillis() - start);
}
4. CollectDirectoryInputTask
4.1 CollectDirectoryInputTask.run
public void run() {
try {
//【見4.2】
handle();
} catch (Exception e) {
e.printStackTrace();
Log.e("Matrix." + getName(), "%s", e.toString());
}
}
4.2 CollectDirectoryInputTask.handle
該方法會通過反射 修改 輸入文件的 屬性亚茬,在增量編譯模式下會修改 file
和
changedFiles
兩個屬性 酪耳,在全量編譯模式下 只會修改 file
這一個屬性
private void handle() throws IOException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
//獲取原始文件
final File dirInput = directoryInput.getFile();
//創(chuàng)建輸出文件
final File dirOutput = new File(traceClassOut, dirInput.getName());
final String inputFullPath = dirInput.getAbsolutePath();
final String outputFullPath = dirOutput.getAbsolutePath();
....
if (isIncremental) {//增量更新,只 操作有改動的文件
Map<File, Status> fileStatusMap = directoryInput.getChangedFiles();
//保存輸出文件和其狀態(tài)的 map
final Map<File, Status> outChangedFiles = new HashMap<>();
for (Map.Entry<File, Status> entry : fileStatusMap.entrySet()) {
final Status status = entry.getValue();
final File changedFileInput = entry.getKey();
final String changedFileInputFullPath = changedFileInput.getAbsolutePath();
//增量編譯模式下之前的build輸出已經(jīng)重定向到dirOutput刹缝;替換成output的目錄
final File changedFileOutput = new File(changedFileInputFullPath.replace(inputFullPath, outputFullPath));
if (status == Status.ADDED || status == Status.CHANGED) {
//新增碗暗、修改的Class文件,此次需要掃描
dirInputOutMap.put(changedFileInput, changedFileOutput);
} else if (status == Status.REMOVED) {
//刪除的Class文件赞草,將文件直接刪除
changedFileOutput.delete();
}
outChangedFiles.put(changedFileOutput, status);
}
//使用反射 替換directoryInput的 改動文件目錄
replaceChangedFile(directoryInput, outChangedFiles);
} else {
//全量編譯模式下讹堤,所有的Class文件都需要掃描
dirInputOutMap.put(dirInput, dirOutput);
}
//反射input,將dirOutput設置為其輸出目錄
replaceFile(directoryInput, dirOutput);
}
4.3 CollectJarInputTask.run
CollectJarInputTask
的工作和 CollectDirectoryInputTask
基本上是一樣的厨疙,只不過操作目標從文件夾換成了 jar
@Override
public void run() {
try {
【見4.4】
handle();
} catch (Exception e) {
e.printStackTrace();
Log.e("Matrix." + getName(), "%s", e.toString());
}
}
4.4 CollectJarInputTask.handle
private void handle() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, IOException {
// traceClassOut 文件夾地址
String traceClassOut = config.traceClassOut;
final File jarInput = inputJar.getFile();
//創(chuàng)建唯一的 文件
final File jarOutput = new File(traceClassOut, getUniqueJarName(jarInput));
....
if (IOUtil.isRealZipOrJar(jarInput)) {
if (isIncremental) {//是增量
if (inputJar.getStatus() == Status.ADDED || inputJar.getStatus() == Status.CHANGED) {
//存放到 jarInputOutMap 中
jarInputOutMap.put(jarInput, jarOutput);
} else if (inputJar.getStatus() == Status.REMOVED) {
jarOutput.delete();
}
} else {
//存放到 jarInputOutMap 中
jarInputOutMap.put(jarInput, jarOutput);
}
} else {// 專門用于 處理 WeChat AutoDex.jar 文件 可以略過洲守,意義不大
....
}
//將 inputJar 的 file 屬性替換為 jarOutput
replaceFile(inputJar, jarOutput);
}
5. MethodCollector
5.1 MethodCollector.collect
//存儲 類->父類 的map(用于查找Activity的子類)
private final ConcurrentHashMap<String, String> collectedClassExtendMap = new ConcurrentHashMap<>();
//存儲 被忽略方法名 -> 該方法TraceMethod 的映射關(guān)系
private final ConcurrentHashMap<String, TraceMethod> collectedIgnoreMethodMap = new ConcurrentHashMap<>();
//存儲 需要插樁方法名 -> 該方法TraceMethod 的映射關(guān)系
private final ConcurrentHashMap<String, TraceMethod> collectedMethodMap;
private final Configuration configuration;
private final AtomicInteger methodId;
// 被忽略方法計數(shù)器
private final AtomicInteger ignoreCount = new AtomicInteger();
//需要插樁方法 計數(shù)器
private final AtomicInteger incrementCount = new AtomicInteger();
....
/**
*
* @param srcFolderList 原始文件集合
* @param dependencyJarList 原始 jar 集合
* @throws ExecutionException
* @throws InterruptedException
*/
public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException {
List<Future> futures = new LinkedList<>();
for (File srcFile : srcFolderList) {
//將所有源文件添加到 classFileList 中
ArrayList<File> classFileList = new ArrayList<>();
if (srcFile.isDirectory()) {
listClassFiles(classFileList, srcFile);
} else {
classFileList.add(srcFile);
}
//這里應該是個bug,這個for 應該防止撒謊給你嗎那個for 的外面
for (File classFile : classFileList) {
// 每個源文件執(zhí)行 CollectSrcTask 【見5.2】
futures.add(executor.submit(new CollectSrcTask(classFile)));
}
}
for (File jarFile : dependencyJarList) {
// 每個jar 源文件執(zhí)行 CollectJarTask 【見5.5】
futures.add(executor.submit(new CollectJarTask(jarFile)));
}
for (Future future : futures) {
future.get();
}
futures.clear();
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
//存儲不需要插樁的方法信息到文件(包括黑名單中的方法) 【見5.6】
saveIgnoreCollectedMethod(mappingCollector);
}
}));
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
//存儲待插樁的方法信息到文件 【見5.7】
saveCollectedMethod(mappingCollector);
}
}));
for (Future future : futures) {
future.get();
}
futures.clear();
}
5.2 CollectSrcTask.run
public void run() {
InputStream is = null;
try {
is = new FileInputStream(classFile);
ClassReader classReader = new ClassReader(is);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//收集Method信息 【見5.3】
ClassVisitor visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
classReader.accept(visitor, 0);
} catch (Exception e) {
...
}
5.3 TraceClassAdapter.visit
到 TraceClassAdapter
類的時候就到了 ASM 框架 發(fā)揮作用的時候了沾凄,ASM 在掃描類的時候 會依次回調(diào) visit
和 visitMethod
方法
private class TraceClassAdapter extends ClassVisitor {
....
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;
}
//存到 collectedClassExtendMap 中
collectedClassExtendMap.put(className, superName);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (isABSClass) {//如果是虛擬類或者接口 就不管
return super.visitMethod(access, name, desc, signature, exceptions);
} else {
if (!hasWindowFocusMethod) {
//該方法是否與onWindowFocusChange方法的簽名一致
// (該類中是否復寫了onWindowFocusChange方法梗醇,Activity不用考慮Class混淆)
hasWindowFocusMethod = isWindowFocusChangeMethod(name, desc);
}
//CollectMethodNode中執(zhí)行method收集操作 【見5.4】
return new CollectMethodNode(className, access, name, desc, signature, exceptions);
}
}
}
5.4 CollectMethodNode
CollectMethodNode繼承了MethodNode ,ASM框架在掃描方法的時候會回調(diào) MethodNode 中的 visitEnd
方法
private class CollectMethodNode extends MethodNode {
....
@Override
public void visitEnd() {
super.visitEnd();
//創(chuàng)建TraceMethod
TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
//如果是構(gòu)造方法
if ("<init>".equals(name)) {
isConstructor = true;
}
//判斷類是否 被配置在了 黑名單中
boolean isNeedTrace = isNeedTrace(configuration, traceMethod.className, mappingCollector);
//忽略空方法撒蟀、get/set方法叙谨、沒有局部變量的簡單方法
if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
&& isNeedTrace) {
//忽略方法遞增
ignoreCount.incrementAndGet();
//加入到被忽略方法 map
collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
return;
}
//不在黑名單中而且沒在在methodMapping中配置過的方法加入待插樁的集合;
if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
traceMethod.id = methodId.incrementAndGet();
collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
incrementCount.incrementAndGet();
} else if (!isNeedTrace && !collectedIgnoreMethodMap.containsKey(traceMethod.className)) {//在黑名單中而且沒在在methodMapping中配置過的方法加入ignore插樁的集合
ignoreCount.incrementAndGet();
collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
}
}
.....
}
5.5 CollectJarTask.run
CollectJarTask
和 CollectSrcTask
一樣都會 調(diào)用到 TraceClassAdapter進行方法的掃描
public void run() {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(fromJar);
Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
while (enumeration.hasMoreElements()) {
ZipEntry zipEntry = enumeration.nextElement();
String zipEntryName = zipEntry.getName();
if (isNeedTraceFile(zipEntryName)) {//是需要被插樁的文件
InputStream inputStream = zipFile.getInputStream(zipEntry);
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//進行掃描 【見5.3】
ClassVisitor visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
classReader.accept(visitor, 0);
}
}
}
....
5.6 MethodCollector.saveIgnoreCollectedMethod
saveIgnoreCollectedMethod
方法很簡單保屯,就是將前面手機的 被忽略的方法內(nèi)容寫到 ignoreMethodMapping.txt 中
/**
* 將被忽略的 方法名 存入 ignoreMethodMapping.txt 中
* @param mappingCollector
*/
private void saveIgnoreCollectedMethod(MappingCollector mappingCollector) {
//創(chuàng)建 ignoreMethodMapping.txt 文件對象
File methodMapFile = new File(configuration.ignoreMethodMapFilePath);
//如果他爸不存在就創(chuàng)建
if (!methodMapFile.getParentFile().exists()) {
methodMapFile.getParentFile().mkdirs();
}
List<TraceMethod> ignoreMethodList = new ArrayList<>();
ignoreMethodList.addAll(collectedIgnoreMethodMap.values());
Log.i(TAG, "[saveIgnoreCollectedMethod] size:%s path:%s", collectedIgnoreMethodMap.size(), methodMapFile.getAbsolutePath());
//通過class名字進行排序
Collections.sort(ignoreMethodList, new Comparator<TraceMethod>() {
@Override
public int compare(TraceMethod o1, TraceMethod o2) {
return o1.className.compareTo(o2.className);
}
});
PrintWriter pw = null;
try {
FileOutputStream fileOutputStream = new FileOutputStream(methodMapFile, false);
Writer w = new OutputStreamWriter(fileOutputStream, "UTF-8");
pw = new PrintWriter(w);
pw.println("ignore methods:");
for (TraceMethod traceMethod : ignoreMethodList) {
//將 混淆過的數(shù)據(jù) 轉(zhuǎn)換為 原始數(shù)據(jù)
traceMethod.revert(mappingCollector);
//輸出忽略信息到 文件中
pw.println(traceMethod.toIgnoreString());
}
} catch (Exception e) {
Log.e(TAG, "write method map Exception:%s", e.getMessage());
e.printStackTrace();
} finally {
if (pw != null) {
pw.flush();
pw.close();
}
}
}
5.7 MethodCollector.saveCollectedMethod
saveCollectedMethod
是將需要插樁的方法寫入 methodMapping.txt
/**
* 將被插樁的 方法名 存入 methodMapping.txt 中
* @param mappingCollector
*/
private void saveCollectedMethod(MappingCollector mappingCollector) {
File methodMapFile = new File(configuration.methodMapFilePath);
if (!methodMapFile.getParentFile().exists()) {
methodMapFile.getParentFile().mkdirs();
}
List<TraceMethod> methodList = new ArrayList<>();
//因為Android包下的 都不會被插裝手负,但是我們需要 dispatchMessage 方法的執(zhí)行時間
//所以將這個例外 加進去
TraceMethod extra = TraceMethod.create(TraceBuildConstants.METHOD_ID_DISPATCH, Opcodes.ACC_PUBLIC, "android.os.Handler",
"dispatchMessage", "(Landroid.os.Message;)V");
collectedMethodMap.put(extra.getMethodName(), extra);
methodList.addAll(collectedMethodMap.values());
Log.i(TAG, "[saveCollectedMethod] size:%s incrementCount:%s path:%s", collectedMethodMap.size(), incrementCount.get(), methodMapFile.getAbsolutePath());
//通過ID 進行排序
Collections.sort(methodList, new Comparator<TraceMethod>() {
@Override
public int compare(TraceMethod o1, TraceMethod o2) {
return o1.id - o2.id;
}
});
PrintWriter pw = null;
try {
FileOutputStream fileOutputStream = new FileOutputStream(methodMapFile, false);
Writer w = new OutputStreamWriter(fileOutputStream, "UTF-8");
pw = new PrintWriter(w);
for (TraceMethod traceMethod : methodList) {
traceMethod.revert(mappingCollector);
pw.println(traceMethod.toString());
}
} catch (Exception e) {
Log.e(TAG, "write method map Exception:%s", e.getMessage());
e.printStackTrace();
} finally {
if (pw != null) {
pw.flush();
pw.close();
}
}
}
6. MethodTracer.trace
trace
方法就是真正開始插樁
public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) throws ExecutionException, InterruptedException {
List<Future> futures = new LinkedList<>();
//對源文件進行插樁 【見6.1】
traceMethodFromSrc(srcFolderList, futures);
//對jar進行插樁 【見6.5】
traceMethodFromJar(dependencyJarList, futures);
for (Future future : futures) {
future.get();
}
futures.clear();
}
6.1 MethodTracer.traceMethodFromSrc
private void traceMethodFromSrc(Map<File, File> srcMap, List<Future> futures) {
if (null != srcMap) {
for (Map.Entry<File, File> entry : srcMap.entrySet()) {
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
//【見6.2】
innerTraceMethodFromSrc(entry.getKey(), entry.getValue());
}
}));
}
}
}
6.2 MethodTracer.innerTraceMethodFromSrc
private void innerTraceMethodFromSrc(File input, File output) {
for (File classFile : classFileList) {
InputStream is = null;
FileOutputStream os = null;
try {
//原始文件全路徑
final String changedFileInputFullPath = classFile.getAbsolutePath();
//插樁后文件
final File changedFileOutput = new File(changedFileInputFullPath.replace(input.getAbsolutePath(), output.getAbsolutePath()));
if (!changedFileOutput.exists()) {
changedFileOutput.getParentFile().mkdirs();
}
changedFileOutput.createNewFile();
if (MethodCollector.isNeedTraceFile(classFile.getName())) {//需要插樁
is = new FileInputStream(classFile);
ClassReader classReader = new ClassReader(is);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// TraceClassAdapter 進行插樁 【見6.3】
ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
is.close();
if (output.isDirectory()) {
os = new FileOutputStream(changedFileOutput);
} else {
os = new FileOutputStream(output);
}
//將修改后的內(nèi)容寫入到 插裝后的文件中
os.write(classWriter.toByteArray());
os.close();
} else {//不需要插樁,直接copy
FileUtil.copyFileUsingStream(classFile, changedFileOutput);
}
} catch (Exception e) {
}
}
}
6.3 TraceClassAdapter
private class TraceClassAdapter extends ClassVisitor {
private String className;
private boolean isABSClass = false;
private boolean hasWindowFocusMethod = false;
private boolean isActivityOrSubClass;
private boolean isNeedTrace;
TraceClassAdapter(int i, ClassVisitor classVisitor) {
super(i, 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;
//是否是 activity 或者其 子類
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) {
//抽象類竟终、接口不插樁
if (isABSClass) {
return super.visitMethod(access, name, desc, signature, exceptions);
} else {
if (!hasWindowFocusMethod) {
//是否是onWindowFocusChange方法
hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc);
}
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
//【見6.4】
return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
hasWindowFocusMethod, isActivityOrSubClass, isNeedTrace);
}
}
@Override
public void visitEnd() {
//如果Activity的子類沒有onWindowFocusChange方法,插入一個onWindowFocusChange方法
if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
insertWindowFocusChangeMethod(cv, className);
}
super.visitEnd();
}
}
6.4 TraceMethodAdapter
private class TraceMethodAdapter extends AdviceAdapter {
.....
//函數(shù)入口處添加 AppMethodBeat.i()方法
@Override
protected void onMethodEnter() {
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) {
//traceMethodCount +1
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
}
}
//函數(shù)出口處添加 AppMethodBeat.O()方法
@Override
protected void onMethodExit(int opcode) {
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) {
//是 onWindowFocusChanged 方法 則在出口添加 AppMethodBeat.at()
if (hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
TraceMethod windowFocusChangeMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
if (windowFocusChangeMethod.equals(traceMethod)) {
traceWindowFocusChangeMethod(mv, className);
}
}
//traceMethodCount +1
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
}
}
}
6.5 MethodTracer.traceMethodFromJar
private void traceMethodFromJar(Map<File, File> dependencyMap, List<Future> futures) {
if (null != dependencyMap) {
for (Map.Entry<File, File> entry : dependencyMap.entrySet()) {
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
//【見6.6】
innerTraceMethodFromJar(entry.getKey(), entry.getValue());
}
}));
}
}
}
6.6 MethodTracer.innerTraceMethodFromJar
private void innerTraceMethodFromJar(File input, File output) {
Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
while (enumeration.hasMoreElements()) {
ZipEntry zipEntry = enumeration.nextElement();
String zipEntryName = zipEntry.getName();
if (MethodCollector.isNeedTraceFile(zipEntryName)) {
InputStream inputStream = zipFile.getInputStream(zipEntry);
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// 【見6.3】
ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
byte[] data = classWriter.toByteArray();
InputStream byteArrayInputStream = new ByteArrayInputStream(data);
ZipEntry newZipEntry = new ZipEntry(zipEntryName);
FileUtil.addZipEntry(zipOutputStream, newZipEntry, byteArrayInputStream);
} else {
InputStream inputStream = zipFile.getInputStream(zipEntry);
ZipEntry newZipEntry = new ZipEntry(zipEntryName);
//直接copy jar 到插裝過后的 存放區(qū)
FileUtil.addZipEntry(zipOutputStream, newZipEntry, inputStream);
}
}
}
7.1
未完待續(xù)切蟋。统捶。
總結(jié)
- Matrix 在 gradle 的評估期 hook 系統(tǒng) 生成dex 的 Task 為自定義的 Task,并在執(zhí)行完相關(guān)流程后柄粹,再執(zhí)行回原有Task喘鸟,將控制權(quán)交還給系統(tǒng)。
- Matrix 使用 Transform 配合 ASM 完成 侵入編譯流程進行字節(jié)碼插入操作驻右。
系列文章
- 騰訊 Apm 框架 Matrix 源碼閱讀 - gradle插件
- 騰訊 Apm 框架 Matrix 源碼閱讀 - 架構(gòu)解析
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 架構(gòu)解析
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 FrameTracer
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 StartupTracer
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 AnrTracer
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 上報字段含義