Android Studio如何調試Framework層的代碼?

Android框架層的代碼龐大且複雜,在分析一些過程的時候經常會跟丟代碼,所以希望能夠動態調試Framework,已經有編譯好了的Android源代碼。還有兩個疑惑,動態調試時,能否只導入Framework的代碼,而不是導入整個源代碼。如果我想斷在ActivityThread的main中函數,應該如何設置?謝謝。


關注這個問題一段時間了,強答一下吧;軟體開發過程中,只有小部分的時候是編碼,大部分的工作都是在調試,Debug是一項非常非常重要的技能,毋庸多言。我這裡只給出F ramework中Java代碼的調試方法,關於native代碼的調試還請高人指點。

OK,回到正題;其實整個調試過程非常簡單:

  1. 在你要調試進程的合適位置打上斷點
  2. 跟蹤代碼(Step in/out/over等等)

接下來就從這兩個方面展開。

如何在正確的地方下斷點

「正確的地方」包含兩個含義:首先,調試是以進程為單位進行的,如果你需要調試運行在進程A 中的代碼,卻把debugger attach到了B進程,那麼這個斷點壓根兒就是牛頭不對馬嘴;另外呢,比如你想調試Android的多媒體框架,你得知道media相關的類在哪吧,也就是說需要在正確的函數裡面下斷點。

首先,如何在合適的進程下斷點?

如果是調試我們自己寫的App,在Android Studio裡面非常簡單,在Run菜單de最後面有一個attach debugger to Android process 的選項,打開之後選擇自己需要調試的進程即可;但是,你要是需要調試Android Framework層的代碼,這樣做是達不到目的的——Framework層的代碼通常運行在別的進程(比如ActivityManagerService運行在system_server進程),而這些進程通常情況下是不可調試的,也就是說在attach debugger to android process 的那個菜單裡面不會有系統的進程,如下圖:

解決這個辦法很簡單:使用模擬器(真機也行,限Nexus系列刷原生Android系統,注意事項見附錄),我的Nexus 5 可以調試的進程如下:

這樣,系統中所有的Android進程都可以調試了;這一點很重要,比如你要分析Activity的啟動流程,相當多一部分代碼是在ActivityManagerService所在的進程system_server執行的,如果你把斷點打在別的進程,就會產生跟丟了的情況。在比如,你要調試ActivityThread的main函數,在main函數裡面執行了一句attach,最終掉了AMS的attachApplication這時候,代碼就通過Binder IPC調用到了AMS的system_server進程。

明白你要執行的代碼運行在哪一個進程相當重要,在Android中,由於Binder通信機制的存在,「進程遷移」使用的非常非常頻繁,因此需要對binder機制有一定的了解;詳細的話可以參考我的博客:Binder學習指南

如何在對應的代碼處下斷點?

假設我們現在把debugger attach到了正確的進程,那麼斷點應該下在哪裡呢?直觀來講,就是說我需要導入所有的Android源碼嗎?如果不是應該導入哪些代碼,怎麼導入?

