使用過AsyncTask、EventBus、Volley以及Retrofit,必須好好了解handler運行機制

咱們都知道在UI線程中不能進行耗時操做,例如數據讀寫、網絡請求。Android 4.0開始,在主線程中進行網絡請求甚至會拋出Android.os.NetworkOnMainThreadException。這個時候,咱們就會開始依賴Handler。咱們在子線程進行耗時操做後,將請求結果經過Handler的sendMessge**() 方法發送出去,在主線程中經過Handler的handleMessage 方法處理請求結果,進行UI的更新。java

後來隨着AsyncTask、EventBus、Volley以及Retrofit 的出現,Handler的做用彷佛被弱化,逐漸被你們遺忘。其實否則,AsyncTask實際上是基於Handler進行了很是巧妙的封裝,Handler的使用依然是其核心。Volley一樣也是使用到了Handler。所以,咱們有必要了解一下Handler的實現機制。android

神奇的Handler

記得好久以前的一天,我在閱讀別人的代碼時,看到了這樣一段:網絡

  1.  
    new Handler().postDelayed(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
    Toast.makeText(mContext, "I'm new Handler !", Toast.LENGTH_SHORT).show();
  5.  
    }
  6.  
    }, 1000);

第一印象就是,這不是在子線程中進行UI操做嗎?這代碼有問題吧,因而乎馬上在本身電腦上寫了個demo試了一下,結果發現真的沒有問題。在一陣懵逼事後,我又寫出下面的代碼,測試一會兒線程中到底能不能進行UI操做。多線程

  1.  
    new Thread(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
     
  5.  
    try {
  6.  
    Thread.sleep( 2000);
  7.  
    } catch (InterruptedException e) {
  8.  
    e.printStackTrace();
  9.  
    }
  10.  
     
  11.  
    Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();
  12.  
     
  13.  
    }
  14.  
    }).start();

結果很明顯,程序一啓動馬上就奔潰了。並拋出異常java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
因而乎我又在try block 以前添加了Looper.prepare()這行代碼。再次運行程序雖然沒有奔潰,但也沒有任何反應,Toast也沒顯示。併發

那麼Handler究竟是什麼呢?他怎麼就這麼神奇。app

實現機制解析

首先,咱們從總體上了解一下,在整個Handler機制中全部使用到的類,主要包括Message,MessageQueue,Looper以及Handler。less

 

 

好了,爲了方便後面的敘述,咱們就首先了解一下這個類圖中使用到幾個類,及其關鍵方法。異步

Message

首先看一下Message這個類的定義(截取部分)async

  1.  
    public final class Message implements Parcelable {
  2.  
     
  3.  
    public int what;
  4.  
    public int arg1;
  5.  
    public int arg2;
  6.  
    public Object obj;
  7.  
    /*package*/ Handler target;
  8.  
    /*package*/ Runnable callback;
  9.  
     
  10.  
    /**
  11.  
    * Return a new Message instance from the global pool. Allows us to
  12.  
    * avoid allocating new objects in many cases.
  13.  
    */
  14.  
    public static Message obtain() {
  15.  
    synchronized (sPoolSync) {
  16.  
    if (sPool != null) {
  17.  
    Message m = sPool;
  18.  
    sPool = m.next;
  19.  
    m.next = null;
  20.  
    m.flags = 0; // clear in-use flag
  21.  
    sPoolSize--;
  22.  
    return m;
  23.  
    }
  24.  
    }
  25.  
    return new Message();
  26.  
    }
  27.  
     
  28.  
    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
  29.  
    */
  30.  
    public Message() {
  31.  
    }
  32.  
    }

看到這個類的前四個屬性,你們應該很熟悉,就是咱們使用Handler時常常用到的那幾個屬性。用來在傳遞咱們特定的信息。其次咱們還能夠總結出如下信息:ide

  • Message 實現了Parcelable 接口,也就是說實現了序列化,這就說明Message能夠在不一樣進程之間傳遞。
  • 包含一個名爲target的Handler 對象
  • 包含一個名爲callback的Runnable 對象
  • 使用obtain 方法能夠從消息池中獲取Message的實例,也是推薦你們使用的方法,而不是直接調用構造方法。

