「Android」adb調試源碼(針對dumpsys SurfceFlinger、trace.txt獲取)


首先對ADB作簡單的闡述,接下來對adb shell dumpsys SurfaceFlinger服務的dump信息的查看、以及ANR問題如何獲取trace文件並簡單分析。

 

-×**************************************************************

目錄:

一、ADB概述

二、ADB常用命令

(1)基本命令

(2)軟件操作命令

(3)文件操作命令

(4)日志操作命令

 

三、dumpsys使用

四、dump SurfaceFlinger的打印信息分析

五、trace分析  

-×*************************************************************

 一、ADB概述

adb(Android Debug Bridge),安卓平台調試橋,是連接Android手機與PC端的橋梁,通過adb可以管理、操作模擬器和設備,如安裝軟件、查看設備軟硬件參數、系統升級、運行shell命令等。

手機啟動USB調試模式,設備連接電腦。

注:我的手機一加6的USB調試模式打開方式如下:

(1)在手機設置的關於手機找到版本號,雙擊七次打開開發者模式;

(2)在開發者選項中打開USB調試選項;
(3)設備連接
 
二、ADB常用命令
(1)基本命令:
1、adb devices 查看設備連接情況
2、adb version 查看版本
3、adb kill-server 關閉adb服務
4、adb start-server 啟動adb服務
5、adb 查看幫助
6、adb shell 進入adb sehll命令
7、adb shell top  查看手機當前進程占用手機內存情況 

8、adb shell kill -3 pid 殺掉進程

9、adb logcat -v process |grep 8607              //8607 是進程 PID

 

(2)軟件操作命令:
  安裝軟件:
     adb install
     adb install <apk文件路徑> :這個命令將指定的apk文件安裝到設備上
   卸載軟件:
     adb uninstall <軟件名>
     adb uninstall -k <軟件名> 如果加 -k 參數,為卸載軟件但是保留配置和緩存文件
(3)文件操作命令:
  從電腦上發送文件到設備
   adb push <本地路徑> <遠程路徑>

  用push命令可以把本機電腦上的文件或者文件夾復制到設備(手機)

 

  從設備上下載文件到電腦
   adb pull <遠程路徑> <本地路徑>

  用pull命令可以把設備(手機)上的文件或者文件夾復制到本機電腦

(4)日志操作命令
  1. adb logcat 查看日志

日志等級(由上往下級別遞增):

V verbase,級別最低,瑣碎、不重要的日志信息

D debug,調試信息

I info,重要信息

W warning,警告信息

E error,錯誤信息

F fatal,嚴重錯誤信息

S slient,無記載

  1. adb logcat --help 查看幫助信息,獲取該命令可配置的參數選項
  1. adb logcat -b <buffer> 加載一個可使用的日志緩沖區供查看

buffer選項可以填為:

radio 通信系統

system 系統組件

event event事件模塊

main java層

kernel linux內核

  1. adb logcat [tag:level] 指定標簽,指定級別過濾顯示日志

例如:adb logcat Test:I

  1. adb logcat -c 清理已存在的日志
  1. adb logcat -g 打印日志緩沖區的大小
  1. adb logcat > home/mylog.txt 日志保存到電腦某路徑
  1. adb logcat -d -f /sdcard/mylog.txt 保存到手機上指定位置(-d 日志顯示在控制台)
  1. adb logcat -f /scard/log.txt 輸出到手機指定位置
  1. adb logcat -s System.out 設置標簽(某個字符串),過濾顯示日志
  1. adb logcat -v <format> 設置日志輸入格式控制輸出字段

format選項可以填為:

brief 顯示優先級/標記和原始進程的PID(默認)

process 只顯示進程PID

tag 只顯示優先級/標記

thread 只顯示進程、線程、優先級/標記

raw

time

long

例如:adb logcat -v process

(5)adb shell命令
  1. adb shell pm list packages 查看2、adb shell pm list packages -f 查看包名對應的apk路徑及名稱
3、adb shell dumpsys  列出手機所有apk的詳細信息

 

三、dumpsys使用

(1)adb shell     進入shell