首先,如果你需要調試的類在sdk裡面導出了,你壓根兒就不需要再導入源碼,Android Studio自動幫你關聯了這部分代碼(前提是你用SDK Manager下載了sdk的源碼,如下圖:

比如你要調試ActivityManagerServce類的attachApplication方法,那麼很簡單;創建一個空的Android項目,SDK版本選擇與你要調試的模擬器/真機 的android相同(這很重要,下文會講述);然後attrach 到system_server進程,直接在attach_application上面打上斷點;隨便啟動一個app,可以看到我們熟悉的調試界面:

如果這部分類在sdk中沒有導入(比如@hide)的,又或者壓根兒不是SDK的類,(比如系統app的源碼)那應該怎麼辦呢?直接導入這部分代碼即可。不需要是Android項目,普通的Java項目即可;舉個例子,假設你想調試原生Android系統的「系統設置」這個程序,改如何做呢?

根據上面的分析,我們首先得知道「系統設置?」運行在哪一個進程,通常情況下進程名字就是包名;我們查出設置的包名即可,而包名是在源碼的AndroidManifeist中聲明的,因此,我們查到?「系統設置」這個程序的源碼即可;源碼在https://android.googlesource.com/ ,系統App的源碼在/packages這個子目錄下面,我們一個個找,可以確定「系統設置」的源碼在 https://android.googlesource.com/platform/packages/apps/Settings/ ;然後我們把這部分代碼git clone下來,導入Android Studio:

我們去AndroidManifest中查到,「系統設置」的包名為:com.android.settings,這樣我們attach到這個進程 :

然後,我們隨便打一個斷點玩一玩,比如進入設置主界面的時候,斷下來;我們在AndroidManifest中查到設置程序的入口界面為:Settings,我們在這個類的onCreate裡面打一個斷線,然後進入設置程序,發現完美滴斷下來了:

OK,到這裡;應該學會如何在正確的位置打斷點了:正確的進程,正確的位置。接下來,要完成調試,還需要一些技巧。

如何跟蹤代碼?

或許你會說,跟蹤代碼不就是step in/out/over么,這有什麼難的?但其實事情並沒有你想像的那麼簡單,要優雅滴調試,還是需要一些姿勢的。

  1. 行號對應

跟蹤代碼一個首要的問題是行號對應。如果你在正確位置下了斷點,但是跟蹤的時候,單步調試,發現運行的代碼和Android Studio裡面的代碼對不上號,那麼就很蛋疼;要使得調試器的行號能夠對應,必須保證設備上的代碼和調試器的代碼是同一份;簡單來說,需要使用Android的原生系統(模擬器,Nexus系列真機),然後調試器裡面使用的SDK版本,必須和設備的系統版本一致。

一定要注意行好對應這一點,這會使調試過程簡單很多;那麼,如果沒有辦法,行號對不上,那該如何調試呢?行號對不上也是可以調試的,這裡先賣個關子,有贊更新 ^_^

2. 熟練使用斷點

斷點是有很多種類型的,方法斷點,watch point,條件斷點都能夠很好滴輔助我們調試;如果你連這幾個名詞都沒有聽說過,一定要惡補一下;可以參閱我的博客:Android Studio你不知道的調試技巧

到這裡,Android Framework的Java層如何調試應該比較清楚了;回到題主的問題:如何調試ActivityThread的main函數?

這個有一點點複雜,因為main函數執行得非常早,在進程啟動之後還沒來得及attach debugger,這代碼估計已經執行了;如果你你自己的app進程,你可以使用Debug.waitForDebugger()這個函數等到調試器;但是Framework的代碼就不行了;如樓上所說,這裡可以取個巧:main函數會通過Binder IPC到AMS進程的attachApplication函數,你直接在AMS所在的進程system_server對應位置打上斷點(上文講述過的attachApplciation),這個函數執行完畢,就自動回到app進程了,debugger捕捉到斷點之後,你可以在app進程裡面下斷點。

拋磚引玉,若有不妥多多指教~


沒有用Android Studio動態調試過android framework的代碼,但用Eclipse動態調試android framework的代碼以前倒是經常干。

這裡主要針對Eclipse環境來回答一下題主的兩個問題。

對於第一個,能否只導入framework的代碼來動態調試的問題。

答案是可以的。

android framework的代碼量本身非常龐大,甚至可以只導入framework的一部分代碼來動態調試對應的那一部分代碼。比如system server進程,也就是我們在DDMS的設備進程列表中看到的那個system_process進程,會運行主要的系統Service的代碼,比如AMS,WMS,PMS等,這些系統Service的代碼位於frameworks/base/services/這個位置,我們需要動態調試這些Service的代碼的時候就可以只導入這一部分的代碼。再比如,android app進程中運行的framework代碼,主要位於frameworks/base/core/這個位置,要動態調試這部分代碼時也可以只導入這部分代碼。

儘管這個時候為android framework或framework的一部分創建的Java Project無法在eclipse中編譯通過,但依然有辦法動態調試相關的代碼。

對於第二個調試設置的問題。

想要動態調試android framework的代碼,首先有個前提,即需要將你編譯的ROM燒進設備,且要求編譯模式是user_debug或eng,也就是你的Eclipse中導入的代碼和你的設備上運行的是同一份。

然後是調試設置,針對題主要動態調試ActivityThread的代碼這種情形,之前用過的一種思路供參考。

ActivityThread中初始化相關的代碼是Android APP進程運行非常早期會執行的代碼,而我們知道Android APP的進程是由zygote進程創建的,因而想要直接在這些代碼中加斷點動態調試比較困難。

但Android APP進程創建起來之後,比較早期的時候就會訪問AMS,印象里AMS的attachApplication()方法(frameworks/base/services/java/com/android/server/am/ActivityManagerService.java)在Android APP剛啟動時會被訪問到,而且是同步訪問,也就是說ActivityThread的執行流程通過IPC訪問到這個方法,在這個方法結束之前,ActivityThread的執行流程不會繼續往前走。因而可以在設備啟動完成之後,創建針對於system server,也就是system_process進程的遠程調試會話,在這個方法中加斷點,從而截斷Android APP進程的啟動過程,然後再創建針對於Android APP的遠程調試會話,在ActivityThread的代碼中加斷點,進而釋放掉system_process的斷點來達到題主單步調試Android App的ActivityThread的代碼的目的。

創建遠程調試會話的過程

比如要調試system_process進程中運行的系統Service的代碼。

第一步,創建Eclipse的Java Project,導入android framework中系統Service的相關代碼。

第二步,在DDMS中單擊選中要調試的進程。

第三步,創建遠程調試會話。

右鍵單擊創建的android framework的Java Project,選中「Debug As」 -&> 「Debug Configurations ...」,在彈出來的對話框中找到「Remote Java Application」這一項,並雙擊它,將直接創建一個遠程調試會話。找到「Connection Properties:」,修改其中的Port為8700。

然後點擊右下角的「Debug」按鈕。

至此則遠程調試會話創建完畢,並啟動對於前面第二步中選中的進程的調試。

然後呢,就可以愉快的加斷點,動態調試system_process進程中運行的framework代碼了。

針對Android App進程的遠程調試會話的創建

如我們前面所述,斷下system_process的AMS中的attachApplication()方法會截斷Android App中ActivityThread的執行流程。這個時候,進程的名字都還沒有設置,因而在DDMS中可能會看到名字還是「?」的Android App進程。然後參照上面針對system_process進程創建遠程調試會話的過程,為Android App進程創建遠程調試會話。隨後在ActivityThread的代碼里加斷點,並讓system_process中的斷點恢復執行,就可以單步調試ActivityThread的代碼了。

Android App進程初始化過程中出現的問題,常可以採用這種方式來調試。


DroidVM之調試Android Framework


前面 幾個答主說的都很好,我今天剛好也寫了一篇小白文,提主可以看看,寫的比較亂,有看不懂的可以繼續聊啊

Android 調試系統代碼(非SDK source裡面的內容) - 知乎專欄


推薦閱讀:

為什麼 Linus Torvalds 用 Fedora 而不用 Ubuntu 或 Windows?
Linux 下進行 PHP 開發,相比 Windows 環境有哪些好處?
代碼使用std::thread,使用-static -lpthread靜態編譯後,運行段錯誤的問題?
C語言中在一個函數中定義另一個函數是否可編譯並運行?
如何選購用於ArchLinux的筆記本?

TAG:Linux | Android開發 | Android | AndroidStudio |