Android多執行緒基礎 解析Handler機制

weixin_34337265發表於2018-02-01

解析非同步訊息處理機制

Android的非同步訊息處理主要由4個部分組成:Message,Handler,MessageQueue以及Looper。

1.Message:是線上程之間傳遞資訊,它可以在內部攜帶少量資訊,用於在不同執行緒之間交換資料。
2.Handler:是處理者的意思,它主要用於傳送和處理訊息的。
3.MessageQueue:是訊息佇列的意思,他主要用於存所有通過Handler傳送的訊息,這部分訊息為一直存在於訊息佇列中,等待被處理。每個執行緒只會有一個MessageQueue物件。
4.Looper:每個執行緒MessageQueue的管理者。呼叫Looper.loop()方法後,會進入到一個無線迴圈中,只要一發現MessageQueue中存在一條訊息,就會將他取出,並且傳遞到Handler的handleMessage()方法中,每個執行緒也只會有一個Looper物件。

非同步訊息處理流程:

需要在主執行緒建立一個Handler物件,並重寫handleMessage( )方法,然後在子執行緒需要更新UI的時候,建立一個Message物件,並通過Handle將資訊傳送,之後這條訊息會被新增到MessageQueue中等待被處理,而Looper會一直迴圈的從MessageQueue中取出訊息,最後分發回撥handleMessage( )方法中。而此時操作也就變換到了主執行緒,因此可以放心的更新UI了。


7403980-28e98923c0514620.png
.png

問題一:在子執行緒想更新UI的時候,為什麼Handler必須宣告在主執行緒呢?

解決這個問題之前,要先知道,UI執行緒的訊息迴圈是在ActivityThread.main方法中建立的,該函式為Android應用程式入口,程式碼如下:

    Looper.prepareMainLooper();//1.建立訊息迴圈Looper
   ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();  //2.開啟訊息迴圈
    throw new RuntimeException("Main thread loop unexpectedly exited");

由這段程式碼可以知道,當應用程式建立啟動時, Looper.prepareMainLooper();就已經幫我們建立了Looper,並且開啟了訊息迴圈。
而當我們在子執行緒執行耗時操作後,就需要Handler將一個訊息Post到UI執行緒,然後在handleMessage( )處理,然而每個Handler都會關聯一個訊息佇列,訊息佇列被封裝在Looper中。

問題二:既然Handler關聯了執行緒以及訊息佇列,那麼他們是如何關聯的呢?摘取一段程式碼

public Handler( ){
     //省略上面程式碼
        mLooper = Looper.myLooper();//獲取Looper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//獲取訊息佇列
        mCallback = callback;
        mAsynchronous = async;
}

從以上原始碼可知,Handler預設函式會獲取Looper物件,還有訊息佇列,那麼他們是如何獲取訊息物件的呢?進去 myLooper,如下:

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

發現它是 sThreadLocal.get( )獲取的,那麼它又是什麼時候存了 Looper物件?繼續看,就會發現這麼一段程式碼

  private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));//建立了Looper物件並賦值給了sThreadLocal
    }

這樣 Looper就和執行緒關聯上了,不同的執行緒就不能訪問對方的訊息佇列,再看回來Handler的預設建構函式, mQueue = mLooper.mQueue;//獲取訊息佇列,讓Looper物件和訊息佇列關聯,而Handler又和Looper關聯,Looper又與執行緒關聯,那麼他們就關聯起來了。
那麼問題一的答案就出來了:
預設情況下,訊息佇列只有一個即上面分析的主執行緒的訊息佇列它在 Looper.prepareMainLooper();方法中建立,而Handler又和Looper關聯,Looper又與執行緒(主執行緒)關聯,此時只有Handler宣告下主執行緒,handleMessage才會在主執行緒執行,在主執行緒更新UI才是安全的。

問題三:建立Looper後,是如何執行訊息迴圈的?

在ActivityThread.main方法中進去看Looper.loop().

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
       //死迴圈
        for (;;) {
            Message msg = queue.next(); //獲取訊息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //....省略
            try {
                msg.target.dispatchMessage(msg);//訊息處理
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
             //....省略
            msg.recycleUnchecked();//回收訊息
        }
    }

在 msg.target.dispatchMessage(msg)中,msg是Message型別進去 Message

public final class Message implements Parcelable {
  Handler target;
  Runnable callback;
  Message next;
}

可以知道target是一個Handler,在上一篇中知道,我們通過知道了Handler將訊息傳送給訊息佇列,然後在這裡訊息佇列又會把訊息分發給Handler處理。
看下訊息分發處理:

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

從上面的Message型別可以知道callback是Runnable型別的,在分發的時候回進行判斷,不為空由 handleCallback(msg);處理,為空時 handleMessage(msg);處理,而 handleMessage(msg);是一個空方法,我們平時寫的更新UI操作也是在這裡面執行的。其實他就是Handler分發的兩種方式,一種Post(Runnable callback),一種sendMessage:

  public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
  public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

這兩種最後都是會執行sendMessageDelayed()方法:

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

繼續進去看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()方法。

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);//將訊息插入訊息佇列
    }

msg.target = this設定當前Handler物件
因此可以知道不管是Pos方法還是sendMessage方法,最後Handler都會把訊息加到訊息佇列MessageQueue中。然後Looper會不斷的去獲取訊息,並且呼叫 Handle的dispatchMessage的訊息也就是( msg.target.dispatchMessage(msg);//訊息處理),這樣訊息就會不停的被產生,被新增,和被處理。這就是訊息的處理機制

相關文章