原文地址: github.com/zhuanghongj…
寫程式碼不可避免會出現BUG,出現時就需要DEBUG。
如果看日誌分析不出問題所在,可能就需要打斷點去除錯。
本文通過總結Android Studio的一些除錯技巧來加強我們發現並解決BUG的能力,而不是僅僅停留在“斷點單步執行”上。
一、概述
先來看一段程式碼:
上圖中左側是我們打的斷點,因為斷點所在程式碼型別不一樣或斷點設定不一樣,所呈現的圖示也不一樣。
在斷點位置右鍵可對該斷點進行設定,如下圖
斷點大致可分為以下幾類:
- 普通斷點
- 條件斷點
- 日誌斷點
- 方法斷點
- 異常斷點
二、除錯基礎
如何進入除錯模式?
一般來說,下好斷點後我們有兩種方式除錯一個
- Debug App:重新編譯並安裝該應用(上圖左紅圈按鈕)
- Attach Debugger to Android process:點選後需要選擇對應的程式(上圖右紅圈按鈕)
其中第二種方式較為常用(因為不用重新進行編譯),只要執行過程中觸發到斷點就可以直接進入除錯模式。
介紹下上面各個除錯相關按鈕的功能:
三、條件斷點
假設你的斷點設定在一個迴圈列表裡面,但你只對這個列表的某一個元素感興趣,希望迴圈到該元素時才觸發斷點。設定條件斷點也很簡單,在斷點上右鍵彈出並設定你的條件即可。
為該斷點設定的條件(假設我們預期 “i等於7時” 才觸發斷點):
- 皮膚左側:顯示了方法呼叫棧及對應資訊:方法名,行號,類名和包名
- 皮膚右側:顯示了當前各個變數的值
四、日誌斷點
很多時候,除錯是為了列印日誌來定位異常程式碼來縮小範圍,然後再使用斷點找到問題所在。所以,經常要做的事情就是新增日誌程式碼,比如輸出函式引數、返回值或其他一些有用資訊。
如果是通過 新增程式碼 列印相關日誌,就需要重新編譯整個應用,少則幾十秒多則幾分鐘。
如果是通過 日誌斷點 列印相關日誌,就可以完全避免編譯這些毫無意義的等待。
- 將
Suspend
屬性取消勾選(這樣雖然還叫做“斷點”,但程式並不會在該斷點斷下來) - 然後勾選
Log message to console
和Evaluate and log
(這樣就會根據你指定的表示式將資訊列印到控制檯)
最後,通過 Debug App 或 Attack process 方式執行程式。在 Console 皮膚下,不僅可以看到你列印的 斷點日誌,還可以看到 正常Log類列印出來的日誌。如下圖:
五、方法斷點
傳統的除錯方法是以“行”為單位的,即“單步除錯”。
但很多時候我們只關心某個函式的引數或返回值。
使用方法斷點,我們可以再函式級別進行除錯。
設定方法斷點有兩種方式:
- 在方法行打上斷點(注意看,左邊的圖示跟普通斷點的圖示是不一樣的噢)
- 通過斷點設定視窗(View BreakPoints -> Add -> Java Method Breakpoints)
六、異常斷點
在有些情況下,我們只對某些特定的異常感興趣,而且希望程式在發生該異常時就能斷下來,就像儲存現場一樣。Android Studio已經賦予了我們這個能力,即 異常斷點。
具體設定方法:開啟
上圖中我們設定了關心的異常 IndexOutOfBoundsException,下面我們寫一段測試程式碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] strs = new String[]{"0", "1"};
Log.w("BP", strs[2]); // Index out of bounds
}
}複製程式碼
上面這段測試程式碼,如果是直接編譯執行的話會導致App閃退。而如果是通過除錯模式執行的話則會觸發 Java Exception Breakpoints,程式碼編輯器會直接顯示觸發斷點的程式碼,並在 Debug 皮膚上顯示相關資訊,如下圖:
注:如果此時你觸發的是一個NullPointerException,則不會觸發異常斷點(因為還沒有新增到“感興趣”列表中)
有同學會想,如果我捕捉了異常還會觸發異常斷點嗎?
答:即使進行了 try...catch... 捕捉異常,斷點依然會在 catch 之前觸發
還有同學會想,如果我對所有的異常或未知的異常感興趣呢?
答:目前我也沒找到好解決辦法,試了“勾選 Any Exception”、“新增 Exception”、“新增 UndeclaredThrowableException” 這幾種方法,都未能快速定位到異常程式碼,知道的同學可以PR下。
七、Field WatchPoint
前面我們新增“異常斷點”並且點選“加號”後,顯示的第二個項 Java Field Watchpoints 是幹什麼的呢?
有木有這樣一種場景:某個變數的值莫名奇妙地不知道被誰修改了?
Java雖然是值傳遞,但引用也可以是值。所有的物件都存放在堆上面,而堆是被所有執行緒共享的。因此,在複雜情況下,你根本不知道這些共享變數是被誰修改了,也不知道具體的函式呼叫路徑。哥,這很危險。
在多執行緒環境下,不變性是一個很重要的特性,高併發性語言(如 Erlang、Scala 等)都對這種不變性有著一定程度的支援。
廢話了這麼多,現在進入正題。
Field WatchPoint 就是我們解決上面難題的關鍵所在,使用它使得我們可以在某個 Field
被訪問或者被修改的時候觸發斷點,設定方法有兩種:
下面用一段程式碼來實踐下:
public class MainActivity extends AppCompatActivity {
private String mField;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mField = "ABC"; // 1. 修改值
changeFieldMethod1();
}
private void changeFieldMethod1() {
changeFieldMethod2();
}
private void changeFieldMethod2() {
mField = "Android"; // 2. 修改值
Log.i("BP", "mField = " + mField); // 3. 訪問值
}
}複製程式碼
我們對 mField
設定了 Field WatchPoint 並同時勾選了 Field access 和 Field modification,因此在1、2和3的位置都會觸發斷點。當在位置2觸發斷點的時候,Debug皮膚的顯示內容如下圖:
在上圖中我們可以看到函式的具體呼叫路徑,以及未執行觸發斷點程式碼前所觀察變數的值。
八、Evaluate Expression
Evaluate Expression 可以直接理解為“計算表示式的值”。
這也是一個非常實用的功能,可以在斷點處直接進入一個求值環境(前面提到過該功能的按鈕圖示及含義),執行任何你感興趣的表示式或程式碼片段:
九、小結
上面介紹了“各種斷點”、“變數觀察”、“表示式求值”等功能及其相關演示,實際上除錯相關知識遠不止這麼多。
比如,開啟 View BreakPoint 設定視窗,如下圖:
我們可以對感興趣的 特定物件、特定類 進行下斷點,也可以設定 斷點次數 或設定觸發斷點的 特定執行緒 等。
差多不先總結到這裡,想到的話再補充。
參考文章: