Android 避免出現(xiàn)程序無響應ANR

2018-08-02 18:28 更新

編寫:kesenhoo - 原文:http://developer.android.com/training/articles/perf-anr.html

可能你寫的代碼在性能測試上表現(xiàn)良好,但是你的應用仍然有時候會反應遲緩(sluggish),停頓(hang)或者長時間卡死(frezze),或者是應用處理輸入的數(shù)據(jù)花費時間過長。對于你的應用來說最槽糕的事情是出現(xiàn)"程序無響應(Application Not Responding)" (ANR)的警示框。

在Android中,系統(tǒng)通過顯示ANR警示框來保護程序的長時間無響應。對話框如下:

anr

此時,你的應用已經(jīng)經(jīng)歷過一段時間的無法響應了,因此系統(tǒng)提供用戶可以退出應用的選擇。為你的程序提供良好的響應性是至關(guān)重要的,這樣才能夠避免系統(tǒng)為用戶顯示ANR的警示框。

這節(jié)課描述了Android系統(tǒng)是如何判斷一個應用不可響應的。這節(jié)課還會提供程序編寫的指導原則,確保你的程序保持響應性。

是什么導致了ANR?(What Triggers ANR?)

通常來說,系統(tǒng)會在程序無法響應用戶的輸入事件時顯示ANR。例如,如果一個程序在UI線程執(zhí)行I/O操作(通常是網(wǎng)絡(luò)請求或者是文件讀寫),這樣系統(tǒng)就無法處理用戶的輸入事件?;蛘呤菓迷赨I線程花費了太多的時間用來建立一個復雜的在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),又或者是在一個游戲程序的UI線程中執(zhí)行了一個復雜耗時的計算移動的操作。確保那些計算操作高效是很重要的,不過即使是最高效的代碼也是需要花時間執(zhí)行的。

對于你的應用中任何可能長時間執(zhí)行的操作,你都不應該執(zhí)行在UI線程。你可以創(chuàng)建一個工作線程,把那些操作都執(zhí)行在工作線程中。這確保了UI線程(這個線程會負責處理UI事件) 能夠順利執(zhí)行,也預防了系統(tǒng)因代碼僵死而崩潰。因為UI線程是和類級別相關(guān)聯(lián)的,你可以把相應性作為一個類級別(class-level)的問題(相比來說,代碼性能則屬于方法級別(method-level)的問題)

在Android中,程序的響應性是由Activity Manager與Window Manager系統(tǒng)服務來負責監(jiān)控的。當系統(tǒng)監(jiān)測到下面的條件之一時會顯示ANR的對話框:

  • 對輸入事件(例如硬件點擊或者屏幕觸摸事件),5秒內(nèi)都無響應。
  • BroadReceiver不能夠在10秒內(nèi)結(jié)束接收到任務。

如何避免ANRs(How to Avoid ANRs)

Android程序通常是執(zhí)行在默認的UI線程(也就是main線程)中的。這意味著在UI線程中執(zhí)行的任何長時間的操作都可能觸發(fā)ANR,因為程序沒有給自己處理輸入事件或者broadcast事件的機會。

因此,任何執(zhí)行在UI線程的方法都應該盡可能的簡短快速。特別是,在activity的生命周期的關(guān)鍵方法onCreate()onResume()方法中應該盡可能的做比較少的事情。類似網(wǎng)絡(luò)或者DB操作等可能長時間執(zhí)行的操作,或者是類似調(diào)整bitmap大小等需要長時間計算的操作,都應該執(zhí)行在工作線程中。(在DB操作中,可以通過異步的網(wǎng)絡(luò)請求)。

為了執(zhí)行一個長時間的耗時操作而創(chuàng)建一個工作線程最方便高效的方式是使用AsyncTask。只需要繼承AsyncTask并實現(xiàn)doInBackground()方法來執(zhí)行任務即可。為了把任務執(zhí)行的進度呈現(xiàn)給用戶,你可以執(zhí)行publishProgress()方法,這個方法會觸發(fā)onProgressUpdate()的回調(diào)方法。在onProgressUpdate()的回調(diào)方法中(它執(zhí)行在UI線程),你可以執(zhí)行通知用戶進度的操作,例如:

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");
    }
}

為了能夠執(zhí)行這個工作線程,只需要創(chuàng)建一個實例并執(zhí)行execute():

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

相比起AsycnTask來說,創(chuàng)建自己的線程或者HandlerThread稍微復雜一點。如果你想這樣做,你應該通過Process.setThreadPriority()并傳遞THREAD_PRIORITY_BACKGROUND來設(shè)置線程的優(yōu)先級為"background"。如果你不通過這個方式來給線程設(shè)置一個低的優(yōu)先級,那么這個線程仍然會使得你的應用顯得卡頓,因為這個線程默認與UI線程有著同樣的優(yōu)先級。

如果你實現(xiàn)了Thread或者HandlerThread,請確保你的UI線程不會因為等待工作線程的某個任務而去執(zhí)行Thread.wait()或者Thread.sleep()。UI線程不應該去等待工作線程完成某個任務,你的UI線程應該提供一個Handler給其他工作線程,這樣工作線程能夠通過這個Handler在任務結(jié)束的時候通知UI線程。使用這樣的方式來設(shè)計你的應用程序可以使得你的程序UI線程保持響應性,以此來避免ANR。

BroadcastReceiver有特定執(zhí)行時間的限制說明了broadcast receivers應該做的是:簡短快速的任務,避免執(zhí)行費時的操作,例如保存數(shù)據(jù)或者注冊一個Notification。正如在UI線程中執(zhí)行的方法一樣,程序應該避免在broadcast receiver中執(zhí)行費時的長任務。但不是采用通過工作線程來執(zhí)行復雜的任務的方式,你的程序應該啟動一個IntentService來響應intent broadcast的長時間任務。

Tip: 你可以使用StrictMode來幫助尋找因為不小心加入到UI線程的潛在的長時間執(zhí)行的操作,例如網(wǎng)絡(luò)或者DB相關(guān)的任務。

增加響應性(Reinforce Responsiveness)

通常來說,100ms - 200ms是用戶能夠察覺到卡頓的上限。這樣的話,下面有一些避免ANR的技巧:

  • 如果你的程序需要響應正在后臺加載的任務,在你的UI中可以顯示ProgressBar來顯示進度。
  • 對游戲程序,在工作線程執(zhí)行計算的任務。
  • 如果你的程序在啟動階段有一個耗時的初始化操作,可以考慮顯示一個閃屏,要么盡快的顯示主界面,然后馬上顯示一個加載的對話框,異步加載數(shù)據(jù)。無論哪種情況,你都應該顯示一個進度信息,以免用戶感覺程序有卡頓的情況。
  • 使用性能測試工具,例如Systrace與Traceview來判斷程序中影響響應性的瓶頸。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號