W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
編寫: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警示框來保護程序的長時間無響應。對話框如下:
此時,你的應用已經(jīng)經(jīng)歷過一段時間的無法響應了,因此系統(tǒng)提供用戶可以退出應用的選擇。為你的程序提供良好的響應性是至關(guān)重要的,這樣才能夠避免系統(tǒng)為用戶顯示ANR的警示框。
這節(jié)課描述了Android系統(tǒng)是如何判斷一個應用不可響應的。這節(jié)課還會提供程序編寫的指導原則,確保你的程序保持響應性。
通常來說,系統(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的對話框:
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)的任務。
通常來說,100ms - 200ms是用戶能夠察覺到卡頓的上限。這樣的話,下面有一些避免ANR的技巧:
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: