Android消息處理機制Handler

在日常開發中,不管出於什麼目的,我們可能都會用到Handler來異步更新UI,有時是爲了將一些費時的操作放到異步線程去處理,然後通過Handler將數據更新到UI線程,有時是爲了在子線程裏更新UI,種種原因,反正我們最後都是選擇了直接的Handler+Message組合或者AsyncTask,而瞭解AsyncTask的同學都知道,AsyncTask內部就是通過Handler和Message實現的線程間通信,所以我們還是要好好熟悉一下這位老朋友

爲什麼要使用Handler

我們使用Handler更多是爲了異步操作,那麼爲什麼非得要異步操作呢,直接在子線程更新UI不行嗎?答案當然是不行的,不然Handler存在的意義是什麼,這一切都是因爲UI的控件都不是線程安全的,如果允許併發訪問,那控件的狀態就是未知的了,可能你剛獲取完一個控件的狀態,準備進行一些操作,這時候另一個線程改變了這個控件的狀態,那就很麻煩了,解決這種問題的方法一般有兩種:1.加鎖,在對控件進行操作的時候先加鎖,不允許其他人訪問,這樣的問題是會導致UI更新的效率會很差,而且容易堵塞某些線程,因爲他需要等上一個訪問這個控件的線程釋放鎖;所以Android選擇的是第二種方法,只允許在一個線程內對UI控件進行更新,這個線程就是主線程,這也就是爲什麼我們在對UI組件進行更新的時候,必須回到主線程去操作。

Handler、MessageQueue和Looper

我用了Handler,不過總是對它一知半解,一直停留在會用的程度,以致於別人在提到MessageQueue和Looper的時候,我竟是一臉懵逼,然後就是無盡的嘲笑,連這些都不知道,你還用什麼Handler,回去搬磚吧!我相信也有很多的Android初學者跟我有同樣的問題,所以在這裏,我想先跟大家詳細介紹一下這幾個概念

Looper

Looper是一個循環類,Handler初始化的時候必須依靠Looper,換言之,Handler初始化的那個線程,必須有Looper,否則就會報異常信息,Looper初始化的過程用代碼表示就是如下所示:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }).start();

我們之所以之前沒有感受到它的存在,是因爲在主線程,ActivityThread默認會把Looper初始化好,prepare以後,當前線程就會變成一個Looper線程,增加一個Looper對象,而Looper會維護着一個MessageQueue對象,用來存放Handler發送的Message。

其實Looper對象的本質是ThreadLocal,是一個線程內部的數據存儲類,有興趣的同學可以去搜索詳細瞭解下

Looper的作用就是從消息隊列裏取出消息然後進行處理,沒有消息處理的時候,它就堵塞在那裏,一有新的消息進來,它就從消息隊列中取出,處理。它就是一個任勞任怨的搬運工,它的特點在於它是跟它的線程是綁定的,處理消息的過程也是在Looper所在的線程去處理的,這就是爲什麼Handler在其他線程發的消息,最後也是在主線程處理,因爲它只跟Handler初始化的線程有關,確切的說是Handler初始化的時候綁定的Looper所在的線程有關。這樣異步的目的就達到了。

如上面代碼顯示的,Looper主要有兩個方法:prepare()用來初始化當前線程的Looper,loop()用來開始Looper的循環,其實還有兩個不是很常用的方法:quit()和quitSafely(),這兩個方法的不同在於,quit會立即停止循環,而quitSafely會在MessageQueue爲空以後才跳出循環。

MessageQueue

MessageQueue是一個消息隊列,用來存放Handler發送的消息,主要有兩個操作:添加和讀取,讀取的同時伴隨着消息從消息隊列的移除,分別對應的方法就是:enqueueMessage(Message msg, long when)和next(),enqueueMessage方法就是簡單地將一條消息插入MessageQueue,next方法相對會複雜一點,它是一個死循環,返回值是一個Message,它的返回值就是用作Looper處理用的,所以延遲發送消息的主要處理步驟就是在next()方法裏,因爲next()之前的操作都是記錄message延遲到什麼時間,然後設置給Message.when,next()之後的Looper是不處理延遲時間的,會直接調用Handler裏的處理邏輯,在next()內部,當沒有消息返回時,next()就會堵塞,直到有新的消息過來再返回,然後又進入堵塞狀態,等待新消息進來。

Message算是一個比較簡單的類,它本質上是一輛馬車,只是用來裝載信息,然後在Handler,MessageQueue和Looper之前傳遞,主要有這麼幾個屬性:

what:int型,最主要的屬性,用於指定消息的類型,這算是Message唯一一個必須指定的值
arg0,arg1:兩個int型值,一般用來傳遞一些progress,或者一些簡單的整型
obj:可以傳遞一些複雜一些的object
data:Bundle型,這個就不用多解釋了,傳遞較多種數據的時候肯定會用到它
callback:Runable型,Handler.post(Runnable)內部就是設置的這個屬性,這個一般不會手動設置,但是也會用到,只是我們感覺不到,下面會詳細解釋,用於覆蓋Handler的默認處理邏輯

大致就這些屬性了,詳細瞭解Message的基本結構還是有必要的,傳遞什麼數據,用什麼方法,對以後的實際開發會有一些幫助。

Handler

