Android_Message_Handler_消息處理機制總結筆記

一次性線程和無限循環線程

普通線程是一次性的,執行結束後也就退出了(這種說法可能不嚴謹,但爲了下文描述方便)。
但某些狀況下須要無限循環、不退出的線程。好比處理用戶交互的線程,它等待並執行用戶的點擊、滑動等等操做事件,也執行由系統觸發的廣播等事件,稱之爲主線程,也叫UI線程。html

關於這種無限循環線程須要說的是:java

  • 每一個事件及其所包含的信息被封裝爲一個Message(下文中提到的「消息」和「事件」是一回事兒);android

  • 線程不能同時處理全部事件,因此須要有個收件箱MessageQueue(下文中提到的線程,若是沒有特別說明是一次性線程,那麼都指looper thread);spring

  • 之因此不一樣於一次性線程,而能無限循環,是Looper的功勞,因此這種線程也被稱爲message looper thread;編程

  • 事件由Handler發送和處理。app

建立一個looper thread

Android提供的類HandlerThread,是一個looper thread類。經過源碼理解它是怎麼辦到的:
HandlerThread源碼連接異步

23 public class HandlerThread extends Thread {
51     @Override
52     public void run() {
            // 初始化當前線程爲looper thread,prepare()方法保證爲每一個線程只新建一個Looper object;
            // 在新建Looper object時,新建了一個MessageQueue object;
            // 因此,HandlerThread對象、Looper對象、MessageQueue對象,三者之間創建了彼此唯一的關聯。
54         Looper.prepare();
55         synchronized (this) {
                // 該靜態方法返回與當前線程關聯的Looper object,若是當前線程未關聯Looper則返回空;
                // 稍後講到的Handler也是經過該方法創建和當前線程的唯一關聯;
                // 至於爲何是當前線程,好像是和ThreadLocal<Looper>這個靜態變量有關,不甚明瞭,存疑 TODO.
56             mLooper = Looper.myLooper();
57             notifyAll();
58         }
            // 你能夠覆寫這個函數,以在循環開始前作些必要的工做,好比實例化一個Handler,稍後講它。
60         onLooperPrepared();
            // 調用loop()方法後,Looper對象就開始循環地從MessageQueue對象中取消息、處理消息、沒消息時等待消息;
            // 這就使得它成爲了一個looper thread;
            // 而如何處理消息,涉及Handler,稍後講它。
61         Looper.loop();
63     }

因此,建立一個HandlerThread實例,就建立了一個looper thread:async

// 在合適的地方新建並啓動線程,好比在Activity.onCreate()方法。
HandlerThread mHandlerThread = new HandlerThread("Gallery Downloading Thread");
mHandlerThread.start();

// 必須調用getLooper(),並且必須在start()以後調用,該方法獲取與線程關聯的Looper對象,以確保線程就緒;
// 由於該方法會被阻塞直至looper對象初始化好了。
mHandlerThread.getLooper();

// 在合適的地方調用quit()方法,好比Activity.onDestroy()方法,以退出線程的looper循環,再也不處理消息隊列中的任何消息;
// 此處存疑 TODO:線程退出了嗎?(好像並無,該如何處理?)
mHandlerThread.quit();

如此,一個looper thread就在運轉了,但它發現沒有消息,因而它等待。問題是,
該如何傳遞消息給它;它拿到消息後又是怎麼處理的呢?答案在Handler。ide

Handler負責消息的發送和處理

Handler源碼連接函數

關於Handler須要說的是:

  • Handler使你可以發送並處理MessageRunnable對象;

  • 當你建立了一個Handler實例,它就被綁定給建立它的線程,只能綁定給這1個線程,這也就意味着只關聯了1個MessageQueue,此後,就能夠發送messages和runnables給所關聯的message queue,而當這些消息出隊時,就在handler綁定的線程中獲得執行;

  • Handler主要有2種用途:(1) 安排messages和runnables在未來的某個時間點執行;(2) 讓另外一個線程作些事情。
    以上內容截取自源碼文件的註釋(翻譯後)。

使用默認構造器建立一個Handler

Handler mHandler = new Handler();

經過源碼來理解爲何handler被綁定到了建立它的線程:

188    public Handler(Callback callback, boolean async) {
            // 經過該靜態方法得到與當前線程綁定的Looper,而Looper是和當前線程唯一關聯的(參看上文描述);
            // 這樣就創建了handler和當前線程及其MessageQueue的唯一關聯;
            // 固然也能夠經過另外一個方法關聯到指定的Looper,此處暫且不表 TODO.
198        mLooper = Looper.myLooper();
            // 若是當前線程沒有looper,就沒有queue,就無法接收消息,因此拋出異常。
199        if (mLooper == null) {
200            throw new RuntimeException(
201                "Can't create handler inside thread that has not called Looper.prepare()");
202        }
203        mQueue = mLooper.mQueue;
204        mCallback = callback;
206    }

// 默認的構造器就調用上面的方法,綁定handler到當前線程,也就是建立它的線程。
113    public Handler() {
114        this(null, false);
115    }

如今有了handler,又該如何發送消息呢?在發送以前,咱們先新建一個消息。

使用默認構造器建立一個Message

Message msg = new Message();
msg.what // 自定義的消息識別碼,整形,以便接收者識別消息;
msg.arg1 // 若是兩個int數據就能夠知足你的要求,就用arg1和arg2;
msg.arg2
msg.obj // 若是複雜,就傳遞Object對象,或者Bundle數據;
msg.setData(Bundle data)

使用Handler發送消息

Handler能夠發送處理MessageRunnable兩種對象,提供了若干方法:

boolean post(Runnable r);
boolean postAtTime(Runnable r, Object token, long uptimeMillis);
boolean postDelayed(Runnable r, long delayMillis);

boolean sendEmptyMessage(int what);
boolean sendMessage(Message msg);
boolean sendMessageAtTime(Message msg, long uptimeMillis);
boolean sendMessageDelayed(Message msg, long delayMillis);

這些方法最終都調用了sendMessageAtTime(),而後把消息放入隊列。Runnable對象在內部被轉成了Message對象的callback字段,稍後講它。

Message綁定給了發送它的Handler

須要特別關注message的成員變量Handler target,消息入隊時,該字段被設置爲發送它的handler,這就確保了message對象和handler對象的唯一關聯。這樣在message出隊時,就知道該交給哪一個handler處理;除此以外它還有別的用處,稍後講它。

高效發送消息的作法

上文發送消息的作法,每次都須要新建一個message實例。若是頻繁的話,Android推薦這樣作:

mHandler
    .obtainMessage(int what, int arg1, int arg2, Object obj)
    .sendToTarget();

Handler.obtainMessage(...) 方法從公共循環池裏獲取消息(若是沒有的話,它會建立新的實例),並傳入消息的各個字段。這樣能夠避免建立新的Message實例,提升效率。
Message.sendToTarget()方法只執行了一條語句target.sendMessage(this),這就是上文提到的target字段的別的用處。這樣就把消息放入了隊列。

使用Handler處理消息

這是消息循環的核心源碼,一個死循環:

// Looper.loop()
109    public static void loop() {
110        final Looper me = myLooper();
114        final MessageQueue queue = me.mQueue;
115
121        for (;;) {
122            Message msg = queue.next(); // might block 沒有消息則阻塞。
123            if (msg == null) {
                    // 收到空消息就退出。
124                // No message indicates that the message queue is quitting.
125                return;
126            }
                // 這就是前面提到的target字段的做用:知道把出隊的消息交給誰。
135            msg.target.dispatchMessage(msg);
153        }
154    }

下面就開始dispatchMessage:

// Handler.dispatchMessage()
93     public void dispatchMessage(Message msg) {
            // 上文提到的,發送消息時,Runnable對象在內部被轉成了Message對象的`callback`字段;
            // 若callback不爲空,那麼消息就是一個runnable對象,那麼就執行它;
94         if (msg.callback != null) {
95             handleCallback(msg);
96         } else {
                // 若是不是runnable,那麼就是Message對象了:
                // 上文只講了使用默認構造器建立handler,也能夠Handler(Callback callback);
                // 這樣就爲每一個handler設置了各自的callback,優先執行它;
97             if (mCallback != null) {
98                 if (mCallback.handleMessage(msg)) {
99                     return;
100                }
101            }
                // 若是handler對象沒有本身的callback,那麼就執行這個方法;
                // 這是一個空方法,當經過默認構造器新建一個handler時須要覆寫它。
102            handleMessage(msg);
103        }
104    }

理論知識已經具有了,那麼接下來,在1個典型的應用場景中使用它們解決咱們的問題。

一個demo使用後臺線程完成下載任務

這段代碼截取自《Android權威編程指南》第26, 27章,做者構建了一個這樣的App:下載Flicker上最新的100張縮略圖,並填充到GridView內。首先經過AsyncTask線程獲取包含縮略圖url信息的XM文件;而後下載那些url指向的縮略圖。這裏須要思考的是,什麼時候下載這些縮略圖,是一次下載完?仍是下載一部分?由誰觸發下載?下載後怎樣填充到GridView內?
解決了這些問題,就基本掌握了建立後臺線程,並和主線程通訊的方法。

public class PhotoGalleryFragment extends Fragment {
    ThumbnailDownloader<ImageView> mThumbnailDownloader;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        mThumbnailDownloader = new ThumbnailDownloader<ImageView>(new Handler());
        mThumbnailDownloader.setOnThumbnailDownloadListener(new OnThumbnailDownloadListener<ImageView>() {
            public void onThumbnailDownloaded(ImageView imageView, Bitmap bitmap) {
                imageView.setImageBitmap(bitmap);
            }
        });
        mThumbnailDownloader.start();
        mThumbnailDownloader.getLooper();
    }

    @Override
    public void onDestroy() {
        mThumbnailDownloader.quit();
    }

    private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView imageView;
            String url;
            // 對於須要顯示在GridView視圖中的成百上千張圖片,咱們不可能一次下載完而後顯示;
            // 只能在須要顯示時下載,而GridView的adapter知道何時顯示哪些視圖;
            // 因此咱們在此處安排後臺線程的下載任務。
            mThumbnailDownloader.queueThumbnail(imageView, url);
            return convertView;
        }
    }
}


public class ThumbnailDownloader<Token> extends HandlerThread {
    private static final String TAG = "ThumbnailDownloader";
    private static final int MESSAGE_DOWNLOAD = 0;

    Handler mHandler;
    Map<Token, String> requestMap = Collections.synchronizedMap(new HashMap<Token, String>());

    Handler mResponseHandler;
    OnThumbnailDownloadListener<Token> mOnThumbnailDownloadListener;

    public interface OnThumbnailDownloadListener<Token> {
        void onThumbnailDownloaded(Token token, Bitmap thumbnail);
    }

    public void setOnThumbnailDownloadListener(OnThumbnailDownloadListener<Token> l) {
        mOnThumbnailDownloadListener = l;
    }

    public ThumbnailDownloader(Handler responseHandler) {
        super(TAG);
        // 後臺線程能在主線程上完成任務的一種方式是,讓主線程將其自身的Handler傳給後臺線程;
        // mResponseHandler始終和主線程保持關聯,由它發送的消息都將在主線程中獲得處理。
        mResponseHandler = responseHandler;
        // 咱們也能夠傳遞主線程的context,經過下述方式獲取主線程的handler:
        // mResponseHandler = new Handler(mContext.getMainLooper());
    }

    public void queueThumbnail(Token token, String url) {
        // requestMap是一個同步HashMap。 使用Token做爲key,可存儲或獲取與特定Token關聯的URL.
        requestMap.put(token, url);
        // mHandler是和後臺線程關聯的,咱們開放這個方法給主線程,主線程調用這個方法來安排後臺線程的任務。
        // 咱們把下載信息封裝成message後放入後臺線程的收件箱。
        mHandler.obtainMessage(MESSAGE_DOWNLOAD, token).sendToTarget();
    }

