一個能讓你了解所有函數調用順序的Android庫!
背景:當項目代碼量很大的時候,或者你作為一名新人要快速掌握代碼的時候,給函數打上log,來了解代碼執行邏輯,這種方式會顯然成本太大,要改動項目編譯運行,NO!太耗時;或者你想debug的方式來給你想關注的幾個函數,來了解代碼執行邏輯,NO!因為你肯定會漏掉函數;也許你可以固執的給你寫的項目打滿log說這樣也行,但是你要知道你方法所調用的jdk的函數或者第三方aar或者jar再或者android sdk中的函數調用順序你怎么辦,還能打log嗎?顯然不行吧,來~這個項目給讓可以讓你以包名為過濾點過濾你想要知道所有函數調用順序。
1. 效果奉上
動作簡介:首先點擊MainActivity的自定義MyTextView然后進入SecondActivity再點擊textview之后finish跳轉回MainActivity
下面是庫處理過所得到的函數調用順序 order.txt 文件(我這里屏蔽了jdk函數,第三方庫函數,以及android sdk中函數,換句話說我就保留了我自己包名中的函數順序)
832 ent 67593 .....com.zjw.appmethodorder.MainActivity.onClick (Landroid/view/View;)V MainActivity.java
832 ent 99956 ..........com.zjw.appmethodorder.MainActivity.onPause ()V MainActivity.java
832 ent 99970 ...........com.zjw.appmethodorder.BaseActivity.onPause ()V BaseActivity.java
832 ent 100472 ............com.zjw.appmethodorder.BaseActivity.baseOnPause ()V BaseActivity.java
832 ent 128540 ........com.zjw.appmethodorder.SecondActivity.<init> ()V SecondActivity.java
832 ent 128562 .........com.zjw.appmethodorder.BaseActivity.<init> ()V BaseActivity.java
832 ent 213911 ........com.zjw.appmethodorder.SecondActivity.onCreate (Landroid/os/Bundle;)V SecondActivity.java
832 ent 213928 .........com.zjw.appmethodorder.BaseActivity.onCreate (Landroid/os/Bundle;)V BaseActivity.java
832 ent 258414 ..........com.zjw.appmethodorder.BaseActivity.baseOnCreate ()V BaseActivity.java
832 ent 1440503 .........com.zjw.appmethodorder.SecondActivity.onResume ()V SecondActivity.java
832 ent 1440563 ..........com.zjw.appmethodorder.BaseActivity.onResume ()V BaseActivity.java
832 ent 1445675 ...........com.zjw.appmethodorder.BaseActivity.baseOnResume ()V BaseActivity.java
832 ent 2954291 .................com.zjw.appmethodorder.MyTextView.onWindowVisibilityChanged (I)V MyTextView.java
832 ent 3065664 ........com.zjw.appmethodorder.MainActivity.onStop ()V MainActivity.java
832 ent 3065701 .........com.zjw.appmethodorder.BaseActivity.onStop ()V BaseActivity.java
832 ent 3069155 ..........com.zjw.appmethodorder.BaseActivity.baseOnStop ()V BaseActivity.java
832 ent 3139519 .......com.zjw.appmethodorder.SecondActivity.click (Landroid/view/View;)V SecondActivity.java
832 ent 3146300 ........com.zjw.appmethodorder.SecondActivity.finish ()V SecondActivity.java
832 ent 3183478 ..........com.zjw.appmethodorder.SecondActivity.onPause ()V SecondActivity.java
832 ent 3183498 ...........com.zjw.appmethodorder.BaseActivity.onPause ()V BaseActivity.java
832 ent 3183843 ............com.zjw.appmethodorder.BaseActivity.baseOnPause ()V BaseActivity.java
832 ent 3209420 ........com.zjw.appmethodorder.MainActivity.<init> ()V MainActivity.java
832 ent 3209438 .........com.zjw.appmethodorder.BaseActivity.<init> ()V BaseActivity.java
832 ent 3283359 ........com.zjw.appmethodorder.MainActivity.onCreate (Landroid/os/Bundle;)V MainActivity.java
832 ent 3283378 .........com.zjw.appmethodorder.BaseActivity.onCreate (Landroid/os/Bundle;)V BaseActivity.java
832 ent 3330938 ..........com.zjw.appmethodorder.BaseActivity.baseOnCreate ()V BaseActivity.java
832 ent 4363295 .....................com.zjw.appmethodorder.MyTextView.<init> (Landroid/content/Context;Landroid/util/AttributeSet;)V MyTextView.java
832 ent 4436094 ..................com.zjw.appmethodorder.MyTextView.onFinishInflate ()V MyTextView.java
832 ent 4449689 .........com.zjw.appmethodorder.MainActivity.initView ()V MainActivity.java
832 ent 4539427 .........com.zjw.appmethodorder.MainActivity.onResume ()V MainActivity.java
832 ent 4539467 ..........com.zjw.appmethodorder.BaseActivity.onResume ()V BaseActivity.java
832 ent 4543597 ...........com.zjw.appmethodorder.BaseActivity.baseOnResume ()V BaseActivity.java
832 ent 4917854 .................com.zjw.appmethodorder.MyTextView.onAttachedToWindow ()V MyTextView.java
832 ent 4918658 .................com.zjw.appmethodorder.MyTextView.onWindowVisibilityChanged (I)V MyTextView.java
832 ent 5090653 ...................................com.zjw.appmethodorder.MyTextView.onMeasure (II)V MyTextView.java
832 ent 5355203 ..................................com.zjw.appmethodorder.MyTextView.onMeasure (II)V MyTextView.java
832 ent 5456681 .......................................com.zjw.appmethodorder.MyTextView.onSizeChanged (IIII)V MyTextView.java
832 ent 5467577 ....................................com.zjw.appmethodorder.MyTextView.onLayout (ZIIII)V MyTextView.java
832 ent 5876623 ...........................................com.zjw.appmethodorder.MyTextView.onDraw (Landroid/graphics/Canvas;)V MyTextView.java
832 ent 6121967 ........com.zjw.appmethodorder.SecondActivity.onStop ()V SecondActivity.java
832 ent 6121986 .........com.zjw.appmethodorder.BaseActivity.onStop ()V BaseActivity.java
832 ent 6123689 ..........com.zjw.appmethodorder.BaseActivity.baseOnStop ()V BaseActivity.java
832 ent 6127522 ........com.zjw.appmethodorder.SecondActivity.onDestroy ()V SecondActivity.java
832 ent 6127679 .........com.zjw.appmethodorder.BaseActivity.onDestroy ()V BaseActivity.java
832 ent 6133301 ..........com.zjw.appmethodorder.BaseActivity.baseOnDestroy ()V BaseActivity.java
OK!發現是不是很炫酷啊,下面來介紹該庫的原理(求star!!!)
2 原理
本庫其實并沒有什么黑科技,本庫也沒有java代碼,核心就是2個build.gradle中的task。首先,原理就是基于android sdk中提供的工具----traceview,和dmtracedump。traceview會生成.trace文件,該文件記錄了函數調用順序,函數耗時,函數調用次數等等有用的信息。而dmtracedump 工具就是基于trace文件生成報告的工具,具體用法不細說。dmtracedump 工具大家一般用的多的選項就是生成html報告,或者生成調用順序圖片(看起來很不直觀)。首先說說為什么要用traceview,和dmtracedump來作為得到函數調用順序的,因為這個工具既然能知道cpu執行時間和調用次數以及函數調用樹(看出函數調用順序很費勁)比如android studio是這樣呈現.trace文件的解析視圖的
或者這樣的
(上面這張圖是網上找的,侵刪) 用上面這2個圖發現你要清晰知道函數調用看懂了才是見鬼了。或者使用dmtracedump 工具解析生成的html長下面這樣(dmtracedump 生成圖片就不說了 生成出的圖片也根本看不出順序這個就略過了)
一開始我以為 Method 序列號有戲于是乎沖動的我把帶序號的東西內容復制出來寫了一個腳本對他們進行排序代碼如下:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Sort implements Comparable<Sort> {
static String uri = "D:/Application/eclipse/javaworkspace/Test/src/JB/1.text";
String str = "";
String content = "";
public Sort(String str,String content) {
super();
this.str = str;
this.content = content;
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
ArrayList<Sort> list = new ArrayList<>();
BufferedReader in = new BufferedReader(new FileReader(uri));
String a = "";
while ((a = in.readLine()) != null) {
//System.out.println(Long.valueOf(getIndexStr(a)));
if(a.contains("com.zjw.appmethodorder")){
list.add(new Sort(getIndexStr(a),a));
}
}
Collections.sort(list);
for (Sort sort : list) {
System.out.println(sort.content);
}
}
public static String getIndexStr(String str) {
String regEx = "(?<=\[)(\S+)(?=\])";// 匹配[]中的數字
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(str.trim());
while (m.find()) {
return m.group().trim();
}
return "";
}
@Override
public int compareTo(Sort o) {
// TODO Auto-generated method stub
//return 0;
return (int) (Long.valueOf(str) - Long.valueOf(o.str));
}
}
結果發現過濾后的東西序列號是按順序的但是并不是代碼執行的邏輯順序。我擦怎么辦,工具代碼也寫了,居然不是我想要的結果,好在花了一些時間發現dmtracedump -ho 選項,經過研究發現,這玩意輸出的內容居然是按邏輯順序從上到下的,于是基于這一點我寫一個項目,其實核心就是2個task完成了了解所有函數調用順序的目的。
//核心任務:在captures文件目錄下輸出 基于最新.trace文件的函數調用信息的txt版本
//說明:dmtracedump 為 android sdk自帶工具,要執行dmtracedump命令則需要先添加環境變量
task AppOutPutMethodOrder() {
doLast {
def capturesDirPath = project.getProjectDir().getParentFile().path + File.separator + "captures";
def capturesDir = file(capturesDirPath);
def traceType = "trace"
if (!capturesDir.exists() || !capturesDir.canRead()) {
return
}
def map = new TreeMap<Long, String>(
new Comparator<Long>() {
public int compare(Long obj1, Long obj2) {
return obj2.compareTo(obj1);
}
});
//遍歷拿到trace 文件名 然后排序 找到最大時間數的trace就是最新的文件,拿到字符串
capturesDir.list(new FilenameFilter() {
@Override
boolean accept(File dir, String name) {
if (name.contains(traceType)) {
def substring = name.substring(name.length() - 22, name.length() - 6).trim()
String regEx = "[^0-9]"
Pattern p = Pattern.compile(regEx)
Matcher m = p.matcher(substring)
def time = m.replaceAll("").trim()
map.put(Long.parseLong(time), name)
}
return false
}
})
def lastTraceName = "";
Set<Long> keySet = map.keySet();
Iterator<Long> iterator = keySet.iterator();
while (iterator.hasNext()) {
Long key = iterator.next();
lastTraceName = map.get(key);
break;
}
def tracePath = capturesDirPath + File.separator + lastTraceName
println "===== tracePath is ${tracePath} =========="
def orderPath = capturesDirPath + File.separator + "base_order.txt"
def orderFile = file(orderPath)
if(orderFile.exists()){
orderFile.write("")
}
Runtime runtime = Runtime.getRuntime();
//說明:dmtracedump 為 android sdk自帶工具,要執行dmtracedump命令則需要先添加環境變量
def baseComand = "dmtracedump -ho " + tracePath + " >> " + orderPath
def command = ""
String[] cmdArray = null;
String osName = System.getProperty("os.name");
String osNameMatch = osName.toLowerCase();
if(osNameMatch.contains("windows")) {
command = "cmd /c start /b "+baseComand;
}else {
cmdArray = ["bash", "-c", baseComand];
}
try {
if (cmdArray != null) {
runtime.exec(cmdArray);
} else {
runtime.exec(command);
}
} catch (Exception e) {
println "=====Exception: ${e.getCause()} =========="
}
}
}
//這里AppFilterMethodOrder 任務其實也不需要 執行找到 captures 目錄找到 base_order.txt
//用Notepad++ 使用正則 先過濾 帶 xit 的行 (我們只關注ent 行就行,ent代表進入執行函數 xit代表退出函數)再過濾掉你不關心的包名
// Notepad++ 中過濾將會使用到的命令行如下
//^.*xit.*$ //去除掉 含有 xit 字符串的行 然后替換為空
// ^((?!XXX).)*$ //去除不包含XXX的行 然后替換為空
//^s+ //合并空行 然后替換為空
task AppFilterMethodOrder(){
doLast{
def capturesDirPath = project.getProjectDir().getParentFile().path + File.separator + "captures";
def orderPath = capturesDirPath + File.separator + "base_order.txt"
if(!file(orderPath).exists()){
return
}
BufferedReader inputStream = new BufferedReader(new FileReader(orderPath));
def filterOrderPath = new File(capturesDirPath + File.separator + "order.txt")
if (!filterOrderPath.exists()){
filterOrderPath.createNewFile();
}else {
filterOrderPath.write("")
}
String content ;
while ((content = inputStream.readLine()) != null) {
if(content.contains(" ent ")
//com.zjw.appmethodorder 這里可以修改成你想要留下的包名對應的函數調用信息
&& content.contains("com.zjw.appmethodorder")
){
filterOrderPath .append(content+"n")
}
}
}
}
3.如何使用
講了一堆原理我們來說說這個庫怎么用吧。
首先編譯運行項目,然后點擊下圖的時鐘(這是使用工具打trace start 和 end)進行操作,可以參考上文所說的動作簡介(記住你操作想想你的生命周期函數調用順序,待會可以和生成的captures目錄下base_order.txt或者生成的order.txt中的函數順序做做對比)然后再點一次下圖那個時鐘。還有一種記錄trace start 和 end的方式就是在修改代碼,即使用 android.os.Debug.startMethodTracing(); 和 android.os.Debug.stopMethodTracing(); 以上操作完成后即會在captures目錄生成 com.zjw.appmethodorder_2017.03.25_21.41.trace 文件,android studio會默認打開一個可視化窗口
然后雙擊右側面板的 AppOutPutMethodOrder 任務 如下圖
這一步完成就將在 captures 目錄生成 base_order.txt 文件,該文件包含所有函數執行順序
等待任務執行完成,再執行 AppFilterMethodOrder 任務 如下圖窗口
該任務目的就是過濾其他非相關包名,留下自己包名的函數,任務執行完成將在 captures 目錄生成 order.txt 文件 如下圖:
接下來打開 order.txt 文件就是上文效果中的那樣啦。
4.關于擴展和改造
這里稍作修改即可。注意 AppOutPutMethodOrder 任務一直是以最新時間生成的.trace文件作為生成base_order.txt基準的。