天天看點

《Android UI基礎教程》——2.6節 防止應用程式無響應(ANR)

本節書摘來自異步社群《android ui基礎教程》一書中的第2章,第2.6節 防止應用程式無響應(anr),作者 【美】jason ostrander,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

2.6 防止應用程式無響應(anr)

android ui基礎教程

一個android應用程式運作在它自身的程序之上,是與其他應用無關的沙盒應用。應用被單個線程操控:主線程,或者叫做ui線程。要讓應用能夠快速響應,android限制了函數調用的時間。如果函數超過了它的時間限制,則會出現一個應用程式沒有響應(anr)的對話框,提示使用者選擇繼續等待或者強制關閉應用。你應該不惜任何代價避免anr的出現。當你在主線程上執行長時間的操作時anr會出現,例子包括網絡i/o、磁盤i/o、資料庫查詢以及密集的cpu運算。

提示: 任何時候你收到的android系統的回調函數都是由主線程完成。這包括活動和服務回調函數、時間處理程式、按鍵監聽程式等。記住不要在這些回調函數中執行任何阻塞操作。如果你确實需要執行這樣的操作,開始一個背景線程或者使用asynctask來處 理它。

2.6.1 strictmode

android 2.3推出了一個新的開發者工具,名字叫做strictmode。這個工具會檢測發生在主線程上的磁盤或者網絡操作并且會采取行動來警告開發者。它提供了許多方法來警告開發者,從簡單的日志記錄到應用程式崩潰等。

strictmode并不能保證能夠找到發生在主線程上的所有的磁盤和網絡i/o。尤其是,任何通過java本地接口(jni)産生的通路都不會被檢測到。需要清醒認識到雖然strictmode很有用,但是它并不足以建立及時響應的程式。

下面是一個簡單的檢測所有類型的網絡和磁盤i/o的strictmode。

這将會檢測正在執行的線程上的所有網絡和磁盤i/o,并且會采取兩種行動:列印警告到日志中并對使用者顯示警告對話框。這個例子設定隻能在目前線程設定警告。要檢測任意線程上的沖突,使用setvmpolicy調用:

建議在所有建立的工程中都啟用strictmode。在開發初期捕獲這些例子更好,不會發生大的架構改變。

盡管strictmode對于建立快速響應的應用非常有幫助,在市場上釋出應用時應當禁用它。否則,使用者可能會遇到違反政策的對話框或者甚至會經曆應用崩潰。處理這個的一個簡單方法就是隻在調試模式時啟用strictmode(有一個調試鍵作為簽名)。要檢測一個應用是否運作在調試模式,檢測applicationinfo标志就行。下面的代碼片段會檢測應用是否使用了調試簽名:

2.6.2 背景任務

你将會遇到一種常見情況是需要執行不能在ui線程上運作的長時間操作——比如下載下傳rss訂閱、寫檔案或者運作定時器等。運作這些任務需要許多時間,這将會阻止ui線程的更新。你可以采用一些政策來處理這種情況。通常情況下,你會建立一個能夠執行該任務的新線程,當完成任務之後再更新ui或者應用程式的狀态。下面是實作這一行為的幾個政策。

handler和消息隊列

防止阻塞ui線程的一個好方法是讓任務線程在背景運作。然而,當任務完成時,你常常需要更新ui。對于ui的更新隻能由ui線程執行,否則将會産生異常。可以使用handler類來做到這一點。handler允許你發送在一段時間之後再由其處理的消息。這些消息可以立即進行處理,也可以計劃好在将來的某段時間進行處理。handler在handlemessage方法中處理消息。

預設情況下,一個handler執行個體是綁定在建立它的線程上的(通常是主線程)。綁定handler到ui線程上為異步更新ui提供了一個友善的方法。然而,你同樣可以選擇提供一個可選的looper執行個體,讓handler運作在單獨的線程上。循環類用于為一個線程運作消息循環。通過使用循環類,你可以發送消息并讓它們運作于任何線程執行個體上。

注意: looper類建立和管理一個包含單個線程的所有消息的messagequeue對象。ui線程已經為你建立了一個消息隊列和循環類。

handler具有發送在一段時間之後再進行處理的消息的能力,這一點使得它非常适合實作基于時間的行為。下面是一個timetracker應用将會用到的跟蹤時間間隔的簡單handler:

這段代碼更新了這個activity類的兩個變量,該類被用于儲存目前時間。之後它更新了ui(記住handler回調函數将會預設在ui線程上執行,除非你明确地給出它會運作于另一個線程上)。最後,它指定另一條消息在100毫秒之後執行。sendemptymessage方法也傳入了一個用于區分的整數參數。由于這裡隻有一個單條消息,是以第一個參數被設定為0。使用handler的消息處理的api,你可以為使用定時器建立友善的方法。

1.在timetrackeractivity類中建立一個starttimer方法。這個方法将會記錄目前的系統時間并且發送一條消息給handler,開啟一個定時器。為了阻止開啟定時器兩次,在發送下一條消息時移除任何存在的消息。

2.stoptimer方法從handler的消息隊列中移除所有消息。

3.resettimer方法将會先調用stoptimer,随後往清單擴充卡中添加目前時間,并在清單中展示。

最終,你将會需要知道定時器是否已經被停止。

4.建立一個檢測消息隊列中消息的方法。

你現在明白完成定時器的所有邏輯了。

僅僅為了從一個背景線程中更新ui而建立一個handler的情況很常見。android提供了activity.runonuithread方法,為這種情況提供了一條捷徑。這個方法采用一個runnable并将其發送給ui線程的處理消息的handler。主線程會在空閑時運作在runnable中的代碼。

開始一個背景線程執行一些任務并在任務結束之後更新ui,這種情況很常見。你可以隻使用一個線程來執行這些任務并使用runonuithread方法來将資料展示給使用者。但是如果你需要展示程序将會發生什麼呢?在這些情況下,向ui消息處理的handler發送runnable是大材小用了。android中有一個叫做asynctask的類,這個類是針對這種情況特别設計的。

你可以擴充asynctask類來建立一個簡單的線程,這個線程可以被用來執行背景任務以及在ui線程上列印結果。它包括了在任務被完成前後的ui更新,還有順便進行的程序更新。下面是一個asynctask的基本形式:

傳入任務的3個參數分别用來指定執行時的參數類型、設定程序的參數類型以及當背景任務完成時的傳回結果類型。你可以在任務運作之前使用onpreexecute來更新ui,使用onprogressupdate來更新ui程序的訓示器,使用onpostexecute在任務結束時更新ui。這些方法都運作于主ui線程之上,是以更新視圖并沒有風險。所有的代碼都運作于doinbackground方法中,你可以将其看作僅僅是一個線程的運作方法。

asynctask對于需要更新一個ui元件的快速一次性任務很有用(例如從twitter下載下傳新的文章并把這些文章加載到時間線中去)。