對(duì)于應(yīng)用開發(fā)者而言蠢笋,衡量應(yīng)用成功最好的指標(biāo)就是開心的用戶雇卷,而且是越多越好。達(dá)到這一目的的最佳途徑就是開發(fā)一個(gè)好應(yīng)用鲸睛,那么什么樣的應(yīng)用才能被稱作是 “好” 應(yīng)用呢娜饵?歸根結(jié)底就是兩件事:功能以及應(yīng)用質(zhì)量。前者取決于開發(fā)者的創(chuàng)造力以及選用的商業(yè)模型官辈;而后者則能夠被客觀測量及改善箱舞。
去年谷歌進(jìn)行的一項(xiàng)內(nèi)部調(diào)查顯示 Play Store 中超過 40% 的一星應(yīng)用存在穩(wěn)定性問題遍坟。另一方面,對(duì)于性能卓越的應(yīng)用晴股,人們打分和評(píng)論往往越來越好愿伴,這讓它們?cè)?Google Play 中的排名上升,下載量也隨之增加电湘。不僅如此公般,用戶參與度也更高,而且愿意花更多的時(shí)間和金錢在這些應(yīng)用上胡桨。
因此,解決應(yīng)用穩(wěn)定性問題能夠顯著影響到應(yīng)用成功與否瞬雹。
通過對(duì)應(yīng)用質(zhì)量的客觀測量昧谊,開發(fā)者能夠輕易發(fā)現(xiàn)應(yīng)用亟待解決的穩(wěn)定性問題,為此我們?cè)?Google Play Console 添加了一款名為 Android vitals 的新板塊酗捌。借助 Android vitals呢诬,開發(fā)者無須添加額外工具代碼或者庫就能了解應(yīng)用存在的性能及穩(wěn)定性問題。當(dāng)應(yīng)用在大量設(shè)備上運(yùn)行時(shí)胖缤,Android vitals 會(huì)收集與應(yīng)用性能相關(guān)的匿名數(shù)據(jù)尚镰。通過這種途徑獲得的信息量是其他方式無法匹及的,即使是硬件實(shí)驗(yàn)室測試也不行哪廓。
Android vitals 可以向開發(fā)者發(fā)送以下三種警告:崩潰狗唉、應(yīng)用程序無法響應(yīng)以及渲染次數(shù)。這三種情況都會(huì)直接影響到用戶體驗(yàn)以及他們對(duì)應(yīng)用的評(píng)價(jià)涡真。此外分俯,用戶可能不會(huì)將 “異常耗電事件” 這類不良行為與您的應(yīng)用直接聯(lián)系起來。
這篇文章將探討其中以下兩個(gè)問題:
1.?過度喚醒:過度喚醒會(huì)對(duì)電池壽命造成影響哆料,而且在無法及時(shí)充電的情況下缸剪,可能導(dǎo)致用戶無法繼續(xù)使用設(shè)備。此類行為可能會(huì)讓用戶迅速卸載您的應(yīng)用东亦;
2.?應(yīng)用程序無法響應(yīng) (ANR) 事件:當(dāng)應(yīng)用的用戶界面卡住時(shí)候杏节,此類事件會(huì)被觸發(fā)。在界面凍結(jié)時(shí)典阵,若您的應(yīng)用在前臺(tái)運(yùn)行奋渔,會(huì)出現(xiàn)對(duì)話框提醒用戶 “關(guān)閉應(yīng)用” 或者 “等待響應(yīng)”。對(duì)用戶而言萄喳,此類行為和應(yīng)用崩潰一樣糟糕卒稳。他們可能不會(huì)馬上卸載您的應(yīng)用,但是如果 ANR 問題一直不解決他巨,就很有可能會(huì)尋找其它替代應(yīng)用充坑。
過度喚醒
那么减江,什么是喚醒?什么時(shí)候又是喚醒 “過度” 呢捻爷?
為了延長電池續(xù)航時(shí)間辈灼,屏幕關(guān)閉后,Android 設(shè)備會(huì)禁用主 CPU 內(nèi)核也榄,進(jìn)入深度睡眠模式巡莹。除非用戶喚醒設(shè)備,設(shè)備最好可以盡可能長地保持這種狀態(tài)甜紫。不過降宅,在發(fā)生某些事件的情況下,還是很有必要喚醒 CPU 并向用戶發(fā)出警告 —— 比如說囚霸,鬧鐘觸發(fā)或者收到新消息腰根。開發(fā)者可以通過喚醒鬧鐘 (wakeup alarm) 來處理此類警告,不過也不一定非要這么操作拓型,在下文中會(huì)對(duì)此稍加解釋额嘿。到目前為止,喚醒看上去似乎是個(gè)不錯(cuò)的東西劣挫,讓重要事情能引起用戶注意册养,不過要是喚醒太多次就適得其反,電池壽命也會(huì)大打折扣压固。
Android vitals 如何顯示過度喚醒
Android vitals 能夠幫助開發(fā)者了解自己的應(yīng)用是否存在喚醒次數(shù)太多的問題球拦。通過收集有關(guān)應(yīng)用行為的匿名數(shù)據(jù),Android vitals 可以顯示有多少比例的用戶在設(shè)備滿電之后帐我,每小時(shí)經(jīng)歷 10 次以上的設(shè)備喚醒刘莹。關(guān)鍵就是看有沒有紅色的圖標(biāo)出現(xiàn),若圖標(biāo)出現(xiàn)焚刚,則說明應(yīng)用已經(jīng)越過了不良行為門檻点弯,屬于 Google Play 中表現(xiàn)最次的一檔應(yīng)用,而您則須要想辦法改善應(yīng)用行為了矿咕。
除了喚醒鬧鐘抢肛,還有別的方法嗎?
開發(fā)者主要是通過 AlarmManager API 設(shè)定 RTC_WAKEUP 或 ELAPSED_REALTIME_WAKEUP 旗標(biāo)碳柱,讓應(yīng)用在特定時(shí)間或者在某一時(shí)間間隔后喚醒設(shè)備捡絮。該功能須謹(jǐn)慎對(duì)待,僅在沒有其它更優(yōu)的任務(wù)調(diào)度和通知機(jī)制的情況下才可使用莲镣。在使用喚醒鬧鐘的時(shí)候福稳,您需要考慮以下幾點(diǎn):
>>若您需要顯示信息以響應(yīng)來自網(wǎng)絡(luò)的數(shù)據(jù),考慮通過使用 Firebase Cloud Messaging 等工具來實(shí)現(xiàn)消息推送瑞侮。利用該機(jī)制而不是定期輪詢新數(shù)據(jù)的圆,您的應(yīng)用會(huì)僅在需要時(shí)才被喚醒鼓拧。
>> 如果您無法使用消息推送并依賴定期輪詢,考慮使用 JobScheduler 或者 Firebase JobDispatcher (或者使用 SyncManager 來處理賬戶數(shù)據(jù))越妈。它們的 API 等級(jí)比 AlarmManager 高季俩,而且在智能任務(wù)調(diào)度方面具備以下優(yōu)點(diǎn):
-- 批量操作:批量操作任務(wù)而不是多次喚醒系統(tǒng)進(jìn)行操作,這使設(shè)備能更長時(shí)間處于睡眠狀態(tài)梅掠。
-- 標(biāo)準(zhǔn):您可以明確任務(wù)運(yùn)行須滿足的具體標(biāo)準(zhǔn)酌住,如網(wǎng)絡(luò)可用性或者電池充電狀態(tài)。設(shè)定標(biāo)準(zhǔn)能夠避免喚醒設(shè)備以及不必要的應(yīng)用運(yùn)行阎抒。
-- 持續(xù)性以及自動(dòng)退避 —— 繼續(xù)執(zhí)行任務(wù) (即使在重啟后) 并且在失敗的情況能自動(dòng)重試酪我。
-- 低耗電模式 (doze) 兼容性 —— 僅在低耗電模式或者應(yīng)用待機(jī)模式未設(shè)定任何限制的情況下,任務(wù)才能運(yùn)行且叁。
當(dāng)且僅當(dāng)消息推送以及任務(wù)調(diào)度對(duì)您的任務(wù)不適用時(shí)祭示,您才可以利用 AlarmManager 設(shè)定喚醒鬧鐘。換個(gè)角度來說就是谴古,僅當(dāng)您想要在特定時(shí)間觸發(fā)鬧鐘,不考慮網(wǎng)絡(luò)以及其它情況稠歉,喚醒鬧鐘才是必要的掰担。
當(dāng) Android vitals 顯示過度喚醒時(shí),您應(yīng)采取何種對(duì)策怒炸?
為了解決過度喚醒問題带饱,您須要確認(rèn)應(yīng)用在什么地方設(shè)定了喚醒鬧鐘,然后降低這些鬧鐘的觸發(fā)頻率阅羹。
那么如何查看應(yīng)用在哪些地方設(shè)了喚醒鬧鐘呢勺疼?您可以打開 Android Studio 中的 AlarmManager 類,右擊 RTC_WAKEUP 或者 ELAPSED_REALTIME_WAKEUP 域捏鱼,選擇 "Find Usages (查找使用)"执庐,然后您就能看到項(xiàng)目中所有使用到此類旗標(biāo)的事件了僧凰。仔細(xì)查看每一種事件倦微,然后考慮能否改用更為智能的任務(wù)調(diào)度機(jī)制猾骡。
您也可以將 Find Usage (查找使用) 中的范圍設(shè)定為 “Project and libraries (項(xiàng)目和庫)”翅阵,查看依賴項(xiàng)是否在使用 AlarmManager API化借。如果確實(shí)在使用淑倾,那么您應(yīng)該考慮使用別的庫上煤,或者向依賴項(xiàng)開發(fā)人員報(bào)告錯(cuò)誤郎哭。
若您認(rèn)為使用喚醒鬧鐘無法避免藏斩,那么如果您的鬧鐘標(biāo)簽滿足以下要求躏结,Play Console 可以提供更好的分析數(shù)據(jù):
>>在鬧鐘標(biāo)簽中包含包、類或者方法名稱狰域。這也可以幫助您輕松確定在源中的哪些地方設(shè)定了鬧鐘媳拴;
>>不要使用 Class#getName() 給鬧鐘命名黄橘,因?yàn)?Proguard 會(huì)對(duì)此產(chǎn)生混淆。請(qǐng)使用硬編碼字符串禀挫;
>>不要向鬧鐘標(biāo)簽添加計(jì)數(shù)器或者其它唯一標(biāo)識(shí)符旬陡,因?yàn)橄到y(tǒng)可能會(huì)貴去掉這類標(biāo)簽,而且無法將它們計(jì)入有效數(shù)據(jù)內(nèi)语婴。
應(yīng)用程序無法響應(yīng)
那么描孟,什么是應(yīng)用程序無法響應(yīng) (以下簡稱為ANR)?它又是怎么影響到用戶的呢砰左?
對(duì)用戶而言匿醒,ANR 就是指當(dāng)他們?cè)噲D與應(yīng)用進(jìn)行交互時(shí),但界面卡住的事件缠导。界面卡屏幾秒后廉羔,會(huì)出現(xiàn)對(duì)話框讓用戶選擇繼續(xù)等待或者強(qiáng)行停止應(yīng)用。
從開發(fā)者的角度來看僻造,ANR 則是指應(yīng)用運(yùn)行的操作耗時(shí)過久憋他,如磁盤或網(wǎng)絡(luò) I/O,導(dǎo)致主線程阻塞髓削。主線程 (有時(shí)候也被稱為 UI 線程) 主要負(fù)責(zé)響應(yīng)用戶事件以及每秒刷新 60 次屏幕竹挡。因此很關(guān)鍵的一點(diǎn)將任何可能延時(shí)主線程工作的操作轉(zhuǎn)到后臺(tái)線程。
Android vitals 如何顯示應(yīng)用程序無法響應(yīng)立膛?
Android vitals 能收集并利用應(yīng)用 ANR 事件的匿名數(shù)據(jù)揪罕,提供多個(gè)級(jí)別的 ANR 具體報(bào)告。主界面上概述了您應(yīng)用中 ARN 活動(dòng)的概覽信息宝泵,顯示用戶至少經(jīng)歷一次 ANR 事件的日對(duì)話比重好啰,并且提供前一天以及前 30 天的情況的單獨(dú)報(bào)告。同時(shí)也提供了不良行為門檻儿奶。
打開詳情界面框往,即 ANR 比率頁面,您能夠了解不同時(shí)間的 ANR 具體比例闯捎,以及針對(duì)不同應(yīng)用版本搅窿、活動(dòng)名稱、ANR 類別隙券、以及 Android 系統(tǒng)下的 ANR 情況男应。您可以就 APK 版本代碼、支持設(shè)備娱仔、OS 版本以及時(shí)間沐飘,篩選查看這些數(shù)據(jù)。
應(yīng)用程序無法響應(yīng)常見原因
如上文所述,當(dāng)應(yīng)用進(jìn)程影響到主線程時(shí)耐朴,ANR 事件會(huì)被觸發(fā)借卧,而導(dǎo)致這種阻塞現(xiàn)象的原因各有不一,較為常見的有:
>>在主線程上執(zhí)行磁盤或者網(wǎng)絡(luò) I/O筛峭。這是迄今為止導(dǎo)致 ANR 的最常見原因铐刘。雖然大部分開發(fā)者認(rèn)同不應(yīng)該在主線程上進(jìn)行讀寫磁盤或者網(wǎng)絡(luò),但是有時(shí)候我們就是忍不住這么做影晓。在理想情況下镰吵,從磁盤上讀取幾個(gè)字節(jié)的數(shù)據(jù)并不會(huì)引發(fā) ANR,但是這絕對(duì)不是什么好主意挂签。如果用戶的設(shè)備閃存很慢疤祭,如果其它同時(shí)進(jìn)行讀寫的應(yīng)用已經(jīng)對(duì)設(shè)備造成了很大壓力,而您的應(yīng)用還在排隊(duì)等著運(yùn)行 “快速” 讀取操作, 這樣真的不夠明智饵婆,所以千萬別在主線程運(yùn)行 I/O勺馆;
>>在主線程上運(yùn)行長計(jì)算。那么內(nèi)存計(jì)算又是怎么一回事呢侨核?訪問時(shí)間長并不會(huì)對(duì)內(nèi)存造成影響草穆,較小的操作應(yīng)該也沒什么問題。但是如果您開始循環(huán)運(yùn)行復(fù)雜計(jì)算并且處理大數(shù)據(jù)集搓译,主線程就很容易發(fā)生阻塞了悲柱。您可以考慮重新調(diào)整百萬像素大圖像的體積,或者在解析大 HTML 文本塊后侥衬,再將文本顯示到 TextView 中∨芊迹總的來說轴总,還是讓應(yīng)用在后臺(tái)運(yùn)行此類操作比較合適;
>>向主線程另一進(jìn)程同步調(diào)用 binder:與磁盤或網(wǎng)絡(luò)操作相似博个,在線程間進(jìn)行阻塞調(diào)用時(shí)怀樟,程序執(zhí)行會(huì)被轉(zhuǎn)移到您無法控制的地方。如果說其它進(jìn)程忙碌盆佣,該怎么辦往堡?如果須要訪問磁盤或者網(wǎng)絡(luò)以響應(yīng)您的請(qǐng)求,又該怎么辦共耍?此外虑灰,數(shù)據(jù)在轉(zhuǎn)移到其它進(jìn)程前,須要經(jīng)過打包 (parcel) 與解包 (unparcel) 兩個(gè)步驟痹兜,會(huì)消耗不少時(shí)間穆咐。因此,還是建議從后臺(tái)線程進(jìn)行進(jìn)程間調(diào)用;
>>使用同步:即使您將復(fù)雜操作轉(zhuǎn)移到后臺(tái)線程運(yùn)行对湃,依舊須要與主線程溝通以顯示計(jì)算結(jié)果崖叫。多線程編程不容易,并且在使用同步鎖的時(shí)候拍柒,很難保證不出現(xiàn)阻塞執(zhí)行心傀。在最糟糕的情況下,可能會(huì)出現(xiàn)死鎖問題拆讯,即不同線程相互卡死脂男。最好不要自己設(shè)計(jì)同步,建議使用專門的解決方案往果,比如說 Handler疆液,將不可變數(shù)據(jù)從后臺(tái)線程傳回主線程。
如何檢測應(yīng)用程序無法響應(yīng)原因
尋找觸發(fā) ANR 的原因不容易陕贮,我們拿 URL 類舉個(gè)例子:
1. 您想看到 URL#equals (判斷兩個(gè) URL 是否相同的方法) 阻塞線程嗎堕油?SharedPreferences 又怎么處理呢?
2. 如果您是在后臺(tái)讀取數(shù)值的話肮之,您能在前臺(tái)調(diào)用 getSharedPreferences 嗎掉缺?
這兩種情況都很可能導(dǎo)致長時(shí)間阻塞操作。幸好我們有 StrictMode戈擒,不用再自己瞎猜是什么原因?qū)е?ARN 了眶明。在調(diào)試構(gòu)建的時(shí)候,您可以使用這個(gè)工具捕捉主線程上的意外磁盤或網(wǎng)絡(luò)訪問筐高。
您可以在應(yīng)用中使用 StrictMode#setThreadPolicy搜囱,自定義檢查項(xiàng),包括磁盤和網(wǎng)絡(luò) I/O 以及您通過 StrictMode#noteSlowCall 在應(yīng)用中觸發(fā)的慢調(diào)用柑土。同時(shí)蜀肘,您也可以自己選擇讓 StrictMode 通過何種方式告知已檢測到阻塞調(diào)用:應(yīng)用崩潰、日志記錄還是顯示對(duì)話框稽屏?您可參看 ThreadPolicy.Builder class 獲取進(jìn)一步信息扮宠。
一旦您消除主線程上的阻塞調(diào)用,請(qǐng)記得再上傳應(yīng)用至 Play Store 前狐榔,關(guān)閉 StrictMode坛增。
解決過度喚醒以及 ANR 問題能夠提升應(yīng)用質(zhì)量及穩(wěn)定性,提高應(yīng)用評(píng)分薄腻,獲取更多好評(píng)收捣,最終增加下載量。使用 Android vitals 讓您輕松快速地了解應(yīng)用中亟待解決的問題庵楷。發(fā)現(xiàn)并解決代碼中的這些問題可能并不容易坏晦,但是您可以利用工具和技術(shù)有效地完成工作。
點(diǎn)擊這里您可查看Android?和?Google Play?相關(guān)內(nèi)容信息