ANRs (“Application Not Responding”),意思是”應用沒有響應“。
在如下情況下,Android會報出ANR錯誤:
– 主線程 (“事件處理線程” / “UI線程”) 在5秒內沒有響應輸入事件
– BroadcastReceiver 沒有在10秒內完成返回
通常情況下,下面這些做法會導致ANR
1、在主線程內進行網路操作
2、在主線程內進行一些緩慢的磁碟操作(例如執行沒有最佳化過的SQL查詢)
應用應該在5秒或者10秒內響應,否則使用者會覺得“這個應用很垃圾”“爛”“慢”…等等
一些資料(Nexus One為例)
• ~0.04 ms – 通過管道進程從A->B再從B->A寫一個位元組;或者(從dalvik)讀一個簡單的/proc檔案
• ~0.12 ms – 由A->B 再由B->A 進行一次Binder的RPC調用
• ~5-25 ms – 從未緩衝的flash
• ~5-200+(!) ms – 向為緩衝的flash中寫點東西(下面是具體資料)
• 16 ms – 60fps的視頻中的一幀
• 41 ms – 24fps的視頻中的一幀
• 100-200 ms – human perception of slow action
• 108/350/500/800 ms – 3G網路上ping(可變)
• ~1-6+ seconds – 通過HTTP在3G網路上擷取6k的資料
android.os.AsyncTask
AsyncTask 可以與UI線程很方便的配合,這個類可以在後台執行一些操作,並在執行結束的時候將結果發布到UI線程中去,並且無需使用線程或handler來控制。
例子:
private class DownloadFilesTask extends AsyncTask {protected Long doInBackground(URL... urls) { // on some background threadint count = urls.length;long totalSize = 0;for (int i = 0; i < count; i++) {totalSize += Downloader.downloadFile(urls[i]);publishProgress((int) ((i / (float) count) * 100));}return totalSize; } protected void onProgressUpdate(Integer... progress) { // on UI thread!setProgressPercent(progress[0]);} protected void onPostExecute(Long result) { // on UI thread!showDialog("Downloaded " + result + " bytes");}} new DownloadFilesTask().execute(url1, url2, url3); // call from UI thread!private boolean handleWebSearchRequest(final ContentResolver cr) { ...new AsyncTask() { protected Void doInBackground(Void... unused) { Browser.updateVisitedHistory(cr, newUrl, false); Browser.addSearchUrl(cr, newUrl); return null; } }.execute() ... return true; }
AsyncTask要點
1、必須從主線程調用,或者線程中有Handler或Looper。
2、不要在一個可能會被另外一個AsyncTask調用的庫裡面使用AsyncTask(AsyncTask是不可重新進入的)
3、如果從一個activity中調用,activity進程可能會在AsyncTask結束前退出,例如:
- 使用者退出了activity
- 系統記憶體不足
- 系統暫存了activity的狀態留待後用
- 系統幹掉了你的線程
如果AsyncTask中的工作很重要,應該使用……
android.app.IntentService
Eclair(2.0, 2.1)文檔中說:
“An abstract Service that serializes the handling of the Intents passed upon service start and handles them on a handler thread. To use this class extend it and implement onHandleIntent(Intent). The Service will automatically be stopped when the last enqueued Intent is handled.”
有點令人困惑,因此…幾乎沒人用
Froyo (2.2) 的文檔, 又澄清了一下….
android.app.IntentService
“IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
This ‘work queue processor’ pattern is commonly used to offload tasks from an application’s main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.
All requests are handled on a single worker thread — they may take as long as necessary (and will not block the application’s main loop), but only one request will be processed at a time.”
IntentService 的好處
- Acitivity的進程,當處理Intent的時候,會產生一個對應的Service
- Android的進程處理器現在會儘可能的不kill掉你
- 非常容易使用
日曆中IntentService的應用
public class DismissAllAlarmsService extends IntentService {@Override public void onHandleIntent(Intent unusedIntent) {ContentResolver resolver = getContentResolver();...resolver.update(uri, values, selection, null);}}in AlertReceiver extends BroadcastReceiver, onReceive(): (main thread) Intent intent = new Intent(context, DismissAllAlarmsService.class); context.startService(intent);
其它技巧
1、當啟動AsyncTask的時候,立刻disable UI元素(按鈕等等)。
2、顯示一些動畫,表示在處理中
3、使用進度條對話方塊
4、使用一個定時器作為耗時警告,在AsyncTask開始時啟動定時器,在AsyncTask的onPostExecute方法中取消定時器。
5、當不確定要耗時多久的時候,組合使用上述所有方法
總結
- 離開主線程!
- 磁碟和網路操作不是馬上就能完的
- 瞭解sqlite在幹嘛
- 進度展示很好
PS,在視頻講座中,作者還提到,Chrome團隊為了避免Jank(響應逾時而死掉),幾乎所有的功能和任務都會在子線程裡面去做。這一點也值得在Android中借鑒。