大家好,我是光源。
距離上次寫博客已有大半年捌蚊,時(shí)間真是如突如其來(lái)的 bug —— 一刻不停歇。這段時(shí)間雖全身心撲在工作上近弟,但仍然保持了記錄的習(xí)慣缅糟,博客相關(guān)的 todo list 已經(jīng)老長(zhǎng)。
那么第一篇我想寫寫 debug
藐吮。
看到這里溺拱,性子急的大兄弟估計(jì)就拍案而起,debug
有啥好寫的谣辞,俺們作為祖?zhèn)鲗懘a的人早就會(huì)了迫摔。
的確的確,debug
可謂是基礎(chǔ)中的基礎(chǔ)了泥从,關(guān)于它的博客不可計(jì)數(shù)句占,那么我為何又想寫這篇博客呢?
我先不直接回答躯嫉,請(qǐng)看以下問題:
一天纱烘,運(yùn)營(yíng)同學(xué)急沖沖拿著現(xiàn)場(chǎng)找到你杨拐,小明,你看這個(gè)數(shù)值怎么不對(duì)啊擂啥。你心想哄陶,這還不容易,我看看幾個(gè)相關(guān)變量的值就行了哺壶。于是你習(xí)慣性地連接手機(jī)到 Android Studio屋吨,想往“甲蟲”那兒點(diǎn)。在千分之一秒內(nèi)你手停下了 —— 重新跑一次可就破壞現(xiàn)場(chǎng)了山宾,那么該如何直接進(jìn)入 debug 呢至扰?
通過不可描述的方式,你進(jìn)入了 debug 模式资锰。你當(dāng)然記得打斷點(diǎn)會(huì)讓線程“卡”在斷點(diǎn)處敢课,但單步調(diào)試、進(jìn)入方法內(nèi)部绷杜、退出方法這些操作隱隱約約有點(diǎn)映像卻又忘了怎么操作直秆?
代碼邏輯比較復(fù)雜,你用單步調(diào)試一步一步往下走接剩,感覺手都快按抽筋了切厘。但又不能用普通斷點(diǎn),程序必須跑到快出問題前才行懊缺。你是否還記得有條件斷點(diǎn)疫稿、異常斷點(diǎn)這類神器?
終于鹃两,你定位到問題所在了遗座。但一堆實(shí)例和屬性,你需要得到的是某些類的某些屬性結(jié)合后的值俊扳,這時(shí)是否知道在變量觀察區(qū)執(zhí)行表達(dá)式途蒋?
Android Studio 如此強(qiáng)大,涉及到的功能當(dāng)然不止以上幾個(gè)問題馋记。但假如你對(duì)這些問題有些遲疑甚至以前都沒聽說過某幾個(gè)詞号坡,那么不妨看下去。全部都熟悉的朋友也不妨看看梯醒,溫故知新宽堆。
debug
不屬于熱門知識(shí),且日常開發(fā)場(chǎng)景下會(huì)用普通斷點(diǎn)調(diào)試也能應(yīng)付大部分 bug茸习,因此大多數(shù)人其實(shí)都是不熟悉的畜隶。所謂“磨刀不誤砍柴工”,debug
這把刀磨好了絕對(duì)可以大幅提高你的調(diào)試水平。廢話說得夠多了籽慢,下面進(jìn)入正題浸遗。(注:筆者用的是 Android Studio 2.4 Preview,不同 Anroid Studio 版本可能有微小差異)
一箱亿、 進(jìn)入 debug 模式的兩種姿勢(shì)
第一種是點(diǎn)擊運(yùn)行按鈕旁邊的“綠色甲蟲”(debug app)開始以調(diào)試模式編譯運(yùn)行跛锌。
這個(gè)方式的特點(diǎn)是,一開始就進(jìn)入了調(diào)試模式届惋。適合希望盡早進(jìn)入調(diào)試模式的場(chǎng)景察净,比如你想從頭開始追蹤問題,或者斷點(diǎn)在啟動(dòng)頁(yè)或首頁(yè)之類的盼樟。它的弊端是每次需要從頭跑一遍,且由于調(diào)試模式下應(yīng)用程序略卡頓锈至,等你到達(dá)調(diào)試頁(yè)面時(shí)會(huì)覺得老費(fèi)勁晨缴。
第二種方式是在手機(jī)或模擬器已經(jīng)跑起來(lái)應(yīng)用程序后點(diǎn)擊 Run
-> Attach debugger to Android process
,選擇應(yīng)用程序主進(jìn)程峡捡,即可進(jìn)入調(diào)試模式击碗。
這種方式的特點(diǎn)是,隨時(shí)隨地自由進(jìn)入調(diào)試模式们拙,不需要重頭開始跑應(yīng)用程序稍途,該方式適合絕大多數(shù)調(diào)試場(chǎng)景。需要注意的是砚婆,假如跑的是 release
版本且有混淆加固之類的話械拍,該方式就無(wú)法正常調(diào)試。
二装盯、 常見的調(diào)試操作
2.1 典型的調(diào)試場(chǎng)景
一個(gè)最常見的調(diào)試場(chǎng)景如下:
- 完成包括打開 Android Studio坷虑、打開需要調(diào)試的類、連接手機(jī)等準(zhǔn)備工作
- 在關(guān)鍵位置
打上普通斷點(diǎn)
埂奈,進(jìn)入調(diào)試模式后觸發(fā)斷點(diǎn)迄损,將當(dāng)前線程阻塞在斷點(diǎn)處 -
單步調(diào)試
,一行一行往下運(yùn)行 - 碰到方法調(diào)用時(shí)
進(jìn)入方法內(nèi)部
- 用單步調(diào)試單步執(zhí)行直到該方法結(jié)束回到方法調(diào)用的下一行账磺,或者直接
退出當(dāng)前方法
- 通過觀察觀察區(qū)的相關(guān)變量芹敌,推斷出問題所在,結(jié)束本次調(diào)試
以上是最常規(guī)的 debug 場(chǎng)景垮抗,下面介紹下調(diào)試界面氏捞。
在 Android Studio 的 debug 標(biāo)簽(假如一開始沒有,等觸發(fā)斷點(diǎn)后自然會(huì)出現(xiàn))中有兩個(gè)面板 debugger 和 console借宵。debugger 又分為 Frames幌衣、Threads 和 Variables 三塊,分別是堆棧內(nèi)容、線程豁护、變量區(qū)哼凯。
在 debugger 標(biāo)簽右邊有一些操作按鈕,是我們常用的調(diào)試操作楚里,下面會(huì)一一介紹断部。(可以用鼠標(biāo)懸停在上面看每個(gè)按鈕的具體名稱)
2.2 設(shè)置斷點(diǎn)
斷點(diǎn)有多種類型,我們這里先只談普通斷點(diǎn)班缎。在每行的最前端單擊一下即可添加斷點(diǎn)蝴光,在斷點(diǎn)上單擊一下是取消斷點(diǎn)。普通模式下斷點(diǎn)只是一個(gè)普通的紅點(diǎn)达址,但假如是在調(diào)試模式下蔑祟,則紅點(diǎn)上會(huì)有一個(gè)“√”或“?”表示該行是否會(huì)被運(yùn)行,例如沉唠,注釋行前的斷點(diǎn)會(huì)是“?”疆虚。
不管怎樣,觸發(fā)斷點(diǎn)肯定是調(diào)試的起點(diǎn)满葛。只有觸發(fā)了斷點(diǎn)径簿,才會(huì)開始阻塞線程(注意是只會(huì)阻塞當(dāng)前線程,這個(gè)后面會(huì)擴(kuò)展一下詳細(xì)介紹)嘀韧,此時(shí)當(dāng)前代碼行會(huì)被藍(lán)色高亮篇亭,觀察面板上的變量也會(huì)顯示當(dāng)前環(huán)境下的值。
2.3 跳到下一個(gè)斷點(diǎn)(F9)
一般情況下锄贷,在調(diào)試時(shí)我們可以根據(jù)我們的經(jīng)驗(yàn)在幾個(gè)關(guān)鍵的位置打上斷點(diǎn)译蒂,這里就需要從一個(gè)斷點(diǎn)直接跳到下一個(gè)斷點(diǎn)。操作是繼續(xù)以 debug 模式運(yùn)行肃叶,快捷鍵是 F9(假如沒改動(dòng)的話應(yīng)該都是這個(gè)快捷鍵蹂随,假如不同,可以把鼠標(biāo)懸浮在該圖標(biāo)上看提示因惭,下同)岳锁。
2.4 單步調(diào)試(F8)
觸發(fā)斷點(diǎn)后,我們當(dāng)然可以通過加 N 個(gè)斷點(diǎn)來(lái)定位問題蹦魔,但在較復(fù)雜的場(chǎng)景里可能沒法判斷出關(guān)鍵點(diǎn)激率。這時(shí)可以在比較靠前的位置添加斷點(diǎn)作為起點(diǎn),一行一行執(zhí)行勿决。操作是單步調(diào)試乒躺,快捷鍵是 F8。
2.5 進(jìn)入方法內(nèi)部(F7)
在單步調(diào)試時(shí)方法調(diào)用語(yǔ)句會(huì)被看做一行低缩,那么如何進(jìn)入方法內(nèi)部呢嘉冒?用“進(jìn)入內(nèi)部”操作曹货,快捷鍵是 F7.
2.6 退出當(dāng)前方法(上檔鍵+F8)
既然有進(jìn)入方法內(nèi)部,就有退出當(dāng)前方法的操作讳推。當(dāng)進(jìn)入某個(gè)方法內(nèi)部后覺得該方法后面的代碼不需要看了顶籽,不需要猛按 F8 讓它們跑完,可以直接退出當(dāng)前方法去到該方法調(diào)用處的下一行银觅。
有了這些操作礼饱,基本可以應(yīng)付常見的 80% 的調(diào)試場(chǎng)景了。但僅僅了解這些究驴,在面對(duì)復(fù)雜場(chǎng)景時(shí)難免效率低下镊绪。下面介紹幾種非常高效的斷點(diǎn)。
三洒忧、 幾種高效斷點(diǎn)
3.1 條件成立時(shí)才觸發(fā)的條件斷點(diǎn)
普通斷點(diǎn)在每次運(yùn)行到時(shí)都會(huì)被觸發(fā)蝴韭,這在多次調(diào)用、有“循環(huán)”的場(chǎng)景會(huì)比較麻煩熙侍,比如循環(huán) 100 次只希望停留在第 98 次万皿。那么此刻就可以用上條件斷點(diǎn)了。
添加條件斷點(diǎn):先在需要的行前左鍵單擊添加普通斷點(diǎn)核行,右鍵點(diǎn)擊該斷點(diǎn)出現(xiàn)對(duì)話框,在“Condition”處填入條件即可蹬耘,條件語(yǔ)法同 Java芝雪,如 i == 98
。點(diǎn)擊 Done综苔,完成添加惩系。這樣當(dāng)條件未滿足時(shí),不會(huì)阻塞程序運(yùn)行如筛;當(dāng)條件滿足時(shí)斷點(diǎn)被觸發(fā)堡牡。
3.2 不會(huì)阻塞應(yīng)用程序的日志斷點(diǎn)
有時(shí)候我們僅僅希望在關(guān)鍵時(shí)刻輸出一些 log,那么我們需要做這些工作:
- 在代碼里加上 log 代碼
- 重新運(yùn)行程序
- 重新觸發(fā)斷點(diǎn)
那么有沒有什么方法可以避免這些繁雜的操作呢杨刨?這時(shí)可以試試日志斷點(diǎn)晤柄。
在條件斷點(diǎn)彈出的對(duì)話框里,將“suspend”設(shè)置為未選中狀態(tài)妖胀,斷點(diǎn)觸發(fā)時(shí)就不會(huì)被阻塞芥颈。此時(shí)對(duì)話框會(huì)多出一些選項(xiàng)。
我們選中“Evaluated and log”赚抡,并在里面填寫希望輸出的日志爬坑,點(diǎn)擊 Done 即可。注意涂臣,日志將輸出到 Console 而非 LogCat盾计。
3.3 被異常觸發(fā)的異常斷點(diǎn)
我們做 debug,一般面對(duì)的都是未崩潰的異常,例如 UI 狀態(tài)不對(duì)署辉、數(shù)值不對(duì)族铆、代碼執(zhí)行邏輯不對(duì)等等。碰到直接 crash 的情況涨薪,我們往往不會(huì)去 debug骑素,而是根據(jù)報(bào)錯(cuò)信息定位到某某行,然后解決問題刚夺。最典型的就是空指針異常了献丑,只要看到報(bào)錯(cuò)位置,基本手到擒來(lái)侠姑。
但日常開發(fā)難免碰到一些給出具體信息和拋異常的位置也沒有頭緒或者未給出具體位置的情況创橄,比如某 API 拋了個(gè)狀態(tài)異常,那么我們可能得去琢磨為何會(huì)狀態(tài)不對(duì)莽红。
這時(shí)還是得調(diào)試妥畏,但我們也不能眼睜睜看著拋出的異常讓應(yīng)用程序崩了,可以用上“異常斷點(diǎn)”安吁。
我們打開斷點(diǎn)管理器醉蚁,可以看到有一類是“Java Exception Breakpoints”,直接勾上是所有異常都會(huì)被觸發(fā)鬼店。
假如只想被某種異常觸發(fā)网棍,我們可以點(diǎn)擊“+”,選中“Java Exception Breakpoints”妇智,然后輸出該種異常即可滥玷。
有朋友可能注意到除了 “Java Exception Breakpoints” 外還有一個(gè) “Exception Breakpoints”,兩者區(qū)別主要是前者只支持 Java 本身的異常巍棱,后者可以支持自定義異常惑畴。
3.4 追蹤關(guān)鍵點(diǎn)的字段斷點(diǎn)和方法斷點(diǎn)
在諸如多線程等復(fù)雜場(chǎng)景下,超多的變量和超多的類再加上一些 native 方法和第三方庫(kù)航徙,調(diào)試的復(fù)雜度也直線上升如贷。
這時(shí)可以嘗試以點(diǎn)破面,抓住關(guān)鍵方法或字段來(lái)追蹤應(yīng)用程序的運(yùn)行軌跡到踏。于是關(guān)注某字段的字段斷點(diǎn)和關(guān)注某方法的方法斷點(diǎn)就派上用場(chǎng)了倒得。
顧名思義,字段斷點(diǎn)的觸發(fā)條件是字段值被更改夭禽,方法斷點(diǎn)是方法被調(diào)用霞掺。
添加方式:在字段那行、在方法聲明的那行單擊即可讹躯。
3.5 注意事項(xiàng)
除了以上這些菩彬,還有其他斷點(diǎn)未列出來(lái)缠劝,比如臨時(shí)斷點(diǎn)等∑睿考慮到這類斷點(diǎn)實(shí)用性不強(qiáng)惨恭,就不多加說明,有需求的朋友自然會(huì)去了解耙旦。
需要說明的是脱羡,給斷點(diǎn)命名只是為了方便交流,各類型之間并沒有特別邊界免都。 實(shí)際上锉罐,斷點(diǎn)只是某些操作的集合而已。比如日志斷點(diǎn)是“不阻塞”和“輸出日志”兩個(gè)操作的集合绕娘,那么我們當(dāng)然可以加上“設(shè)置條件”操作變成“條件日志斷點(diǎn)”脓规,諸如此類。我們?cè)O(shè)置斷點(diǎn)的面板是允許我們將多種斷點(diǎn)條件混合使用的险领。
所以侨舆,斷點(diǎn)名稱和類型不重要,重要的是針對(duì)現(xiàn)場(chǎng)選用合適的操作绢陌。
四 調(diào)試中的變量
在設(shè)置了合適的斷點(diǎn)后挨下,我們就可以進(jìn)行下一步操作 —— 觀察變量,準(zhǔn)確的說是觀察變量的值脐湾。
4.1 變量觀測(cè)面板
眾所周知复颈,應(yīng)用程序在運(yùn)行期間元素都處于一種動(dòng)態(tài)狀態(tài),此刻你是無(wú)法觀測(cè)到具體變量的值的沥割。只有當(dāng)動(dòng)態(tài)變成靜態(tài),即阻塞住應(yīng)用程序凿菩,才能開始變量觀測(cè)机杜。
這個(gè)“阻塞”操作也就是上文提到的斷點(diǎn)觸發(fā)。
這里需要特別指出的是衅谷,當(dāng)需要追蹤某一個(gè)特定變量時(shí)椒拗,字段斷點(diǎn)是一大利器。
如上圖所示获黔,變量觀察面板會(huì)列出所有當(dāng)前能訪問到的成員變量和局部變量蚀苛。
點(diǎn)擊變量前的箭頭,可以將該實(shí)例展開玷氏,列出所有字段堵未。
4.2 Add New Watch
將所有變量、所有字段列出來(lái)是比較直觀盏触,但當(dāng)我們要去獲取某些屬性時(shí)就略蛋疼了渗蟹。
比如块饺,獲取某成員變量 View
的第一個(gè) child
的 measuredHeight
。假如是靠手動(dòng)去一個(gè)個(gè)“打開”屬性列表雌芽,拿得多麻煩授艰。
又比如,我們需要獲取到兩個(gè)屬性相加后占另一個(gè)值的百分比世落。先去找到這兩個(gè)屬性的值然后額外拿計(jì)算器計(jì)算淮腾?
這時(shí)就可以用上 "Add New Watch"了,添加一個(gè)觀察表達(dá)式屉佳。通俗點(diǎn)就是在變量觀測(cè)區(qū)執(zhí)行一個(gè)表達(dá)式并得到它的值谷朝。
在面試左側(cè)點(diǎn)擊綠色“+”,或者點(diǎn)擊右鍵在菜單中選擇“New Watch”, 就會(huì)出現(xiàn)一個(gè)框忘古,輸入表達(dá)式即可徘禁。
舉個(gè)例子:
在示例中輸入toolbar.getChildAt(0).getMeasuredHeight()
,可得到如圖的結(jié)果髓堪。
4.3 設(shè)置變量的值
變量的值除了能被觀察外送朱,還可以在運(yùn)行時(shí)改變。 這個(gè)可以說是超酷的黑科技了干旁。試想驶沼,碰到一個(gè) if else
時(shí),我們可以很輕松無(wú)成本地通過更改變量的值争群,讓應(yīng)用程序能跑到我們期望的分支上回怜。
設(shè)置變量的值有兩種方式:方式一是在變量觀測(cè)區(qū)右鍵單擊變量,在菜單中選中“set value”换薄;方式二是鼠標(biāo)懸浮在代碼區(qū)中的某個(gè)可訪問的變量上玉雾,在彈出的浮層里更改值。
五 犀利的小功能
除了以上那些常規(guī)的操作轻要,還有一些“還有這種操作”的小功能复旬。
5.1 Force Run to Cursor
該操作可以忽視已存在的斷點(diǎn),直接跳到光標(biāo)所在的行冲泥。有種脫離斷點(diǎn)驹碍、繁瑣操作的束縛輕松自由的感覺有木有。
5.2 Drop frame
經(jīng)過不懈努力凡恍,終于快到出錯(cuò)的那個(gè)時(shí)刻啦志秃,你眉飛色舞手指按得飛快,結(jié)果小手一抖嚼酝,多按了幾個(gè) F8 (單步調(diào)試快捷鍵)浮还,錯(cuò)過了出錯(cuò)的那個(gè)時(shí)刻。如果能回退到方法執(zhí)行前闽巩?
Drop frame 功能能讓你回到當(dāng)前方法被調(diào)用的地方碑定,并且當(dāng)前上下文所有變量也都恢復(fù)到方法調(diào)用前 —— 時(shí)光回溯有木有流码?
當(dāng)然別太興奮,由于 DalvikVM 和 Android Runtime (ART) 不支持延刘,大部分情況下你是用不到的漫试,那個(gè)按鈕長(zhǎng)期處于不可點(diǎn)擊狀態(tài)。但如果你是在跑 JUnit 測(cè)試的話碘赖,是可以用上的驾荣。
5.3 Log
在多線程環(huán)境下,光靠 debug 是不行的普泡。有時(shí) debug 本身會(huì)帶來(lái)一些問題混淆了現(xiàn)場(chǎng)播掷,比如因?yàn)?debug 時(shí)的卡頓造成環(huán)境不一致等等,這時(shí)應(yīng)該學(xué)會(huì)使用打日志的形式幫忙調(diào)試撼班。
5.4 布局查看器
在 Android Studio 的 Android Device Monitor 里有一個(gè) “Dump View Hierarchy for UI Automator” 功能歧匈,可以看到當(dāng)前手機(jī)上應(yīng)用的具體布局情況 —— 甚至布局元素的屬性都會(huì)一一給出(僅可用于可調(diào)試的應(yīng)用)。對(duì) UI 調(diào)試非常有幫助砰嘁。
六 后記
許久沒寫博客件炉,這篇寫得好累。矮湘。想寫的文字倒是挺順暢斟冕,但截圖、傳圖實(shí)在麻煩缅阳。
如你所見磕蛇,這篇博客是以“Android 朝花夕拾” 開頭的,我期望將它寫成系列 —— 假如我堅(jiān)持下去的話十办。
該系列主要是想寫一些重要但日常開發(fā)可能不太重視的東西秀撇,或者足夠重視但又沒花時(shí)間去研究的東西,比如線程調(diào)度向族、比如存儲(chǔ)呵燕,我們除了在初學(xué)時(shí)會(huì)看看這些內(nèi)容外,在技術(shù)不斷提高后是否有回頭整理這些東西呢炸枣?是否真正對(duì)它們了如指掌呢?
往往是一些最常用最熟悉的東西讓我們踩坑弄唧。那么我就想寫寫這些東西适肠。廢話不多說,先這樣候引。