    @Override
    protected void onLooperPrepared() {
        // onLooperPrepared()方法發生在Looper.loop()以前,此時消息尚未開始循環,
        // 因此是咱們實現mHandler的好地方,在此處下載。
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MESSAGE_DOWNLOAD) {
                    Token token = (Token)msg.obj;
                    handleRequest(token);
                }
            }
        };
    }

    private void handleRequest(final Token token) {
        // 下載,並根據獲取的數據構建Bitmap對象;
        final String url = requestMap.get(token);
        final Bitmap bitmap;
        // 下載完成後,咱們在後臺線程使用與主線程關聯的handler,安排要在主線程上完成的任務。
        // 除了post,咱們也能夠sendMessage給主線程,那麼主線程的handler須要覆寫本身的handleMessage()方法。
        mResponseHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mOnThumbnailDownloadListener != null) {
                    mOnThumbnailDownloadListener.onThumbnailDownloaded(token, bitmap);
                }
            }
        });
    }
}

後記和參考

強烈建議閱讀《Android權威編程指南》第26, 27這兩章代碼。
本書的官方主頁連接 能夠從這裏獲取本書的源碼
但我在學習這兩個章節的時候,有些概念和類的描述理解很吃力,好比「消息循環由一個線程和一個looper組成。 Looper對象管理着線程的消息隊列」,又好比「一個Handler僅與一個Looper相關聯,一個Message 也僅與一個目標Handler」。
由於做者着重講解app的實現思路,對涉及到的類只給出告終論,沒有說明爲何。因此初學時很費解,實際編程時也是隻知其然不知其因此然。後來讀了些博文(下面給出了連接),又閱讀了源碼,總算釐清了這些類,而這篇文章就是我理解後的一個產物。

下面是3個做者的4篇博文,總結的都很棒,側重於源碼分析。尤爲是第2篇,做者最後畫了一張圖,經過傳送帶來解釋涉及到的類和概念:「在現實生活的生產生活中,存在着各類各樣的傳送帶,傳送帶上面灑滿了各類貨物,傳送帶在發動機滾輪的帶動下一直在向前滾動,不斷有新的貨物放置在傳送帶的一端,貨物在傳送帶的帶動下送到另外一端進行收集處理。」

下面就是源碼連接了,對於理解android的消息處理機制很是有幫助。


版權聲明:《Android Message Handler 消息處理機制總結筆記》由 WeiYi.Li 在 2015年10月15日寫做。著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
文章連接:http://li2.me/2015/10/communicate-with-a...

相關文章
相關標籤/搜索