MessageQueue

MessageQueue顧名思義,就是上面所說的Message所組成的queue。

首先看一下構造方法:

  1.  
    MessageQueue( boolean quitAllowed) {
  2.  
    mQuitAllowed = quitAllowed;
  3.  
    mPtr = nativeInit();
  4.  
    }

接收一個參數,決定當前隊列是否容許被終止。同時調用 一個native方法,初始化了一個long類型的變量mPtr。

同時,在這個類當中,還定義了一個next 方法,用於返回一個Message 。

  1.  
    Message next() {
  2.  
    // Return here if the message loop has already quit and been disposed.
  3.  
    // This can happen if the application tries to restart a looper after quit
  4.  
    // which is not supported.
  5.  
    final long ptr = mPtr;
  6.  
    if (ptr == 0) {
  7.  
    return null;
  8.  
    }
  9.  
    ……
  10.  
    }

因爲這個方法中有一些native調用,未能徹底理解,只知道會返回一個Message對象。

這個next方法至關因而隊列出棧,有出棧必然有進棧,enqueueMessage 方法就是完成這個操做;這個咱們後面再說。

Looper

上面說到了MessageQueue,那麼這個Queue又是由誰建立的呢?其實就是Looper。關於Looper有兩個關鍵方法:

prepare() 和 loop()

Looper-prepare()

  1.  
    public static void prepare() {
  2.  
    prepare( true);
  3.  
    }
  4.  
     
  5.  
    private static void prepare(boolean quitAllowed) {
  6.  
    if (sThreadLocal.get() != null) {
  7.  
    throw new RuntimeException("Only one Looper may be created per thread");
  8.  
    }
  9.  
    sThreadLocal.set( new Looper(quitAllowed));
  10.  
    }

能夠看到,對於每個線程只能有一個Looper。也就是說執行prepare方法時,必然執行最後一行代碼
sThreadLocal.set(new Looper(quitAllowed));

咱們再看Looper(quitAllowed)方法:

  1.  
    private Looper(boolean quitAllowed) {
  2.  
    mQueue = new MessageQueue(quitAllowed);
  3.  
    mThread = Thread.currentThread();
  4.  
    }

這樣,MessageQueue 就被建立了。這裏也能夠看到,默認狀況下,一個MessageQueue的quiteAllow=true。

這裏使用到的sThreadLocal 是一個ThreadLocal對象。簡單來講,使用它能夠用來解決多線程程序的併發問題。使用set方法,將此線程局部變量的當前線程副本中的值設置爲指定值;使用get方法,返回此線程局部變量的當前線程副本中的值。

Looper-loop()

再看一下loop方法(截取主要邏輯)

  1.  
    public static void loop() {
  2.  
    final Looper me = myLooper();
  3.  
    if (me == null) {
  4.  
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  5.  
    }
  6.  
    final MessageQueue queue = me.mQueue;
  7.  
    for (;;) {
  8.  
    Message msg = queue.next(); // might block
  9.  
    if (msg == null) {
  10.  
    // No message indicates that the message queue is quitting.
  11.  
    return;
  12.  
    }
  13.  
    msg.target.dispatchMessage(msg);
  14.  
    msg.recycleUnchecked();
  15.  
    }
  16.  
    }

首先看第一句代碼執行的方法:

  1.  
    /**
  2.  
    * Return the Looper object associated with the current thread. Returns
  3.  
    * null if the calling thread is not associated with a Looper.
  4.  
    */
  5.  
    public static @Nullable Looper myLooper() {
  6.  
    return sThreadLocal.get();
  7.  
    }

很明顯,這樣返回的Looper就是剛纔prepare時set進去的那個,由於都是在同一線程。再明確一下,一個線程對應一個Looper。

