Tina Tang's Blog

在哪裡跌倒了,就在哪裡躺下來

0%

Android筆記(36)-透過breakpoints來debug

瞭解如何在 debugging 時使用中斷點(breakpoints),並留意特定變數。

到目前為止,大多數的新手 developers 可能已經會使用 Log statements 進行 debugging。完成單元 1 後,您將學到如何解讀 stack traces 和研究 error messages。雖然這兩種工具都是功能強大的 debugging 工具,但現代 IDE 提供更多功能,讓您的 debugging 流程更有效率。

在本課程中,您將瞭解 Android Studio 整合的 debugger、如何暫停執行 app,以及一次執行單行程式碼,找出 bug 的確切來源。此外,您將學會如何使用一項稱為 Watches 的功能以及如何追蹤特定變數,而不必新增特定的 log statements。

學習目標

  • 如何將 debugger 附加到運作中的 app。
  • 使用中斷點(breakpoints)來暫停執行中的 app,逐行檢查程式碼。
  • 條件運算式加入中斷點(breakpoints)以節省 debugging 時間。
  • 在 Watches 窗格中新增變數,以輔助 debugging。

建立新 project

我們不會一開始就對複雜的大型 app 進行偵錯,而是會從空白專案著手並介紹一些錯誤程式碼,藉此展示 Android Studio 中的偵錯工具。

建立新的 Android Studio 專案,即可開始使用。

  1. 在「Select a Project Template」(選取專案範本) 畫面中選擇「Empty Views Activity」(空白活動)。

  2. 將應用程式命名為「Debugging」,接著確認語言已設為 Kotlin,其他項目則保持不變。

  3. 系統將開啟一項新的 Android Studio 專案,並顯示一個名為 MainActivity.kt 的檔案。

回報 bug

還記得單元 1 中 debugging 課程的除以零範例嗎?在迴圈(loop)的最終迭代(iteration)中,app 嘗試執行除以零時,因為不可能除以零,因此 app 當機,並彈出 java.langArithmeticException。藉由檢查 stack trace 找出 bug 並予以解決,並且使用 log statements 驗證該假設。

由於您已經熟悉那個範例,因此將用於示範中斷點(breakpoints)的使用方式。breakpoints 會逐行處理程式碼,不必先新增 log statements 或重新執行 app。

  1. 開啟 MainActivity.kt,並將程式碼替換為以下程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

public val TAG = "MainActivity"

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
division()
}

fun division() {
val numerator = 60
var denominator = 4
repeat(5) {
Log.v(TAG, "${numerator / denominator}")
denominator--
}
}

}
  1. 執行 app。觀察 app 是否如預期當機。

使用 breakpoints debug

瞭解 logging 後,您已學會如何有策略地放置 logs,以便識別錯誤及驗證問題是否已修正。然而,在面對並非您導入的 bugs 時,我們未必能明確得知要將 log statements 放置在何處,或是要輸出哪些變數。通常,您只能在執行階段(runtime)找到這項資訊。

1
2
3
4
5
6
7
8
fun division() {
val numerator = 60
var denominator = 4
repeat(5) {
Log.v(TAG, "${numerator / denominator}")
denominator--
}
}

此時,中斷點(breakpoints)就能發揮功效。即使您已根據堆疊追蹤(stack trace)中的資訊大致推斷出造成 bug 的原因,您仍可在某行程式碼新增 breakpoints 做為停止訊號。一旦觸及 breakpoints,系統就會暫停執行程式碼,讓您可以在執行階段使用其他 debugging 工具,以便詳細瞭解實際情況並判斷問題。

附加 debugger

Android Studio 會在背景使用名為 Android Debug Bridge (ADB) 的工具。這項指令列工具已整合至 Android Studio,可為執行中的 app 提供 breakpoints 等 debugging 功能。進行 debugging 的工具通常稱為 debugger

如要將 debugger 用於或附加至 app,您無法像往常一樣依序點選「Run」>「Run ‘app’」執行 app,而是改為「Run」 >「Debug ‘app’」。

在 project 中新增 breakpoints

請執行下列步驟,觀察 breakpoints 的實際運作情形:

  1. 找到您希望中斷動作的 line number點選旁邊的空白以新增 breakpoint。line number 旁邊會出現一個點,且該行程式碼會被 highlighted
  1. 利用「Run」(執行) >「Debug ‘app’」或工具列中的圖示執行附有 debugger 的 app:
app 啟動後,您會看到像這樣的畫面:

一旦啟動 app,系統會 highlighted 有效的 breakpoint。

