前言html
本文翻譯自Android開發者官網的一篇文檔,主要用於介紹ANR相關的一些要點。android
中國版官網原文地址爲:https://developer.android.google.cn/training/articles/perf-anr。數據庫
路徑爲:Android Developers > Docs > 指南 > Best practies > Performance > Keeping your app responsive網絡
正文app
在世界上,編寫能夠贏得每個性能測試的代碼是有可能的,可是仍然感受在某些顯著的時間段內緩慢、掛起或者凍住,或者花費太長的時間來處理輸入事件。可能發生的應用響應上最糟糕的事情是「應用未響應」(ANR)對話框。異步
在Android中,系統經過顯示一個說明您的應用已經中止響應的對話框來防止應用在一段時間內響應不足,就像圖1中的對話框同樣。在這個點上,您的應用已經至關一段時間沒有響應了,因此係統給用戶提供了一個選項來終止該應用。將響應設計到您的應用,來讓系統歷來不給用戶顯示ANR對話框是極其重要的。性能
圖1.展現給用戶的ANR對話框測試
本文描述了Android系統如何決定是否應用是不響應的而且提供指導來確保您的應用保持響應。google
是什麼觸發了ANR?url
通常來講,若是應用不能響應用戶輸入事件,系統會顯示ANR。例如,若是應用在UI線程上的某些I/O操做(頻繁地訪問網絡)上阻塞了,以致於系統沒法處理進來的用戶輸入事件。或者可能應用在UI線程上花費了太多時間來構建一個複雜的內存結構或者在遊戲中計算下一個移動。確保這些計算高效老是很重要的,可是即便是最高效的代碼仍然會花費時間來運行。
在任何您的應用可能執行長時間操做的場景下,您都不該該在UI線程中執行這項工做,而應該建立一個工做線程來處理大部分的工做。這讓UI線程(它驅動用戶接口時間循環)保持運行而且防止系統判定您的代碼已經凍住了。由於這樣的線程一般是在class級別上完成的,因此您能夠把響應當作是一個class問題。(將這和基本的代碼性能進行比較,代碼性能問題是一個方法級別的概念。)
在Android中,應用響應被Activity Manager和Window Manager 系統服務監視,當檢測到有如下條件之一時,Android將會爲特定的應用顯示ANR對話框:
如何避免ANR
Android應用一般所有在一個單一的線程中運行(默認爲「UI線程」或者「主線程」)。這意味着您的應用在UI線程中正在執行的須要花費很長時間來完成的任何任務,均可能觸發ANR對話框,由於您的應用沒有給本身機會來處理輸入事件或者意圖廣播。
所以,任何在UI線程中運行的方法應該儘量作少許的工做。尤爲是,Activity應該儘量少地在如onCreate()和onResume()這樣的關鍵生命週期方法中設置。潛在的諸如網絡操做或者數據庫操做這樣的長時間運行的操做,或者諸如從新設置bitmap大小等這樣昂貴的計算,應該在工做線程中來執行(或者在數據操做的狀況下,經過異步請求)。
爲更長時間的操做建立工做線程最有效的方式是使用AsyncTask類。簡單地繼承AsyncTask而且實現doInBackground()方法來執行工做。爲了將進度改變發送給用戶,您能夠調用publishProgress(),它調用了onProgressUpdate()回調方法。從onProgressUpdate()方法(它在UI線程中運行)的實現,您能夠通知用戶。例如:
1 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { 2 // Do the long-running work in here 3 protected Long doInBackground(URL... urls) { 4 int count = urls.length; 5 long totalSize = 0; 6 for (int i = 0; i < count; i++) { 7 totalSize += Downloader.downloadFile(urls[i]); 8 publishProgress((int) ((i / (float) count) * 100)); 9 // Escape early if cancel() is called 10 if (isCancelled()) break; 11 } 12 return totalSize; 13 } 14 15 // This is called each time you call publishProgress() 16 protected void onProgressUpdate(Integer... progress) { 17 setProgressPercent(progress[0]); 18 } 19 20 // This is called when doInBackground() is finished 21 protected void onPostExecute(Long result) { 22 showNotification("Downloaded " + result + " bytes"); 23 } 24 }
要執行該工做線程,簡單地建立一個實例而且調用excute()方法:
1 new DownloadFilesTask().execute(url1, url2, url3);
您可能但願建立您本身的Thread或者HandlerThread類,雖然這比AsyncTask更加複雜。若是您這樣作,您應該經過調用Process.setThreadPriority()方法而且傳入THREAD_PRIORITY_BACKGROUND值來給「後臺」優先級設置線程優先級。若是您沒有經過這個方法來將該線程設置爲更低的優先級,那麼該線程可能仍然會拉低您應用的速度,由於它默認狀況下會以和UI線程相同的優先級來運行。
若是您實現Thread或者HandlerThread,請確保當正在等待工做線程完成時,UI線程不會阻塞——不要調用Thread.wait()或者Thread.sleep()。當等待工做線程完成時,主線程不該該阻塞,而應該爲其它線程提供一個Handler,當工做線程完成時將其傳回主線程。經過這種方式設計應用將容許應用的UI線程保持對輸入事件的響應,而且這樣避免了5秒的輸入事件超時所引發的ANR對話框。
BroadcastReceiver執行時間的特別限制強調了廣播接收器應該作什麼:小的,離散的後臺工做量,好比保存設置或者註冊通知。因此,當其它方法在UI線程中被調用時,在廣播接收器中應用應該避免潛在的長時間運行操做或者計算。可是,若是潛在的長時間運行的action須要處理來響應intent廣播,您的應用不該該經過工做線程處理密集的任務,而應該經過啓動IntentService。
當BroadcastReceiver對象執行太頻繁時,另一個常見的BroadcastReceiver對象問題會發生。頻繁的後臺執行會下降其它應用可用內存的數量。更多關於如何有效地讓BroadcastReceiver對象有效/失效,請查閱【按要求操做廣播接收器】
★ 提示:您可使用StrictMode來協助找到潛在的長時間運行操做,好比您可能無心間在主線程中執行的網絡或者數據庫操做。
增強響應
通常來講,100到200毫秒時閾值,超過這個閾值用戶將察覺到應用緩慢。因此,在爲了不ANR你應該作的以外,這裏有一些附加的提示,讓您的應用看起來對用戶是響應的:
結語
本文最大限度保持原文的意思,因爲筆者水平有限,如有翻譯不許確或不穩當的地方,請指正,謝謝!