這樣就確保咱們能夠在不一樣的線程中建立各自的Handler,進行各自的通訊而不會互相干擾

回到代碼,後面邏輯就很簡單了,在一個死循環中,經過隊列出棧的形式,不斷從MessageQueue 中取出新的Message,而後執行msg.target.dispatchMessage(msg) 方法,還記的前面Message類的定義嗎,這個target屬性其實就是一個Handler 對象,所以在這裏就會不斷去執行Handler 的dispatchMessage 方法。若是取出的Message對象爲null,就會跳出死循環,一次Handler的工做整個就結束了。

Handler

上面說了這麼多終於輪到Handler,那麼就看看在Handler中到底發生了什麼。回到咱們一開始的代碼。

  1.  
    new Handler().postDelayed(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
    String currentName=Thread.currentThread().getName();
  5.  
    Toast.makeText(mContext, "I'm new Thread "+currentName, Toast.LENGTH_SHORT).show();
  6.  
    }
  7.  
    }, 4000);

這裏咱們用Toast彈出了當前線程的name,結果發現這個線程的名字竟然是main,這也是必然結果

讓咱們一步一步看看,神奇的Handler究竟是怎樣工做的。就從這個代碼開始解讀。首先看一下Handler的構造方法。

  1.  
    public Handler() {
  2.  
    this(null, false);
  3.  
    }
  4.  
     
  5.  
    ---------------
  6.  
     
  7.  
    public Handler(Callback callback, boolean async) {
  8.  
    if (FIND_POTENTIAL_LEAKS) {
  9.  
    final Class<? extends Handler> klass = getClass();
  10.  
    if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
  11.  
    (klass.getModifiers() & Modifier.STATIC) == 0) {
  12.  
    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
  13.  
    klass.getCanonicalName());
  14.  
    }
  15.  
    }
  16.  
     
  17.  
    mLooper = Looper.myLooper();
  18.  
    if (mLooper == null) {
  19.  
    throw new RuntimeException(
  20.  
    "Can't create handler inside thread that has not called Looper.prepare()");
  21.  
    }
  22.  
    mQueue = mLooper.mQueue;
  23.  
    mCallback = callback;
  24.  
    mAsynchronous = async;
  25.  
    }

這裏作的事情很簡單,就是完成了一些初始化的工做,調用Looper.myLooper()賦值給當前mLooper,關聯MessageQueue;這裏因爲代碼中調用的是不帶任何參數的構造函數,所以會建立一個mCallback=null且非異步執行的Handler 。

接下看postDelayed 方法。

  1.  
    public final boolean postDelayed(Runnable r, long delayMillis)
  2.  
    {
  3.  
    return sendMessageDelayed(getPostMessage(r), delayMillis);
  4.  
    }
  5.  
     
  6.  
    private static Message getPostMessage(Runnable r) {
  7.  
    Message m = Message.obtain();
  8.  
    m.callback = r;
  9.  
    return m;
  10.  
    }

這裏經過getPostMessage(Runnable r) 方法,把咱們在Activity裏寫的Runnable 這個線程賦給了Message 的callback這個屬性。

平時你們使用Handler也發現了,他爲咱們提供了不少方法

 

 

所以,上面的postDelayed通過了各類展轉反側,最終來到了這裏:

  1.  
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  2.  
    MessageQueue queue = mQueue;
  3.  
    if (queue == null) {
  4.  
    RuntimeException e = new RuntimeException(
  5.  
    this + " sendMessageAtTime() called with no mQueue");
  6.  
    Log.w( "Looper", e.getMessage(), e);
  7.  
    return false;
  8.  
    }
  9.  
    return enqueueMessage(queue, msg, uptimeMillis);
  10.  
    }