先前已開啟 Logcat 視窗的某個畫面底部已開啟「Debug」分頁。

左側是 functions list,與堆疊追蹤(stack trace)中顯示的 list 相同。右側是一個窗格,可讓您查看目前 function (即 division()) 中個別變數(variables)的值(value)。您也可以透過頂端的按鈕,在程式暫停執行期間進行瀏覽。最常用的按鈕是「Step Over」(不進入),用來執行 highlighted 的單行程式碼。

請按照下列步驟操作,對程式碼進行 debug:

  1. 到達 breakpoint 後,第 19 行 (宣告 numerator 變數) 會 highlighted,但尚未執行。使用「Step Over」(不進入) 按鈕執行第 19 行: 現在將 highlighted 第 20 行。
  1. 在第 22 行設定 breakpoint。這就是發生除法的位置,也是堆疊追蹤(stack trace)回報 exception 的一行。
  1. 使用「Debug」視窗左側的「Resume Program」(繼續執行程式) 按鈕,前往下一個 breakpoint:

執行 division() 函式的其餘部分。

  1. 請注意,執行作業在第 17 行停止,未執行該行。
  1. 每個變數 numeratordenominator 的值會顯示在其宣告旁邊。變數值會顯示在「Variables」分頁的 debug 視窗中。
  1. 再按下「Debug」視窗左側的「Resume Program」(繼續執行程式) 按鈕四次。每次迴圈暫停時觀察 numeratordenominator 的值。在最後一個迭代作業中,numerator 應為 60,而 denominator 應為 0。然而將 60 除以 0 並不符合數學規則!

這樣一來,您就能得知造成 bug 的確切程式碼行,並瞭解確切原因。和先前一樣,只要將程式碼重複的次數從 5 變更為 4,即可修正 bug。

1
2
3
4
5
6
7
8
fun division() {
val numerator = 60
var denominator = 4
repeat(4) {
Log.v(TAG, "${numerator / denominator}")
denominator--
}
}

提示:如要移除中斷點(breakpoints),請按一下 line number 旁邊的點。


設定 breakpoints conditions

在上一節中,您必須逐一檢查迭代迴圈,直到分母為 0。在較為複雜的 app 中,如果您的錯誤資訊較少,可能會很繁瑣。不過,如果您有一個假設 (例如,app 只有在分母為零時才會異常終止),則可以修改中斷點,限制只有在符合假設時才會觸及中斷點,這樣就不必逐一檢查迴圈迭代。

  1. 如有需要,請在重複迴圈中將 4 變更為 5,以重新回報錯誤。
1
2
3
repeat(5) {
...
}
  1. 在含有 repeat 陳述式的行上放置新的 breakpoints。
  1. 在紅色中斷點圖示上按一下滑鼠右鍵。選單會顯示數個選項,例如是否已啟用中斷點。停用的中斷點仍然存在,但不會在執行階段觸發。您也可以選擇新增 Kotlin 運算式,如果運算結果為 True,就會觸發中斷點。例如,如果使用 denominator > 3 運算式,則只有在初次疊代迴圈時,才會觸發中斷點。如果只想在 app 可能除以零時觸發中斷點,請將運算式設為 denominator == 0。中斷點的選項會如下所示:
  1. 依序點選「Run」>「Debug ‘app’」執行您的 app,並觀察是否已到達 breakpoints。

如您所見,分母已是 0。只有符合條件時才會觸發 breakpoints,因此您不必逐一檢查程式碼,可節省時間和精力。

  1. 和先前一樣,您會看到錯誤是因為循環執行一行程式碼太多次所造成,此時分母設為 0

新增 Watches

如果想在 debugging 時監控特定值,並不需要在「Variables」分頁中搜尋該值,只要新增所謂的 Watches 即可監控特定變數。這些變數會顯示在 debug 窗格中。當執行暫停該變數位於範圍內時,就會顯示在「Watches」窗格中。如此一來,處理大型 projects 時,就能提高 debugging 效率。您可以集中追蹤所有相關變數

  1. 在「Debug」view 中,「Variables」窗格右側會顯示另一個名為「Watches」的空白窗格。按一下左上角的加號按鈕: 您可能會看到顯示「New Watch」的選單選項。
  1. 在提供的欄位中輸入變數名稱 denominator,接著按一下 enter 鍵。

  2. 依序按一下「Run >「Debug ‘app’」重新執行 app,這時您會發現以下情況:遇到 breakpoint 時,系統會在「Watches」窗格顯示 denominator 的值。