JankMan-極致的卡頓分析系統(tǒng)
1.卡頓分析系統(tǒng)介紹
此系統(tǒng)擁有了端上采集兩個維度數據的能力
- 方法運行數據:系統(tǒng)在編譯期間基于ASM9+AGP7+自定義方法ID映射+自定義字節(jié)碼指令集實現了方法運行數據的采集。
- 幀性能數據:系統(tǒng)在運行期間基于FrameMatrix+自定義數據結構體實現了端上幀數據的采集。
當APP發(fā)生運行卡頓時,系統(tǒng)可自動分析堆棧,并且關聯卡頓幀的方法調用鏈,并作出記錄最終導出至文件。整體基于協(xié)程制作,性能損耗僅需1%。
基于Compose KMP獨創(chuàng)了多端可用的線下還原器,將采集到的數據做格式效驗,并作出二次還原解析,進行了perfetto的二次開發(fā),實現了可視化展示整體信息的能力。
2.思路介紹
2.1方法運行數據采集
2.1.1方法ID映射
由于系統(tǒng)會在運行期間會采集大量數據,所以需要將龐大的方法名映射為指定ID的能力,如下
//方法ID`方法具體全路徑和行參 24`com.d.hookplus.HookApplication$onCreate$1.onActivitySaveInstanceState[android.app.Activity,android.os.Bundle,]
此處的方法名包含了全路徑和行參,如果通過字符串記錄是十分龐大的,所以在編譯期間使用ASM技術將其對應成ID,降低空間復雜度
2.2.2函數記錄能力
在ASM輪訓期間,在方法開始和結束位置各插入對應的指令用于實現標記功能
override fun onMethodEnter() {
//方法進入
methodVisitor?.perfettoVisitMethodDelegate(PerfettoCentre.const.METHOD_ENTER, methodId)
super.onMethodEnter()
}
?
override fun onMethodExit(opcode: Int) {
//方法退出
methodVisitor?.perfettoVisitMethodDelegate(PerfettoCentre.const.METHOD_EXIT, methodId)
super.onMethodExit(opcode)
}處理退出指令時,catch和return指令也有對應的記錄
在對應的時機插入對應的代碼
this.let { methodVisitor ->
methodVisitor.visitFieldInsn(
GETSTATIC,
"com/d/hookcore/perfetto/PerfettoCore",
"Companion",
"Lcom/d/hookcore/perfetto/PerfettoCore$Companion;"
)
methodVisitor.visitIntInsn(BIPUSH, enterOrExit)
methodVisitor.visitIntInsn(SIPUSH, methodId)
methodVisitor.visitMethodInsn(
INVOKEVIRTUAL,
"com/d/hookcore/perfetto/PerfettoCore$Companion",
"getTraceLine",
"(II)V",
false
)
}
例:
private fun initRecycler() {
//方法開始,第一個參數用于標記進入或者退出,第二個參數用于標記映射的方法ID
PerfettoCore.getTraceLine(0,12)
var recyclerView: RecyclerView = findViewById(R.id.rec)
recyclerView.adapter = NodeAdapter(messageList)
PerfettoCore.getTraceLine(1,12)
}
- 插入完代碼的例子如下:
2.2.3.運行方法記錄內容
方法記錄后的結果如下
如:main-1,942155.120954153,1,39,1
| 說明 | 例 |
|---|---|
| 當前線程 | Main |
| 當前線程ID | 1 |
| 當前時間秒值 | 942155.120954153 |
| 當前方法標記(進入或退出) | 1 |
| 當前方法ID | 39 |
| 當前幀位下標 | 1 |
2.2幀數據采集
2.2.1于傳統(tǒng)方式的區(qū)別
區(qū)別于Choreographer Hook的方式,系統(tǒng)采用了FrameMatrix實現了幀數據采集
Choreographer的Hook點是在Looper.loop之中的Printer.println下,
final Printer logging = me.mLogging;
if (logging != null) {
//Hook點
logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);
}
缺點如下:
每次都需要字符串匹配,性能損耗嚴重
在API31之中被封掉了
2.2.2FrameMatrix的能力
FrameMatrix通過addOnFrameMetricsAvailableListener實現了幀數據獲取的能力
window.addOnFrameMetricsAvailableListener({ window, frameMetrics, dropCountSinceLastInvocation ->
//do something
}, frameMetricsHandler)
如果不對window進行addOnFrameMetricsAvailableListener,數據也會保留在平臺層,所以沒有性能損耗
FrameMatrix是Android平臺層提供的幀數據采集,包含如下信息
| 類別 | 參數 | 說明 |
|---|---|---|
| 獲取每幀性能數據 | FIRST_DRAW_FRAME | 表示當前幀是否是當前 Window 布局中繪制的第一幀 |
| INTENDED_VSYNC_TIMESTAMP | 當前幀的預期開始時間, 如果此值與 VSYNC_TIMESTAMP 不同,則表示 UI 線程上發(fā)生了阻塞,阻止了 UI 線程及時響應vsync信號 | |
| TOTAL_DURATION | 表示此幀渲染并發(fā)布給顯示子系統(tǒng)所花費的總時間, 等于所有其他具有時間價值的指標的值之和 | |
| VSYNC_TIMESTAMP | 在所有 vsync 監(jiān)聽器和幀繪制中使用的時間值(Choreographer 的幀回調, 動畫, View#getDrawingTime等) | |
| cpuDuration | COMMAND_ISSUE_DURATION | 表示向 GPU 發(fā)出繪制命令的耗時 |
| SWAP_BUFFERS_DURATION | 表示將此幀的幀緩沖區(qū)發(fā)送給顯示子系統(tǒng)所花的時間 | |
| uiDuration | UNKNOWN_DELAY_DURATION | 表示等待 UI 線程響應并處理幀所經過的時間, 大多數情況下應為0 |
| INPUT_HANDLING_DURATION | 表示處理輸入事件回調的耗時 | |
| ANIMATION_DURATION | 表示執(zhí)行動畫回調的耗時 | |
| LAYOUT_MEASURE_DURATION | 表示對 View 樹進行 measure 和 layout 所花的時間 | |
| DRAW_DURATION | 表示將 View 樹轉換為 DisplayList 的耗時 | |
| SYNC_DURATION | 表示將 DisplayList 與渲染線程同步所花的時間 |
2.3.同步算法
同步算法是將方法運行數據和幀性能數據自動分析,裁剪,合并出問題堆棧,并保存到指定位置的過程
在開發(fā)者自定義連續(xù)卡頓多少幀后進行Dump
如果每卡頓一幀就Dump,信息量太密集,并且意義不大,建議開發(fā)者連續(xù)卡頓5幀起步
2.3.1.同步算法細節(jié)
整個同步的過程是在單獨的一個HandlerThread中進行的,所以面臨了兩個難題:
由于HandlerThread接受幀數據的時機是不確定的,即可能方法數據已經收集到很多幀以后了,但是幀數據才剛剛到來。如何精準定位到卡頓范圍內的全部函數
如何盡可能減小性能損耗,降低時間復雜度和空間復雜度
所以我們整個同步的過程是圍繞著這兩個問題進行設計的
2.3.2.算法合并過程
如果卡頓幀連續(xù)個數到達了開發(fā)者定義的個數,那么開始還原
將首個卡頓幀的開始時間和連續(xù)卡頓幀后的第一個不卡頓幀的開始時間作為時間范圍,與函數運行數據的時間點進行校準。匹配到函數運行的范圍區(qū)域,并通過兩個指針進行標記
具體匹配的過程是通過魔改版的二分查找實現
將標記出的函數運行范圍進行導出
導出的能力是基于NIO實現的
導出完畢后將函數指針位移到開始位置,重復利用空間
上述過程均在子線程進行,對主線程無影響,現階段損耗為3%左右

2.4.可視化展示
- 將Dump出的數據進行二次改造,支持可視化展示
2.4.1.線下還原器
- 基于perfetto的構造格式進行二次改造,用于支持可視化展示
2.4.2.支持多端的線下還原器
基于Compose KMP實現了多端的可視化還原器,效果如下:

參數說明如下:
- MappingFile:函數ID映射文件
- SourceFile:后綴為.zy_trace的文件,是系統(tǒng)自動采集的格式
- OutputPath:輸出為perfetto識別格式的文件目錄
還原格式細節(jié)
如下:
com.d.hookplus|11116 main-1,942155.105176549,0,29,0 main-1,942155.105229674,1,29,0 main-1,942155.105313008,0,29,0 main-1,942155.105322383,1,29,0 main-1,942155.105492174,0,29,0 main-1,942155.105503112,1,29,0 main-1,942155.105524466,0,30,0 .......
# tracer: nop # # entries-in-buffer/entries-written: 30624/30624 #P:4 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION # | | | | |||| | | com.d.hookplus-main-1 [000]... 942155.105177: tracing_mark_write: B|11116|0com.d.hookplus.NodeAdapter.getItemCount.[] com.d.hookplus-main-1 [000]... 942155.105230: tracing_mark_write: E|11116|0com.d.hookplus.NodeAdapter.getItemCount.[] com.d.hookplus-main-1 [000]... 942155.105313: tracing_mark_write: B|11116|0com.d.hookplus.NodeAdapter.getItemCount.[] com.d.hookplus-main-1 [000]... 942155.105322: tracing_mark_write: E|11116|0com.d.hookplus.NodeAdapter.getItemCount.[] com.d.hookplus-main-1 [000]... 942155.105492: tracing_mark_write: B|11116|0com.d.hookplus.NodeAdapter.getItemCount.[] com.d.hookplus-main-1 [000]... 942155.105503: tracing_mark_write: E|11116|0com.d.hookplus.NodeAdapter.getItemCount.[] com.d.hookplus-main-1 [000]... 942155.105524: tracing_mark_write: .....
- 改造前:
- 改造后:
2.4.3.可視化展示
- 最終將還原器輸出的文件直接導入至perfetto系統(tǒng)中,效果如下

以上就是JankMan-極致的卡頓分析系統(tǒng)的詳細內容,更多關于JankMan卡頓分析系統(tǒng)的資料請關注腳本之家其它相關文章!
相關文章
Android直播軟件搭建之實現背景顏色滑動漸變效果的詳細代碼
這篇文章主要介紹了Android直播軟件搭建之實現背景顏色滑動漸變效果的詳細代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09
AndroidStudio 配置 AspectJ 環(huán)境實現AOP的方法
本篇文章主要介紹了AndroidStudio 配置 AspectJ 環(huán)境實現AOP的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02