通過以前的構造方法,mQueue顯然不爲null,繼續往下看

  1.  
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  2.  
    msg.target = this;
  3.  
    if (mAsynchronous) {
  4.  
    msg.setAsynchronous( true);
  5.  
    }
  6.  
    return queue.enqueueMessage(msg, uptimeMillis);
  7.  
    }

注意,注意,注意 這裏進行了一次賦值:

msg.target = this;

前面提到,這個target就是一個Handler對象,所以這裏Message就和當前Handler關聯起來了。enqueueMessage,哈哈,這就是咱們以前在MessageQueue中提到的進棧操做的方法,咱們看一下:

  1.  
    boolean enqueueMessage(Message msg, long when) {
  2.  
    if (msg.target == null) {
  3.  
    throw new IllegalArgumentException("Message must have a target.");
  4.  
    }
  5.  
    if (msg.isInUse()) {
  6.  
    throw new IllegalStateException(msg + " This message is already in use.");
  7.  
    }
  8.  
     
  9.  
    synchronized (this) {
  10.  
    if (mQuitting) {
  11.  
    IllegalStateException e = new IllegalStateException(
  12.  
    msg.target + " sending message to a Handler on a dead thread");
  13.  
    Log.w(TAG, e.getMessage(), e);
  14.  
    msg.recycle();
  15.  
    return false;
  16.  
    }
  17.  
     
  18.  
    msg.markInUse();
  19.  
    msg.when = when;
  20.  
    Message p = mMessages;
  21.  
    boolean needWake;
  22.  
    if (p == null || when == 0 || when < p.when) {
  23.  
    // New head, wake up the event queue if blocked.
  24.  
    msg.next = p;
  25.  
    mMessages = msg;
  26.  
    needWake = mBlocked;
  27.  
    } else {
  28.  
    // Inserted within the middle of the queue. Usually we don't have to wake
  29.  
    // up the event queue unless there is a barrier at the head of the queue
  30.  
    // and the message is the earliest asynchronous message in the queue.
  31.  
    needWake = mBlocked && p.target == null && msg.isAsynchronous();
  32.  
    Message prev;
  33.  
    for (;;) {
  34.  
    prev = p;
  35.  
    p = p.next;
  36.  
    if (p == null || when < p.when) {
  37.  
    break;
  38.  
    }
  39.  
    if (needWake && p.isAsynchronous()) {
  40.  
    needWake = false;
  41.  
    }
  42.  
    }
  43.  
    msg.next = p; // invariant: p == prev.next
  44.  
    prev.next = msg;
  45.  
    }
  46.  
     
  47.  
    // We can assume mPtr != 0 because mQuitting is false.
  48.  
    if (needWake) {
  49.  
    nativeWake(mPtr);
  50.  
    }
  51.  
    }
  52.  
    return true;
  53.  
    }

這個方法就是典型的隊列入隊操做,只不過會根據Message這個對象特有的一些屬性,以及當前的狀態是否inUse,是否已經被quit等進行一些額外的判斷。

這樣,咱們就完成消息入隊的操做。還記得咱們在Looper中說過,在loop方法中,會從MessageQueue中取出Message 並執行他的dispatchMessage 方法。

dispatchMessage

  1.  
    public void dispatchMessage(Message msg) {
  2.  
    if (msg.callback != null) {
  3.  
    handleCallback(msg);
  4.  
    } else {
  5.  
    if (mCallback != null) {
  6.  
    if (mCallback.handleMessage(msg)) {
  7.  
    return;
  8.  
    }
  9.  
    }
  10.  
    handleMessage(msg);
  11.  
    }
  12.  
    }

到這裏,就很明確了,在以前的postDelayed 方法中,已經經過getPostMessage,實現了 m.callback = r;這樣這裏就會執行第一個if語句:

  1.  
    private static void handleCallback(Message message) {
  2.  
    message.callback.run();
  3.  
    }

這樣,就會執行咱們在Activity 的Runnable 中的run 方法了,也就是顯示Toast。