(2)dumpsys -l   查看所有正在運行的服務名

    service list    查看這些服務名稱調用了哪個服務

    下面列舉了其中一些服務名:

服務名 類名 功能
activity ActivityManagerService AMS相關信息
package PackageManagerService PMS相關信息
window WindowManagerService WMS相關信息
input InputManagerService IMS相關信息
power PowerManagerService PMS相關信息
batterystats BatterystatsService 電池統計信息
battery BatteryService 電池信息
alarm AlarmManagerService 鬧鍾信息
dropbox DropboxManagerService 調試相關
procstats ProcessStatsService 進程統計
cpuinfo CpuBinder CPU
meminfo MemBinder 內存
gfxinfo GraphicsBinder 圖像
dbinfo DbBinder 數據庫
服務名 功能
SurfaceFlinger 圖像相關
appops app使用情況
permission 權限
processinfo 進程服務
batteryproperties 電池相關
audio 查看聲音信息
netstats 查看網絡統計信息
diskstats 查看空間free狀態
jobscheduler 查看任務計划
wifi wifi信息
diskstats 磁盤情況
usagestats 用戶使用情況
devicestoragemonitor 設備信息

 

(3)dumpsys <service>    打印具體某一項服務(service就是前面表格中的服務名)

dumpsys cpuinfo //打印一段時間進程的CPU使用百分比排行榜
dumpsys meminfo -h  //查看dump內存的幫助信息
dumpsys package <packagename> //查看指定包的信息
dumpsys SurfaceFLinger    //查看SF服務

 

四、dump SurfaceFlinger的打印信息分析

  SurfaceFlinger的dump信息主要通過dumpAllLocked 函數來獲取。

  一般包含:
1、layer的信息,layer一般對應於一個surface;
2、opengl的信息。一般是跟gpu比較相關的參數,opengl是標准的接口;
3、display。安卓支持三種類型的display,可以導出display當前的顯示狀態,也就是各個surface(layer)在各個display的顯示屬性;
4、surfaceflinger管理graphis buffer的信息。主要是layer申請的幀數據內存;
5、hwcomopser的如果實現dump接口也能知道hwcomposer的一些參數;
6、gralloc的內存分配信息。如果gralloc有實現dump接口的話;

 

(1)特殊宏的打開

Build configuration: [sf] [libui] [libgui]

 

(2)打印目前正在使用的Sync機制

Sync configuration: [using: EGL_ANDROID_native_fence_sync EGL_KHR_wait_sync]

 (3)打印Layer

Visible layers (count = 9)

 

  count的值來源於layersSortedByZ中layer的數量,接下來就進入各個layer的dump。

  例如:

  >  0xb3f92000指向當前layer對象的值,括號中是當前layer的名稱,id是創建layer時產生的序列號

+ Layer 0xb3f92000 (com.sec.android.app.launcher/com.android.launcher2.Launcher) id=87

 

  >  區域信息,兩段是兩個Region的dump,每個region可能包含多個區域,所以這里count也可能不等於1,

前兩行的值來源於activeTransparentRegion,表示的是這個layer里面透明區域的大小
后兩行值來源於visibleRegion,表示可見區域的大小

Region transparentRegion (this=0xb3f92164, count=1)
    [  0,   0,   0,   0]
  Region visibleRegion (this=0xb3f92008, count=1)
    [  0,   0, 1440, 2560]

 

  >  基本信息:

