天天看點

避免Application Not Responding (ANR),保持你的程式能夠得到響應。

ANR可能是monkey測試中最容易出現的問題了。Android程式員面試的時候,也極有可能會談到這個方面。下面的文章主要是對Google Developers的http://developer.android.com/training/articles/perf-anr.html結合自己了解的純人腦翻譯。

Here we go:

你寫的代碼可能成功通過了所有的性能測試,但程式仍可能在特定的情況下遲鈍、挂起或者當機,又或者花了很久去處理輸入。最糟糕的情況莫過于,程式出現了 "Application Not Responding" (ANR) 應用程式無響應的對話框。

避免Application Not Responding (ANR),保持你的程式能夠得到響應。

在Android中,系統檢測到你的程式在一定時間沒有響應就會出現如上的對話框。這時,系統會在一段沒有響應的時間之後,讓使用者可以選擇停止程式。在設計程式的時候,避免程式出現這種情況,是非常關鍵的。這篇文章介紹了“Android如何判定程式是否沒有響應 和 如何保證程式保持響應”。

一、什麼導緻了ANR? 

通常,當程式不能響應使用者的輸入時,系統就會顯示ANR。比如,如果程式阻塞在UI線程中處理I/O(通常是通路網絡),而不能處理使用者輸入事件。又比如, app花了太多時間用于建構記憶體結構或者在UI線程中計算遊戲的下一步動作。讓計算高效的執行很重要,但有時即使再高效的代碼也需要一定時間去運作。是以,你的app在任何需要一個長時間操作的時候,你不應該在UI線程中去執行任務,而應該建立一個工作線程,讓這個線程去執行大部分的工作。 這就保持UI線程(驅動使用者界面的事件循環)保持運作,并且防止系統判定你的程式停止運作。因為這樣的線程(UI線程)處理通常是在類級别完成的,你可以認為“響應”是最進階别的問題(與之相比的基本代碼的運作,是方法級别的)。

在Android中,程式的響應情況由Activity Manager和Window Manager system services監控。當出現如下的任一情況就會顯示ANR對話框:

  • 輸入事件 (比如按鍵和觸屏事件) 5秒鐘沒有響應。
  • 一個

     BroadcastReceiver

     10秒鐘沒有結束執行

二、如果避免ANR?

Android程式通常運作在一個線程之内(預設是“UI線程”或者“主線程”)。 這意味着在UI線程中需要長時間才能結束運作的任務會觸發ANR,因為程式無暇處理輸入事件或intent broadcasts。是以,任何在UI線程中跑的方法應當盡可能少地在相應線程處理任務。特别地,activities 應該盡可能少在關鍵生命周期方法如 

onCreate()

 和

onResume()

。潛在需要長時間運作的操作如網絡、資料庫操作,或者耗費大量計算資源的如重新計算位圖等應該在工作線程中執行。 (或者比如在資料庫中, 通過異步請求)。

更有效的方法是用 

AsyncTask

 類為長時間的操作建立一個工作線程。 簡單地繼承(extend) 

AsyncTask

 和實作(implement) 

doInBackground()

 方法來執行工作。要通知進展變化給使用者時,你可以調用 

publishProgress()

, 它可以援引 

onProgressUpdate()

 回調方法。通過方法 

onProgressUpdate()

 (在UI線程中執行),你可以通知使用者,比如:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do the long-running work in here
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This is called when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}      

要執行這個工作線程,建立一個執行個體并調用 

execute()

:

new DownloadFilesTask().execute(url1, url2, url3);      

盡管比 

AsyncTask

 要複雜,你可能不想用異步處理,轉而建立自己的 

Thread

 或

HandlerThread

 類。如果你這麼做,需要把線程優先級設定為 "background" 優先級, 通過調用

Process.setThreadPriority()

 并傳參 

THREAD_PRIORITY_BACKGROUND

。如果你不這樣降低線程的優先級,那麼這個線程很可能還是會拖慢你的app,因為它和UI線程的優先級預設是一樣的。

如果你使用 

Thread

 或 

HandlerThread

, 要確定你的UI線程不會在等待工作線程完成時阻塞——不要調用 

Thread.wait()

 or 

Thread.sleep()

。為了不用阻塞等帶工作線程完成,你的主線程應該提供一個 

Handler

 讓其他線程在執行完後通知(post back)主線程。這樣設計你的程式,你會讓你的UI線程保持對輸入的響應,因而避免了ANR的對話框(由輸入事件的5秒逾時産生)。

在 

BroadcastReceiver

 執行時間上的特定限制,強調了broadcast receivers應該做的是:在背景,小而離散的工作量,比如儲存一個設定或者注冊一個

Notification

。其他在UI線程中被調用的方法也是一樣,程式應該避免潛在的長時間操作或者broadcast receiver中的長時間計算。把這些強度大的工作交給工作線程,你的程式應該起一個 

IntentService

 如果需要長時間的操作來回應 intent broadcast。

提示 :你可以使用 

StrictMode

 來幫助發現潛在的長時間運作操作如網絡、資料庫操作,而這些操作可能不是你故意在主線程中去做的。

三、加強響應

通常超過100到200毫秒的門檻值,使用者會察覺程式的緩慢。是以,這裡有更多的一些建議讓你不僅僅是避免ANR,而且讓你的程式對使用者來說是響應及時的:

  • 如果你的程式在背景操作,需要對使用者的輸入響應,那麼在界面要去展示執行的進度 (比如在你的UI線程中用一個 

    ProgressBar

     )。
  • 特别是對遊戲而言,在工作線程中計算運動(moves)。
  • 如果你的程式有一個耗時的初始按照階段,可以考慮用一個啟動畫面或者盡可能快的渲染主界面,表明正在加載并且異步地填充資訊,在這兩種情況下,你應該以某種方式表示正在取得進展,以免使用者覺得應用程式當機。
  • 使用性能工具,如 Systrace 和 Traceview 來檢查你程式響應的瓶頸。
上一篇: mysql記事本

繼續閱讀