到了這裏,咱們終於明白了,使用Handler 的postDelay 方法時,其Runnable中的run方法並非在子線程中執行,而是把這個Runnable賦值給了一個Message對象的callback屬性,而這個Message會被傳遞到建立Handler所在的線程,也就是這裏的主線程,因此這個Toast的顯示依舊是在主線程中。這也和postDelay API 中所聲明的內容是一致的。

/**

  1.  
    * Causes the Runnable r to be added to the message queue, to be run
  2.  
    * after the specified amount of time elapses.
  3.  
    * The runnable will be run on the thread to which this handler
  4.  
    * is attached.
  5.  
    * /

到這裏,一開始所說的第一個代碼塊所執行的邏輯已經理清楚了,可是仍是有一點疑問,咱們並無在Handler的構造方法中看到Looper 的prepare()方法和loop() 方法被執行,那麼他們究竟是在哪裏執行的呢?這個問題我也是疑惑了好久,最終才明白是在
ActivityThread的main方法中執行。簡單來講,ActivityThread是Java層面一個Android程序真正的入口。關於ActivityThread更多的內容能夠看看這篇文章

ActivityThread-main方法(截取主要部分)

  1.  
    public static void main(String[] args) {
  2.  
     
  3.  
     
  4.  
    Looper.prepareMainLooper();
  5.  
     
  6.  
    ActivityThread thread = new ActivityThread();
  7.  
    thread.attach( false);
  8.  
     
  9.  
    if (sMainThreadHandler == null) {
  10.  
    sMainThreadHandler = thread.getHandler();
  11.  
    }
  12.  
     
  13.  
    if (false) {
  14.  
    Looper.myLooper().setMessageLogging( new
  15.  
    LogPrinter(Log.DEBUG, "ActivityThread"));
  16.  
    }
  17.  
     
  18.  
    // End of event ActivityThreadMain.
  19.  
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  20.  
    Looper.loop();
  21.  
     
  22.  
    throw new RuntimeException("Main thread loop unexpectedly exited");
  23.  
    }

這個類藏的比較深,你能夠在Android-SDK\sources\android-24\android\app 這個目錄中找到。

也就是說,在一個Android 程序啓動之初,系統會幫咱們爲這個主線程建立好Looper。只不過這個方法名字比較特殊,叫作prepareMainLooper。

  1.  
    public static void prepareMainLooper() {
  2.  
    prepare( false);
  3.  
    synchronized (Looper.class) {
  4.  
    if (sMainLooper != null) {
  5.  
    throw new IllegalStateException("The main Looper has already been prepared.");
  6.  
    }
  7.  
    sMainLooper = myLooper();
  8.  
    }
  9.  
    }

注意這裏調用prepare時傳遞的參數值爲false,和咱們以前建立普通Looper時是不一樣的,這也 能夠理解,由於這是主線程,怎麼能夠被容許被外部代碼終止呢。

到這裏,咱們終於完整的理解了開頭咱們提到的第一個代碼塊的內容了。

至於第二種使用寫法出錯的緣由也在明顯不過了,主線程會在程序啓動時在main方法中幫咱們主動建立Looper,調用loop方法;而咱們本身建立的線程就得咱們主動去調用Looper.prepare(),這樣才能保證MessageQueue被建立,程序不會奔潰;可是咱們所指望的Toast依然沒有顯示出來,這是爲何呢?由於,咱們沒有調用loop方法。消息被加入隊列了,可是沒有辦法彈出。所以咱們將代碼修改以下:

  1.  
    new Thread(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
     
  5.  
    Looper.prepare();
  6.  
     
  7.  
    try {
  8.  
    Thread.sleep( 2000);
  9.  
    } catch (InterruptedException e) {
  10.  
    e.printStackTrace();
  11.  
    }
  12.  
     
  13.  
    Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();
  14.  
     
  15.  
    Looper.loop();
  16.  
     
  17.  
    }
  18.  
    }).start();

這樣就沒問題了,Toast就能夠顯示出來了。實際上,平時寫代碼確定不會這麼寫,這裏只是爲了說明問題。

handleMessage

回想一下,咱們使用Handler最多見的場景:

  1.  
    handler = new MyHandler();
  2.  
     
  3.  
    private class MyCallback implements Callback {
  4.  
     
  5.  
    @Override
  6.  
    public void onFailure(Call call, IOException e) {
  7.  
    Message msg = new Message();
  8.  
    msg.what = 100;
  9.  
    msg.obj = e;
  10.  
    handler.sendMessage(msg);
  11.  
    }
  12.  
     
  13.  
    @Override
  14.  
    public void onResponse(Call call, Response response) throws IOException {
  15.  
    Message msg = new Message();
  16.  
    msg.what = 200;
  17.  
    msg.obj = response.body().string();
  18.  
    handler.sendMessage(msg);
  19.  
    }
  20.  
    }

上面的代碼是OKHttp的回調方法,因爲其回調方法不處於UI 線程,所以須要咱們經過Handler將結果發送到主線中取執行。
那麼這又是怎樣實現的呢?

前面咱們截圖說過,Handler爲咱們提供許多sendMessage 相關的方法,所以這裏咱們在onResponse 中執行的sendMessage 通過層層傳遞,異曲同工依然會回到MessageQueue的enqueueMessage方法,也就是說全部的sendMessageXXX方法完成的工做無非就是隊列入棧的工做,就是將包含特定信息的Message加入到MessageQueue中。而咱們也知道,經過loop方法,會從MessageQueue中取出Message,執行每個Message 所對應Handler的dispatchMessage方法,咱們再看一次這個方法:

  1.  
     
  2.  
     
  3.  
    public void dispatchMessage(Message msg) {
  4.  
    if (msg.callback != null) {
  5.  
    handleCallback(msg);
  6.  
    } else {
  7.  
    if (mCallback != null) {
  8.  
    if (mCallback.handleMessage(msg)) {
  9.  
    return;
  10.  
    }
  11.  
    }
  12.  
    handleMessage(msg);
  13.  
    }
  14.  
    }

這一次,咱們建立的Message很簡單,其callback屬性必然是空的,並且在實例化Handler時,調用的是其無參構造函數 ,所以這個時候,就會執行最後一行代碼handleMessage(msg) ;

  1.  
    /**
  2.  
    * Subclasses must implement this to receive messages.
  3.  
    */
  4.  
    public void handleMessage(Message msg) {
  5.  
    }

