1. 程式人生 > >安卓權威編程指南-筆記(第24章 Looper Handler 和 HandlerThread)

安卓權威編程指南-筆記(第24章 Looper Handler 和 HandlerThread)

結果 pan color font 特定 消息循環 現在 返回 消息

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)