layerStack=   0, z=    21010, pos=(0,0), size=(1440,2560), crop=(0, 0,1440,2560), isOpaque=0, 
invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
client=0xb11160c0

 

    對應的dumpAllLock中的源碼:

  result.appendFormat(      
            "layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), crop=(%4d,%4d,%4d,%4d), "
            "isOpaque=%1d, invalidate=%1d, "
            "alpha=0x%02x, flags=0x%08x, tr=[%.2f, %.2f][%.2f, %.2f]\n"
            "      client=%p\n",
            s.layerStack, s.z, s.transform.tx(), s.transform.ty(), s.active.w, s.active.h,
            s.active.crop.left, s.active.crop.top,
            s.active.crop.right, s.active.crop.bottom,
            isOpaque(s), contentDirty,
            s.alpha, s.flags,
            s.transform[0][0], s.transform[0][1],
            s.transform[1][0], s.transform[1][1],
            client.get());

 

  • layerStack表示這個layer是保存在哪個layerstack中(不同的display是有不同的layerstack的);

  • z表示Z軸坐標,z值越大,layer越靠上;

  • pos的值是layer左上角的位置,這個值比較特殊的是ImageWallpaper這個layer的pos值,因為ImageWallpaper的大小大於屏幕大小,所以ImageWallpaper的pos值在屏幕的外面(note4是pos=(-560,0)).

  • size自然是layer的大小;

  • crop代表裁剪區域,這點依然是對於壁紙很明顯,因為壁紙layer大小大於屏幕,必須涉及到需要裁剪一部分顯示在屏幕上,因此它的裁剪區域是crop=( 560, 0,2000,2560);
  • isOpaque代表是否是不透明的,只有完全不透明的layer這個值才是1,比如壁紙,像狀態欄和launcher他們都是0,代表不是完全不透明;
  • invalidate表示這個layer的數據是失效的,這個值絕大多數情況下都是0.因為我們看到的一般都是繪制好的有效的數據.一種情況下這值特別頻繁的多見為1,就是剛剛鎖屏(解鎖)時.因為突然鎖屏,會導致繪制的內容和要顯示的內容完全不同,導致layer的各種數據要重新計算,所以將layer置為失效;
  • alpha表示了這張layer的透明度,這個值跟isOpaque是有區別的.isOpaque表示了這個layer可以是透明的,也就是沒有顯示數據的地方,可以透明;而alpha表示透明度,也即是有數據的地方也可以因為透明度而收到影響產生透明的效果;
  • flag值含義豐富,它是眾多flag或出來的結果,會a影星layer的狀態;
  • 接下來的一組tr數據代表屏幕的旋轉和縮放程度.大多數的layer實際上是不需要旋轉和縮放的,因為他們定義的大小就是跟屏幕一致的,所以他們的這組數據是[1.00, 0.00][0.00, 1.00],實際上如果你使用這組數據來做矩陣變換的話,矩陣是不會發生變化的.需要旋轉的比較典型的場景是照相機.橫着拿相機時它的layer的變換矩陣是[-1.00, 0.00][-0.00, -1.00],也就是旋轉180°. 這個值的來源是上層調用setMatrix函數設置的.
  • client含義比較簡單,值的來源是創建layer時,對應的SurfaceSession中mNativeClient.這東西也是跟SurfaceSession一一對應的,也就是跟SurfaceFlinger連接時一一對應的.從這個值我們可以判斷,client值相同的layer,必然來自同一個進程(因為他們是由同一個連接創建出來的).

(4)打印Displays信息

  首先會打印當前display的數量,數量基於mDisplays的大小,這個容器在SurfaceFlinger初始化時會生成數據,后面根據收到不同的消息在handleTransactionLocked函數中也會調整.
正常情況下是1,也就是只有一個display(Built-in Screen),當設備連接了HDMI或者使用了屏幕共享等功能時,會有額外的display加入。

Displays (2 entries)  //這個是連接了HDMI后的數據
+ DisplayDevice: HDMI Screen
   type=1, hwcId=1, layerStack=6, (1920x1080), ANativeWindow=0xb4d94d08, orient= 0 (type=00000000), flips=1173, isSecure=1, 
    secureVis=0, powerMode=2, activeConfig=0, numLayers=1
   v:[0,0,1920,1080], f:[0,0,1920,1080], s:[0,0,1920,1080],transform:[[1.000,0.000,-0.000][0.000,1.000,-0.000][0.000,0.000,1.000]]
mAbandoned=0
-BufferQueue mMaxAcquiredBufferCount=2, mDequeueBufferCannotBlock=0, default-size=[1920x1080], default-format=1, transform-hint=00, 
  FIFO(0)={}
 [00:0xb6418c80] state=FREE    , 0xb43ed880 [1920x1080:1920,  1]
 [01:0xb43cb300] state=FREE    , 0xb640d970 [1920x1080:1920,  1]