空的 ! 沒錯,這個方法就是空的,由於這是須要咱們在Handler的繼承類中本身實現的方法呀。好比下面這樣;

  1.  
    class MyHandler extends Handler {
  2.  
    @Override
  3.  
    public void handleMessage(Message msg) {
  4.  
    super.handleMessage(msg);
  5.  
    loading.setVisibility(View.GONE);
  6.  
    switch (msg.what) {
  7.  
    case 100:
  8.  
    Object e = msg.obj;
  9.  
    Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
  10.  
    break;
  11.  
    case 200:
  12.  
    String response = (String) msg.obj;
  13.  
    tv.setText(response);
  14.  
    break;
  15.  
    default:
  16.  
    break;
  17.  
    }
  18.  
    }
  19.  
    }

咱們在handleMessage方法中,實現了本身的處理邏輯。

總結

好了,這就是Handler的實現機制,這裏再作一次總結稱述。

  • 經過Looper的prepare方法建立MessageQueue
  • 經過loop方法找到和當前線程匹配的Looper對象me
  • 從me中取出消息隊列對象mQueue
  • 在一個死循環中,從mQueue中取出Message對象
  • 調用每一個Message對象的msg.target.dispatchMesssge方法
  • 也就是Handler的dispatchMessage 方法
  • 在dispatchMessage 根據Message對象的特色執行特定的方法。

至此,終於弄明白了Handler的運行機制。

相關文章
相關標籤/搜索