說了這麼多鋪墊,準備工作算是差不多了,Handler也就要上場了,其實Looper,MessageQueue和Message,除了Message有些接觸以外,其他兩個在實際開發中其實是見不到的,而Handler則是開發者接觸最多的,因爲幾乎所有的處理邏輯都是寫在Handler裏的,發送消息和處理消息也都算是Handler接手的,但是我們平常是怎麼創建一個Handler的呢? 無外乎兩種:

創建一個自定義的Handler繼承Handler,並重寫handlerMessage()方法。
直接使用默認的Handler類,但是在新建Handler對象時,傳入一個Callback對象。

上面說的這兩種方法,我用的比較多的是第二種方法,因爲用起來更加靈活一點。

獲得一個Message:obtain()

在發送一個Message之前 ,我們肯定要先得到一個Message對象,可以直接new一個Message對象出來,但是這種方法並不推薦,更推薦用Message的obtain方法,它類似一個線程池,創建了一個Message池,如果有閒置的Message就直接返回,不然就新建一個,用完以後,返回消息池,這種方法大大減少了當有大量Message對象而產生的垃圾回收問題,而且有obtain方法有多種形式,基本能滿足我們的一些需求:

obtain() 返回一個空的Message
obtain(Message msg) copy一個message並返回
obtain(Handler h) 返回一個設置了target的message,設置了這個屬性以後message就可以使用sendToTarget()方法,其實內部調用的就是Handler.sendMessage(this)
obtain(Handler h, Callback callback) 同上,並設置message的callback屬性
obtain(Handler h, int what) 同obtain(Handler h),並設置message的what屬性
obtain(Handler h, int what, Object obj)
obtain(Handler h ,int what, int arg0, int arg1)
obtain(Handler h, int what, int arg0, int arg1, Object obj)

這些方法都大同小異,無非就是預設幾個屬性,不過,以後儘量還是要用obtain方法,算不上方便快捷吧,但至少算的上優雅。

發送一個Message:sendMessage()

一個Message已經準備好了,蓄勢待發,接下來的工作就是把它發射出去,這時候就要交給Handler,調用它的sendMessage(Message msg),其實我們也可以不去費心得到一個Message對象直接用Handler.sendEmptyMessage(int what),發送一個空message,而且Handler還有sendMessageDelay和sendMessageAtTime方法,這些有什麼不同點,可以直接看源碼

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendEmptyMessage(int what)
   {
       return sendEmptyMessageDelayed(what, 0);
   }

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

從這些代碼中,我們可以看出來,sendEmptyMessage和sendMessage都是調用的sendMessageDelay方法,只不過sendEmptyMessage是在方法內部用Message.obtain()方法得到了一個空message,然後sendMessageDelay方法內部又是調用的sendMessageAtTime,所以殊途同歸,最後只要看sendMessageAtTime就好了:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

沒錯,又回來了,看到了enqueueMessage方法,就是這樣,一條message被插入MessageQueue隊列,接下來的視情節就交給Looper,它會循環調用MessageQueue的next方法,而next會在合適的時機返回要處理的Message,交給Looper處理。

消息處理的過程

具體消息處理的過程又是怎樣的呢?Handler的post方法和sendMessage有什麼不同?都將在這一節揭曉。其實post的Runnable對象,有沒有覺得很熟悉,剛纔提到的Message也有一個屬性是Runnable,就是callback,正如我們所料想的那樣,post其實也是發送的一個message,和sendMessage一樣,只不過它給message設置了屬性CallBack,還是貼最誠實的源碼:

public void dispatchMessage(Message msg) {
       if (msg.callback != null) {
           handleCallback(msg);
       } else {
           if (mCallback != null) {
               if (mCallback.handleMessage(msg)) {
                   return;
               }
           }
           handleMessage(msg);
       }
   }

從源碼中我們可以看到,當message的callback屬性不爲空的話,就會調用handlerCallback(msg)方法,而這個方法內部則是直接運行的message的callback,消息處理結束;當message的callback爲空時,就是這個message是一個普通的message,會先查看mCallback是否存在,如果存在的話,嘗試調用mCallback的handlerMessage方法,根據返回值決定是否再調用Handler的handlerMessage方法,而這個mCallback變量就是我們在新建handler時,傳入的那個Runable對象:

Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return false;
            }
        });

現在終於理解爲什麼在這個地方傳入一個Runable對象也能攔截下消息處理了,至此,消息處理的優先級就出來了:

Message的callback>handler的Callback>Handler的handlerMessage

可能存在的問題:內存泄露

在使用Handler的時候,之前一直沒有關心過,直到有一次發生了內存泄露才注意到這一點,在Activity銷燬的時候要記得把handler的消息隊列清空,或者在使用handler時,把handler設成靜態變量或者弱引用。

Message上限

之前我記得有個同事問我Message是否有上限,我當時答不上來,後來去搜索也沒搜索到,只查到Message.obtain()的消息池上限是10個,但是MessageQueue得上限還是不知道,或許是他問錯了,或者我理解錯了,還是這個問題是存在的,只是我還沒找到答案,有知道的看官可以留言指導一下。

The End

雖然現在RxJava和Agera用的比較火,但是關於Handler的一些東西,作爲開發者,我們還是有必要知道的,通過看任玉剛大大的書,還有網上的各種資料,最後算是對Handler和Message有了個一知半解,有不對的地方,希望大家批評指正。

注:本文轉自伯樂在線專欄作者 - 邵輝:http://android.jobbole.com/83823/

發佈了50 篇原創文章 · 獲贊 17 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章