>[02:0xb43cb280] state=ACQUIRED, 0xb43ed830 [1920x1080:1920,  1]
+ DisplayDevice: Built-in Screen     //DisplayDevice是設備的名字,這個可以調用接口設置,但是比較常見的值一般有:Built-in Screen,HDMI Screen,Virtual Screen,wfdservice等等
   type=0, hwcId=0, layerStack=0, (1080x1920), ANativeWindow=0xb4d94608, orient= 0 (type=00000000), flips=3140, isSecure=1, 
    secureVis=0, powerMode=2, activeConfig=0, numLayers=2
   v:[0,0,1080,1920], f:[0,0,1080,1920], s:[0,0,1080,1920],transform:[[1.000,0.000,-0.000][0.000,1.000,-0.000][0.000,0.000,1.000]]

 

 五、trace分析

trace.txt生成:當APP(包括系統APP和用戶APP)進程出現ANR、應用響應慢或WatchDog的監視沒有得到回饋時,系統會dump此時的top進程,進程中Thread的運行狀態就都dump到這個Trace文件中了。

ANR一般有三種類型:

  1、KeyDispatchTimeout(5 seconds) --主要類型  按鍵或觸摸事件在特定時間內無響應

  2、BroadcastTimeout(10 seconds)        BroadcastReceiver在特定時間內無法處理完成

  3、ServiceTimeout(20 seconds) --小概率類型  Service在特定的時間內無法處理完成

---------------------------------------------------------------

 1、adb shell 進入手機的/data/anr文件目錄下面查看生成的trace.txt文件

  如果ls查看文件列表沒有權限,可以先adb  root一下

 2、adb pull /data/   ./     將該文件導出,然后分析

 ---------------------------------------------------------------

log打印了ANR的基本信息(adb shell top查看cg進程,adb logcat -v process |grep PID查看日志),

可以分析CPU使用率得知ANR的簡單情況;如果CPU使用率很高,接近100%,可能是在進行大規模的計算更可能是陷入死循環;如果CUP使用率很低,說明主線程被阻塞了,並且當IOwait很高,可能是主線程在等待I/O操作的完成。

對於ANR只是分析Log很難知道問題所在,我們還需要通過Trace文件分析stack調用情況,在log中顯示的pid在traces文件中與之對應,然后通過查看堆棧調用信息分析ANR的代碼

