安卓權威編程指南-筆記(第24章 Looper Handler 和 HandlerThread)
AsyncTask是執行後臺線程的最簡單方式,但它不適用於那些重復且長時間運行的任務。
1. Looper
Android中,線程擁有一個消息隊列(message queue),使用消息隊列的線程叫做消息循環(message loop)。消息循環會循環檢查隊列上是否有新消息。
消息循環由線程和looper組成,Looper對象管理著線程的消息隊列。
主線程就是個消息循環,因此也擁有Looper,主線程的所有工作都是由其looper完成的,looper不斷的從消息隊列中抓去消息,然後完成消息指定的任務。
2. Message
消息是Message類的一個實例,它有好幾個實例變量,其中有三個需要在實現時定義。
1.What:用戶定義的int型消息代碼,用來描述消息。
2.obj:隨消息發送的用戶指定對象。
3.target: 處理消息的Handler。
3. Handler
Message的目標(target)是Handler類的一個實例,Handler可看作message handler的簡稱,創建Message時,它會自動與一個handler相關聯,message待處理時,Handler對象負責觸發消息處理事件、
Handler不僅僅是處理Message的目標,也是創建和發布Message的接口。
4. 關系
Looper擁有Message收件箱,所以Message必須在Looper上發布或處理。為與Looper協同工作,Handler總是引用著它。
一個Handler僅與一個Looper相關聯,一個Message也僅於一個目標Handler(也稱作Message目標)相關聯。Looper擁有整個Message隊列,多個Message可以引用同一目標Handler。
多個Handler也可與一個Looper相關聯,這意味著一個Handler的Message可能與另一個Handler的Message存放在同一消息隊列中。
5. 使用Handler
通常不需要手動設置消息的目標Handler。創建信息時,調用Handler.obtainMessage()方法。當傳入其他消息字段給他時,該方法會自動設置目標給Handler對象。
為避免創建新的Message對象,Handler.obtainMessage()方法會從公共循環吃獲取消息。
一旦取得Message,就可以調用sendToTarget()方法將其發送給它的Handler,然後Handler會將這個Message放置在Looper消息隊列的尾部。
Looper取得消息隊列中的特定消息後,會將它發送給消息目標去處理。消息一般是在目標的Handler.handleMessage()實現方法中進行處理。
6. HandlerThread
HandlerThread類幫我們完成了建立looper的過程,只要繼承它就能省去一些工作。
public class ThumbnailDownloader<T> extends HandlerThread { private static final String TAG = "ThumbnailDownloader"; private static final int MESSAGE_DOWNLOAD = 0; //標識下載請求 private Boolean mHasQuit = false; private Handler mRequestHandler; //存儲對Handler的引用,這個Handler負責在ThumbnailDownloader後臺線程上管理下載請求消息隊列。這個Handler也負責從消息隊列裏取出並處理下載請求消息。 //onLooperPrepared()在Looper首次檢查消息隊列之前調用的。 @Override protected void onLooperPrepared(){ mRequestHandler = new Handler(){ @Override public void handleMessage(Message msg){ //隊列中的下載消息取出並可以處理時,就會觸發調用Handler.handleMessage()方法。 //處理操作 } }; } public void queueThumbnail(T target, String url) { //當傳入其他消息字段給它時,該方法會自動設置目標給Handler對象(obtainMessage) //sendToTarget()方法將Message發送給它的Handler,然後Handler會將這個Message放置在Looper消息隊列的尾部。 mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD,target).sendToTarget(); } public void clearQueue(){ mRequestHandler.removeMessages(MESSAGE_DOWNLOAD); } }
主線程中這樣調用:
mThumbnailDownloader = new ThumbnailDownloader<>(responseHandler); mThumbnailDownloader.start(); mThumbnailDownloader.getLooper(); //在start()方法之後調用getLooper()方法是一種保證線程就緒的處理方式。可以避免潛在競爭。 // 在需要的時候調用 mThumbnailDownloader.queueThumbnail(holder, url);
7.線程交互
主線程現在能夠適時調用這個線程的方法,用於下載圖片了。但是還存在一個問題,那就是下載線程下載完一個任務以後如何更新視圖呢?我們知道 UI 只能在主線程裏更新,所以我們采用在主線程裏聲明一個 Handler,傳遞給下載線程,讓下載線程在下載完成後在主線程執行更新操作。因為不能直接引用主線程的方法,故而在這裏用到了回調。
7.1下載線程中:
// ThumbnailDownloader,也就是下載線程中 // 成員聲明 private Handler mResponseHandler; private ThumbnailDowloadListener<T> mThumbnailDownloadListener; // 回調接口 public interface ThumbnailDowloadListener<T> { /* * 圖片下載完成,可以交給UI去顯示時,接口中的方法就會被調用。 * 會使用這個方法把處理已下載圖片的任務代理給另一個類(PhotoGalleryFragment),這樣ThumbnailDownloader就可以把下載結果傳給其他視圖對象。 */ void onThumbnailDownloaded(T target, Bitmap thumbnail); } public void setThumbnailDownloaderListener(ThumbnailDowloadListener<T> listener) { mThumbnailDownloadListener = listener; } // 通過構造函數傳遞主線程的 Handler public ThumbnailDowloader(Handler responseHandler) { super(TAG); mResponseHandler = responseHandler; }
這樣,主線程通過調用這些方法,就能夠讓下載線程獲取到主線程的 Handler 和回調接口實例。
7.2主線程中
// 成員聲明 private ThumbnailDowloader<PhotoHolder> mThumbnailDownloader; // 傳遞實例給下載線程 // 這個 Handler 在主線程中建立,所以是和主線程 Looper 相關聯的 Handler responseHandler = new Handler(); mThumbnailDownloader = new ThumbnailDowloader<>(responseHandler); mThumbnailDownloader.setThumbnailDownloaderListener( new ThumbnailDowloader.ThumbnailDowloadListener<PhotoHolder>() { @Override public void onThumbnailDownloaded(PhotoHolder target, Bitmap thumbnail) { Drawable drawable = new BitmapDrawable(getResources(), thumbnail); target.bindDrawable(drawable); } } );
8.線程交互
現在,通過 mResponseHandler,下載線程能夠訪問與主線程 Looper 綁定的 Handler。同時,還有 ThumbnailDownloadListener 使用返回的 Bitmap 執行 UI 更新操作。具體來說, 就是通過 onThumbnailDownloaded 實現,使用新下載的 Bitmap 來設置 PhotoHolder 的 Drawable。
和在下載線程上把下載圖片的請求放入消息隊列類似,我們也可以返回定制 Message 給主線程,要求顯示已下載圖片。不過,這需要另一個 Handler 子類,以及一個 handleMessage(…) 覆蓋方法。方便起見,我們轉而使用另一個方便的 Handler 方法——post(Runnable)。
mResponseHandler.post(new Runnable() { @Override public void run() { if (mRequestMap.get(target) != url || mHasQuit) { return; } mRequestMap.remove(target); mThumbnailDownloadListener.onThumbnailDownloaded(target, bitmap); } });
在這裏,新建的 Runnable 對象會被當成 Message 的回調方法,直接執行 run() 方法,所以相當於發送一個消息,裏面寫明了怎麽做,而不是把對象和消息類型發給 Handler,讓 Handler 決定怎麽做。
安卓權威編程指南-筆記(第24章 Looper Handler 和 HandlerThread)