背景
當項目代碼量很大的時候,或者你作為一名新人要快速掌握代碼的時候,給函數打上log,來了解代碼執行邏輯,這種方式會顯然成本太大,要改動項目編譯運作,no!太耗時;或者你想debug的方式來給你想關注的幾個函數,來了解代碼執行邏輯,no!因為你肯定會漏掉函數;也許你可以固執的給你寫的項目打滿log說這樣也行,但是你要知道你方法所調用的jdk的函數或者第三方aar或者jar再或者android sdk中的函數調用順序你怎麼辦,還能打log嗎?顯然不行吧,來~這個項目給讓可以讓你以包名為過濾點過濾你想要知道所有函數調用順序。
效果奉上
動作簡介:首先點選mainactivity的自定義mytextview然後進入secondactivity再點選textview之後finish跳轉回mainactivity
下面是庫處理過所得到的函數調用順序order.txt檔案(我這裡屏蔽了jdk函數,第三方庫函數,以及android sdk中函數,換句話說我就保留了我自己包名中的函數順序)
ok!發現是不是很炫酷啊,下面來介紹該庫的原理(求star!!!)
原理
本庫其實并沒有什麼黑科技,本庫也沒有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/testc/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) {
//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 = new file(capturesdirpath);
capturesdir.traverse {
if (it.isfile() && it.name.endswith(".trace")) {
def ordername = it.name.replace("trace", "txt")
def orderfile = new file(capturesdirpath, ordername)
orderfile.write("")
def dmtracedumpdir = getdmtracedumpdir();
//說明:dmtracedump 為 android sdk自帶工具,要執行dmtracedump指令則需要先添加環境變量
def basecomand = dmtracedumpdir + "dmtracedump -ho " + it.absolutepath + " >> " + orderfile.absolutepath
println basecomand
string osnamematch = system.getproperty("os.name").tolowercase();
if (osnamematch.contains("windows")) {
("cmd /c start /b " + basecomand).execute()
} else {
["bash", "-c", basecomand].execute()
}
}
}
}
/**
* read the sdk dir from local.properties
* eg :
* sdk.dir = /home/env/sdk
* so:
* dmtracedump.dir = /home/env/sdk/platform-tools
*
* @return the dir which dmtracedump tools exists
*/
def getdmtracedumpdir() {
def rootdir = project.rootdir
def localproperties = new file(rootdir, "local.properties")
def sdkdir = null;
if (localproperties.exists()) {
properties properties = new properties()
localproperties.withinputstream { instr ->
properties.load(instr)
sdkdir = properties.getproperty('sdk.dir')
if (sdkdir == null || !(new file(sdkdir).exists())) {
sdkdir = android.getsdkdirectory().getabsolutepath()
sdkdir = android.plugin.getsdkfolder().getabsolutepath()
def dmtracedumptooldir = sdkdir + file.separator + "platform-tools" + file.separator
if (new file(dmtracedumptooldir).exists()) {
return dmtracedumptooldir;
return ""
//這裡appfiltermethodorder 任務其實也不需要 執行找到 \captures 目錄找到 base_order.txt
//用notepad++ 使用正則 先過濾 帶 xit 的行 (我們隻關注ent 行就行,ent代表進入執行函數 xit代表退出函數)再過濾掉你不關心的包名
// notepad++ 中過濾将會使用到的指令行如下
//^.*xit.*$ //去除掉 含有 xit 字元串的行 然後替換為空
// ^((?!xxx).)*$ //去除不包含xxx的行 然後替換為空
//^\s+ //合并空行 然後替換為空
task appfiltermethodorder() {
//todo 替換為你想要過濾的包名
def filterpackagename = "com.zjw.appmethodorder"
if (project.hasproperty("package_name")) {
filterpackagename = project.getproperty("package_name")
//處理包名
def filtersignature = filterpackagename.replaceall("[.]", "/")
if (it.isfile() && it.name.endswith(".txt") && !it.name.contains("--filter")) {
def ordername = it.name.replace(".txt", "--filter.txt")
def ordertimename = it.name.replace(".txt", "--timefilter.txt")
def ordertimefile = new file(capturesdirpath, ordertimename)
ordertimefile.write("")
it.eachline { line ->
if (line.contains(" ent ")
//相容不同版本traceview 有的是方法包名有的是方法簽名
&& (line.contains(filterpackagename)
|| line.contains(filtersignature))
) {
orderfile.append(line + "\n")
}
//生成帶xit 和 ent 的trace行 函數耗時計算方式: xit字元後 數值 減去 ent字元後的 數字 (內插補點就是耗時 機關:微妙)
//注意:好像函數體中含thread.sleep的計算不準确
if (line.contains(filterpackagename)
|| line.contains(filtersignature)){
ordertimefile.append(line + "\n")
如何使用
講了一堆原理我們來說說這個庫怎麼用吧。
下載下傳utils.gradle到工程根目錄
修改根目錄下build.gradle,增加 apply from: rootproject.getrootdir().getabsolutepath() + "/utils.gradle" 對utils.gradle的引用,具體可參考本工程根目錄下的build.gradle.
使用下面的詳細介紹生成trace檔案
使用下面的指令生成堆棧檔案 ./gradlew appoutputmethodorder
上面指令檔案内容太多時,通過這個指令進行過濾包含需要過濾的字元串 ./gradlew appfiltermethodorder -p package_name=com.zjw.appmethodorder
注意:請先確定 anroid sdk 中的dmtracedump 工具加入在你的環境變量中(mac同學因為task面闆執行的bug 需要把gradle添加到環境變量中)
首先編譯運作項目,然後點選下圖的時鐘(這是使用工具打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任務 (特别注意:用mac的同學注意了,現在已知輕按兩下執行task會輸出空檔案,貌似是studio的bug,可以使用 ./gradlew appoutputmethodorder執行該任務)如下圖
這一步完成就将在captures目錄生成和trace檔案同名的txt檔案(如示例com.zjw.appmethodorder_2017.03.25_21.41.trace),該檔案包含所有函數執行順序
等待任務執行完成,再輕按兩下執行appfiltermethodorder任務 (特别注意:用mac的同學注意了,現在已知輕按兩下執行task會輸出空檔案,貌似是studio的bug,可以使用 ./gradlew appfiltermethodorder -p package_name=com.zjw.appmethodorder執行該任務)如下圖視窗
該任務目的就是過濾其他非相關包名,留下自己包名的函數,任務執行完成将在captures目錄生成和·trace檔案同名+--filter.txt檔案(例如示例appmethodorder\captures\com.zjw.appmethodorder_2017.03.25_21.41--filter.txt) 如下圖:
接下來打開trace檔案同名+--filter.txt檔案(例如示例appmethodorder\captures\com.zjw.appmethodorder_2017.03.25_21.41--filter.txt)就是上文效果中的那樣啦。
關于擴充和改造
這裡改成你想要過濾的包名即可。
小工具
windows 環境下 可使用tool檔案夾下的method-trace-analysis.jar 直接導入.trace檔案,一鍵分析
執行appfiltermethodorder 任務 新增字尾為--filtertime.txt 的檔案,用于計算方法耗時
如上圖例:mainactivity.onpause方法執行耗時為149231-148152 = 1079,最終耗時為 1079μs(微秒) 約為 1毫秒
1.生成帶xit 和 ent 的trace行 函數耗時計算方式: xit字元後 數值 減去 ent字元後的 數字 (內插補點就是耗時 機關:微妙)
2.注意:函數體中含thread.sleep的計算不準确
作者:zjw-swun
來源:51cto