(此處trace的分析參考 https://blog.csdn.net/qq_25804863/article/details/49111005 )

----- pid 17027 at 2017-06-22 10:37:39 -----    // ANR出現的進程pid和時間
Cmd line: org.code:MessengerService                // ANR出現的進程名(或者進程包名)
Build fingerprint: 'Homecare/qucii8976v3_64:6.0.1/pansen06141150:eng/test-keys'        // 下面記錄系統版本,內存等狀態信息
ABI: 'arm64'
Build type: optimized
Zygote loaded classes=6576 post zygote classes=13
Intern table: 13780 strong; 17 weak
JNI: CheckJNI is on; globals=283 (plus 158 weak)
Libraries: /system/lib64/libandroid.so /system/lib64/libcompiler_rt.so /system/lib64/libjavacrypto.so /system/lib64/libjnigraphics.so /system/lib64/libmedia_jni.so /system/lib64/libwebviewchromium_loader.so libjavacore.so (7)
Heap: 29% free, 8MB/12MB; 75731 objects
Dumping cumulative Gc timings
Total number of allocations 75731
Total bytes allocated 8MB
Total bytes freed 0B
Free memory 3MB
Free memory until GC 3MB
Free memory until OOME 183MB
Total memory 12MB
Max memory 192MB
Zygote space size 3MB
Total mutator paused time: 0
Total time waiting for GC to complete: 0
Total GC count: 0
Total GC time: 0
Total blocking GC count: 0
Total blocking GC time: 0

suspend all histogram:    Sum: 76us 99% C.I. 0.100us-28us Avg: 7.600us Max: 28us
DALVIK THREADS (15):
// Signal Catcher是記錄traces信息的線程
// Signal Catche(線程名)、(daemon)表示守護進程、prio(線程優先級,默認是5)、tid(線程唯一標識ID)、Runnable(線程當前狀態)
"Signal Catcher" daemon prio=5 tid=3 Runnable
//線程組名稱、suspendCount、debugSuspendCount、線程的Java對象地址、線程的Native對象地址
  | group="system" sCount=0 dsCount=0 obj=0x12d8f0a0 self=0x5598ae55d0
  //sysTid是線程號(主線程的線程號和進程號相同)
  | sysTid=17033 nice=0 cgrp=default sched=0/0 handle=0x7fb2350450
  | state=R schedstat=( 4348125 172343 3 ) utm=0 stm=0 core=1 HZ=100
  | stack=0x7fb2254000-0x7fb2256000 stackSize=1013KB
  | held mutexes= "mutator lock"(shared held)
  native: #00 pc 0000000000489e28  /system/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, int, char const*, art::ArtMethod*, void*)+236)
  native: #01 pc 0000000000458fe8  /system/lib64/libart.so (art::Thread::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) const+220)
  native: #02 pc 0000000000465bc8  /system/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+688)
  native: #03 pc 0000000000466ae0  /system/lib64/libart.so (art::ThreadList::RunCheckpoint(art::Closure*)+276)
  native: #04 pc 000000000046719c  /system/lib64/libart.so (art::ThreadList::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+188)
  native: #05 pc 0000000000467a84  /system/lib64/libart.so (art::ThreadList::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+492)
  native: #06 pc 0000000000431194  /system/lib64/libart.so (art::Runtime::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+96)
  native: #07 pc 000000000043e604  /system/lib64/libart.so (art::SignalCatcher::HandleSigQuit()+1256)
  native: #08 pc 000000000043f214  /system/lib64/libart.so (art::SignalCatcher::Run(void*)+452)
  native: #09 pc 0000000000068714  /system/lib64/libc.so (__pthread_start(void*)+52)
  native: #10 pc 000000000001c604  /system/lib64/libc.so (__start_thread+16)
  (no managed stack frames)

//main(線程名)、prio(線程優先級,默認是5)、tid(線程唯一標識ID)、Sleeping(線程當前狀態)
"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x73132d10 self=0x5598a5f5e0
  //sysTid是線程號(主線程的線程號和進程號相同)
  | sysTid=17027 nice=0 cgrp=default sched=0/0 handle=0x7fb6db6fe8
  | state=S schedstat=( 420582038 5862546 143 ) utm=24 stm=18 core=6 HZ=100
  | stack=0x7fefba3000-0x7fefba5000 stackSize=8MB
  | held mutexes=
  // java 堆棧調用信息(這里可查看導致ANR的代碼調用流程)(分析ANR最重要的信息)
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x0c60f3c7> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x0c60f3c7> (a java.lang.Object)    // 鎖住對象0x0c60f3c7
  at java.lang.Thread.sleep(Thread.java:985)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at org.code.ipc.MessengerService.onCreate(MessengerService.java:63)    //導致ANR的代碼
  at android.app.ActivityThread.handleCreateService(ActivityThread.java:2877)
  at android.app.ActivityThread.access$1900(ActivityThread.java:150)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1427)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:148)
  at android.app.ActivityThread.main(ActivityThread.java:5417)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

 

Traces中顯示的線程狀態都是C代碼定義的.我們可以通過查看線程狀態對應的信息分析ANR問題

如: TimedWaiting對應的線程狀態是TIMED_WAITING

kTimedWaiting, // TIMED_WAITING TS_WAIT in Object.wait() with a timeout 執行了無超時參數的wait函數

kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep() 執行了帶有超時參數的sleep函數

 

ZOMBIE                 線程死亡,終止運行
RUNNING/RUNNABLE           線程可運行或正在運行
TIMED_WAIT               執行了帶有超時參數的wait、sleep或join函數
MONITOR                線程阻塞,等待獲取對象鎖
WAIT                   執行了無超時參數的wait函數
INITIALIZING              新建,正在初始化,為其分配資源
STARTING                  新建,正在啟動
NATIVE                  正在執行JNI本地函數
VMWAIT                正在等待VM資源
SUSPENDED                線程暫停,通常是由於GC或debug被暫停

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM