偵錯
這是「使用 Android Studio 開發 Web 程式」系列的第三篇文章,接續前一篇文章的內容,程式碼照著規格打完了,第一件事當然就是先執行看看成果,如果有出現不符預期的結果,就要進到了偵錯的程序以便修正程式碼。但如果負責的是 Server 端的元件,還要先準備好 Container 的環境、佈署編譯好的檔案、調整設定檔等等的程序。過往大部份人可能都會選擇 Tomcat 做為 Container,而現在有一個輕量化的選擇 Jetty。Jetty 已在 Gradle 的支援範圍內,透過 Gradle 的 Jetty Plugin 來執行 jettyRun Task 就可以直接啟動 Jetty,並透過瀏覽器來檢視程式執行後的網頁結果,不需要再加外進行安裝 Jetty 的動作。
使用 Jetty Plugin 的 build.gradle 檔案內容,示範如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apply plugin: "war" | |
apply plugin: "jetty" | |
dependencies { | |
compile "javax.servlet:servlet-api:2.5" | |
} | |
httpPort = 8080 | |
stopPort = 9090 | |
stopKey = "stopKey" |
以下是使用 Android Studio 的 Terminal 視窗下指令後顯示的結果(執行前要先切換到專案所在的目錄):
如果執行成功,就可以依照結果上出現的網址輸入在瀏覽器裡,瀏覽器會顯示程式執行的結果。另外,由於 Android Studio 產生的 Gradle 專案預設會使用 Gradle Wrapper,Wrapper 會被產生在專案的相同目錄下。執行時是使用名稱為 gradlew 的 Wrapper,在 Windows 的平台是批次檔、非 Windows 的平台則為 Shell Script,Shell Script 要先設定執行的權限。透過 Wrapper 會自動下載 Gradle 的程式後執行,所以不用先行安裝 Gradle,但是如果對 Gradle 版本有特別要求的話,則要改為使用自行安裝 Gradle 的方式。
只是要在 IDE 中設中斷點,並且在瀏覽器提出要求的過程中觸發中斷,還需要在命令列中增加以下的參數:
—Dorg.gradle.daemon=true -Dorg.gradle.jvmargs="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
第一個參數是要 Gradle 以 Daemon 模式運作,Daemon 模式下會重用 Process、減少 Gradle 程式啟動的成本,可以加快每一次執行 Gradle 指令時的建置速度。當只有第二個參數被使用時,第一個參數是輸出時會提示的選項,同時也是官網建議在進行開發、偵錯時使用的選項。如果是使用 Android Studio 來啟動 Task 則不用特別設定,預設就是使用 Daemon 模式。
第二個參數是開啟 JVM 偵錯功能,讓 Debugger 可以透過 5005 Port 與 JVM 建立偵錯所需的通訊機制。"address=5005" 所指定的數字可以更改成任意沒有被佔用的 Port,但必須要與 Debugger 所設定的 Port 相同。
完成了準備的工作後,接下來就要讓 Android Studio 的 Debugger 知道偵錯目標的資訊以便進行偵錯的工作。點選 Android Studio 的選單功能【Run -> Edit Configurations...】會出現「Run/Debug Configurations」視窗。
在「Run/Debug Configurations」視窗左上方點選【+】按鈕,選擇「Remote」類型,在下方清單的 Remote 分類就會多出一個項目,設定的畫面如下方所示。
所輸入的「Name」會被顯示在 Toolbar 中【Run/Debug Configuration】的下拉清單中。由於同時啟動多個 Configuration 的執行個體會有 Port 被佔用的問題,所以勾選了「Single instance only」選項。
在 Configuration 頁籤中,上半部是提示要設定 JVM 進行遠端偵錯的參數範例,不同時期的 JDK 有不同的參數規格,每一個範例右方有複製按鈕,可以直接複製到剪貼簿中,省去打字的麻煩。
「Settings」群組內的選項,如果是在開發階段於本機進行偵錯可以使用預設的內容。但如果是在系統測試或正式環境中偵錯,Host 值應該要依據實際的 Server 名稱或 IP 位址填入。Port 則是依據 JVM 參數所下的 address 數值做調整。
完成以上的設定,就可以在 Terminal 視窗中執行之前示範並加上參數的指令,待等 Jetty 啟動。回到程式碼編輯視窗設定好正確的中斷點位置,接著在 Toolbar 中,選擇【Run/Debug Configuration】的下拉清單裡剛才設定的 Remote 類型的項目,按下右方 Debug 圖示的按鈕或 Shift+F9 的快速鍵。
如果一切順利,會在 Android Studio 下方的 Console 視窗中看到以下訊息:
Connected to the target VM, address: 'localhost:5005', transport: 'socket'
代表 Debugger 已經成功的和 Jetty 的 JVM 連結上,可以開始進行偵錯的作業。此時就可以啟動瀏覽器,輸入網址,以便執行程式。當執行到所設定的中斷點就會和其他專案一樣,IDE 就會停在中斷點對應的程式碼上,並且可進行觀察變數內容、單步執行等操作。
到這裡雖然工作已經可以進行了,但是每一次執行偵錯都要先下一次指令太麻煩了,而且當程式碼有修改,則 Jetty 要重啟才會執行新的內容。然而先前所下的 gradlew 指令會 Block 住 Terminal,要先按下 Ctrl+C 之後才會關閉 Jetty,也才能再下一次啟動 Jetty 的指令。
gradlew 指令會 Block 住的情況如果不消除,對以後自動化建置的作業也會造成問題、導致自動化無法進行。所幸 Jetty Plugin 也有提供 Daemon 參數,可以消除 jettyRun 執行後會 Block 住的情況。為了後續自動化建置也可以一體套用,所以在專案目錄的 build.gradle 裡加上以下的內容:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
jettyRun.daemon = true |
加完了之後,可以先在 Terminal 試看看,果然可以直接回到提示字元。但這時要記得再執行 jettyStop 的 Task,不然 Jetty 沒有關閉,下次執行 jettyRun 時會出現 Port 被佔用的錯誤訊息。
接下來為了確保 jettyRun 在執行前,Jetty 都有確實做關閉的程序,所以要在 build.gradle 裡設定 jettyRun 執行之前要先執行 jettyStop。但目前示範使用的 Gradle 2.2.1 有一個 Bug,使用 Daemon 模式的 Jetty 無法被 jettyStop 正常地關閉,參考官方的解決方法後,必須要新增的內容如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.gradle.api.plugins.jetty.internal.Monitor | |
[jettyRun, jettyRunWar]*.doLast { | |
/** | |
* THIS IS A WORKAROUND! THE CURRENT VERSION OF THIS TASK DOESN'T START A WATCHER IN DAEMON MODE | |
* | |
* If starting the monitor fails, it may be because the jetty task was updated to fix this issue | |
* When that happens, we shouldn't need the custom task any more | |
* | |
* Copied From: AbstractJettyRunTask | |
*/ | |
if (getStopPort() != null && getStopPort() > 0 && getStopKey() != null) { | |
Monitor monitor = new Monitor(getStopPort(), getStopKey(), server.getProxiedObject()); | |
monitor.start(); | |
} | |
} | |
[jettyRun, jettyRunWar].each { jetty -> | |
jetty.dependsOn jettyStop | |
} |
剩下的就是讓之前設定好的 Run/Debug Configuration 與 jettyRun 串連起來,先回到「Run/Debug Configurations」視窗,並新增一個 Gradle 類型的 Run/Debug Configuration。在「Name」的欄位裡輸入一個自己可以識別的名稱,同時為了保險起見勾選了「Single instance only」選項,以避免 Debug Port 被不同 JVM Instance 佔住了。
「Gradle project」欄位直接由 Registered Gradle projects 裡挑選對應的 Web 專案,「Tasks」欄位輸入 jettyRun,「VM Option」輸入 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=50005,或是由 Remote 類型中複製後貼上。
這裡補充說明一下,JVM 的參數也可以設定在 gradle.property 裡,gradle.property 可放在以下幾個位置:
- 要套用 JVM 參數的專案其 build.gradle 所在目錄
- 登入使用者 Home 路徑下的 .gradle 目錄
但在命令列中指定,將會覆蓋過 gradle.property 設定的內容,先前的 VM Option 則等同於在命令列中指定。選擇在 Run/Debug Configuration 裡設定是因為只有在 IDE 裡才有 Debug 的需求,如果設在專案目錄的 gradle.property 檔案內,當檔案被 Checkin 到版控系統,可能會被 CI 系統下載進而影響測試或自動化建置的結果。所以為了不影響測試或自動化建置,才會避免在 gradle.property 中進行相關的設定。
最後,點選回到最早建立的 Remote 類型項目,在設定畫面的右下方空白清單中指定剛才新增 Gradle 類型項目為 Before execute。完成後,在【Run/Debug Configuration】的下拉清單指定 Remote 類型項目並點選 Toolbar 上的 Debug 圖示,如果設定無誤就可以看到過程中執行了 jettyStop 及 jettyRun,並把 Debugger 連結到指定的 Port 上一次完成。所以每次重啟偵錯只要一個按鍵,再切換回瀏覽器重新載入頁面即可。
如果不是要偵錯,只是要看執行的結果,可以將【Run/Debug Configuration】的下拉清單切換成 Gradle 類型的項目後,按下執行圖示的按鈕即可。
在 GitHub 上有人提供了一個可以使程序更簡易的 Plugin 叫 Gretty,有興趣可以自行研究看看。接下來會進入到系列文章的最後一篇:測試。
以下為完整的 build.gradle 的內容:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apply plugin: "war" | |
apply plugin: "jetty" | |
import org.gradle.api.plugins.jetty.internal.Monitor | |
[jettyRun, jettyRunWar]*.doLast { | |
/** | |
* THIS IS A WORKAROUND! THE CURRENT VERSION OF THIS TASK DOESN'T START A WATCHER IN DAEMON MODE | |
* | |
* If starting the monitor fails, it may be because the jetty task was updated to fix this issue | |
* When that happens, we shouldn't need the custom task any more | |
* | |
* Copied From: AbstractJettyRunTask | |
*/ | |
if (getStopPort() != null && getStopPort() > 0 && getStopKey() != null) { | |
Monitor monitor = new Monitor(getStopPort(), getStopKey(), server.getProxiedObject()); | |
monitor.start(); | |
} | |
} | |
[jettyRun, jettyRunWar].each { jetty -> | |
jetty.dependsOn jettyStop | |
} | |
dependencies { | |
compile project(":utils") | |
compile "javax.servlet:servlet-api:2.5" | |
} | |
jettyRun.daemon = true | |
httpPort = 8080 | |
stopPort = 9090 | |
stopKey = "stopKey" | |
0 意見:
張貼留言