Android Handler 原始碼探索

惡魔小渣發表於2019-04-17

一、概述

Handler 作為一種非同步訊息通訊機制,通常在面試的時候會被問到原始碼部分,本篇文章就通過原始碼來揭開Handler的神祕面紗,真正瞭解Handler的本質。

一提起Handler,相信大家都會想到幾個重要的類:Handler、Message、MessageQueue、Looper。那這四者是如何協作的呢?下面我們一一來分析這幾個類的原始碼。

二、原始碼解析

首先先放一張整體流程圖,如下:

Android Handler 原始碼探索
整體的流程是: Message通過obtain()方法獲得一個message,並對message進行屬性及所需傳遞資料的設定,通過Handler的sendMessage()方法傳送訊息,MessageQueue通過enqueueMessage()方法將訊息新增入訊息池中,並通過Looper.loop()方法進行訊息的運輸,最後MessageQueue通過next()方法將訊息從訊息池中取出,並通過message的target屬性找到對應的Handler,由對應的Handler呼叫dispatchMessage()方法進行訊息的分發處理。

Message為傳輸的內容,所以我們從Message開始探索。

Message

Message 的屬性主要如下:

/**用來標誌一個訊息*/
 public int what;
/**如果傳遞的資料是整型的,可直接使用這兩個引數*/
 public int arg1;
 public int arg2;
 
 /*package*/ int flags;
 /*package*/ long when;
 /**如果傳遞的資料是複雜的型別,可放在Bundle中傳遞*/
 /*package*/ Bundle data;
 
/** 傳送和處理訊息所關聯的Handler*/
/*package*/ Handler target;

/** 訊息池*/
public static final Object sPoolSync = new Object();
private static Message sPool; //訊息連結串列的鏈頭
private static int sPoolSize = 0;
	
/**訊息池所放最多訊息數目*/
private static final int MAX_POOL_SIZE = 50;

複製程式碼

從以上原始碼我們可以看出Message是有一個存放訊息的訊息池的,那這個訊息池具體有什麼作用呢?我們從Message的建構函式繼續來看。

眾所周知,Message獲取一個物件的方式有三種:

  1. new Message()
  2. Message.obtain()
  3. Handler.obtainMessage()

下面為Handler.obtainMessage()的原始碼:

public final Message obtainMessage()
    {
        return Message.obtain(this);
    }
複製程式碼

從上面程式碼我們可以看到,Handler.obtainMessage()事實上還是呼叫的Message的obtain()方法。那麼我們繼續來看Message的obtain()方法:

 /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
複製程式碼

從原始碼中可以看到,Message.obatain()是先從訊息池中去取訊息,如果訊息池中有則將該訊息重置屬性引數,然後返回;如果訊息池中沒有訊息則建立一個新的訊息並返回。這樣做就可以避免重複過多的建立Message物件,減少了記憶體的佔用。

在上面的程式碼中我們看到了從訊息池中取出訊息重用的部分,那訊息是如何被新增到訊息池中的呢?是通過Message的recycleUnchecked()方法:

  void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // 將該訊息打上被回收的標籤,並清空該訊息的引數部分,加入到訊息池中。
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool; //next 為鏈頭指向的下一個訊息,sPool為鏈頭
                sPool = this;
                sPoolSize++;
            }
        }
    }
複製程式碼

從上面的程式碼我們可以看出,訊息池的本質其實是一個單連結串列,當有訊息進入訊息池的時候就將該訊息插入到鏈頭。從訊息池中取訊息的時候也是從鏈頭開始取出。 那麼recycleUnchecked()方法何時被呼叫呢?在MessageQueue和Looper中都有該方法的呼叫。 MessageQueue中的removeMessage()方法:

 void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();  //呼叫
                p = n;
            }

            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.what == what
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked(); //呼叫
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }
複製程式碼

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;

       //...
        for (;;) {
            //...
            msg.recycleUnchecked(); //呼叫
        }
    }

複製程式碼

MessageQueue

MessageQueue為管理Message的單連結串列,雖然MessageQueue的名字是訊息佇列,但不能被它的名字所誤導,其本質是披著佇列名字的單列表。Handler為MessageQueue新增訊息,然後由Looper從中取出訊息,從而實現訊息的運輸傳遞。

由於MessageQueue與Looper是一對一的關係,Looper的唯一性保證了MessageQueue的唯一性,所以其初始化是在Looper中完成的,一般由Looper.myQueue()獲得一個訊息佇列。

//quitAllowed 為是否允許中途退出的標誌,呼叫Native方法,暫不做深入探究
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
複製程式碼

新增訊息主要為enqueueMessage()方法:

 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) { //message必須有target,即message由handler傳送而來
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {  //判斷該message是否已被使用
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) { //如果該佇列已經被退出,則不能再加入訊息
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();  //進入佇列後,標記該訊息在被使用中
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //將訊息新增入連結串列中
            if (p == null || when == 0 || when < p.when) {
                // 如果該佇列為空,或者新加入的訊息所用時間最短,則將該訊息作為連結串列表頭,如果該佇列是在阻塞狀態則喚醒該佇列
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //通過迴圈對比新加入的訊息所用執行時間與佇列中已有訊息佔用執行時間,尋找新加入訊息在佇列中的位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //找到位置,插入新訊息
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製程式碼

訊息出隊的方法為next()方法,下面讓我們看下具體原始碼實現。

Message next() {
        //當應用退出重啟的時候,訊息loop會退出或者被釋放,這個時候不支援message被取出佇列,所以直接結束該方法。
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //等待
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 檢索下一個訊息,並返回
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;  //mMessages始終指向訊息佇列的頭節點訊息,如果該物件為空,則說明訊息佇列為空
                if (msg != null && msg.target == null) {
                    //如果訊息沒有target,那它就是一個屏障,需要一直往後遍歷找到第一個非同步的訊息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                         //如果下一個訊息還沒有準備好,則設定一個定時器當該訊息準備好時喚醒它執行
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 取出訊息
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                        //保證mMessages始終指向頭節點
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //沒有訊息.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //·····
        }
    }

複製程式碼

Looper

在瞭解Message和MessageQueue後,我們已經知道Message是如何加入到MessageQueue中,那麼在MessageQueue中,訊息又是如何被排程的呢?下面就讓我們一起來看下Looper的功能。

Looper的主要方法有prepare()和loop(),下面讓我們依次來探索。


     private static final String TAG = "Looper";
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // 主執行緒中的Lopper

    final MessageQueue mQueue;  //當前Looper管理的訊息佇列 
    final Thread mThread;   // 當前Looper對應的執行緒

    private Printer mLogging;
    private long mTraceTag;
複製程式碼

Looper的屬性比較簡單,Looper主要通過prepare()方法來建立Looper。

 public static void prepare() {
        prepare(true);
    }
    
 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都是通過一個ThreadLocal物件來儲存。而且在儲存前對該ThreadLocal.get()方法進行了判空,說明Looper.prepare()方法只能被呼叫一次,該ThreadLocal物件只能儲存一個Looper,從而保證每個執行緒只能有一個Looper.那麼ThreadLocal是怎樣管理執行緒和Looper的呢,讓我們看下它的主要原始碼:

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    
     public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
複製程式碼

通過上面程式碼我們可以看到,ThreadLocal當中有一個ThreadLocalMap來儲存Thread和Looper資料,並關聯二者。好了,現在我們知道一個執行緒只有一個唯一的Looper後,讓我們繼續看下Looper的建構函式。

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

複製程式碼

我們知道Looper的prepare()方法只能呼叫一次,那麼由此而知Looper的建構函式也只會呼叫一次。而MessageQueue是在Looper的建構函式中建立的,那麼由此可以推斷Looper的唯一決定了MessageQueue的唯一,也就是說一個執行緒有唯一的一個Looper,這個Looper管理一個唯一的MeesageQueue.

現在理清了Looper和MessageQueue的關係,讓我們看下Looper是如何管理MessageQueue的。

 public static void loop() {
        //獲得當前執行緒的Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //獲得當前Looper管理的訊息佇列
        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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;
        //通過無限迴圈取出訊息進行排程
        for (;;) {
             //如果沒有阻塞則從訊息佇列中取出訊息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 如果沒有訊息說明該訊息佇列已經退出
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                //通過Message屬性找到對應的Handler 分發訊息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            //進行Message的回收,釋放佔據的資源
            msg.recycleUnchecked();
        }
    }
複製程式碼

由以上的原始碼分析,我們可以總結下Looper的主要功能:

1、繫結當前執行緒,保證當前執行緒唯一的Looper,同時保證唯一的MessageQueue;

2、執行loop()方法,不斷從訊息佇列中排程訊息,並通過訊息的target屬性找到對應的Handler 執行dispatchMessage()方法處理訊息;

現在有了儲存訊息的工具和排程訊息的工具,只缺少處理訊息的工具了,這個工具正是大名鼎鼎的Handler。下面讓我們一起去看看Handler的原始碼。

Handler

Handler的主要功能是什麼呢?首先,Handler在子執行緒中將Message傳送到MessageQueue中;然後,等待Looper排程訊息後,通過Message的target屬性找到傳送該訊息的Handler;最後,Handler在自己的dispatchMessage()中呼叫handleMessage()方法進行訊息的最終處理。下面讓我們一步步揭開原始碼,首先從Handler的建構函式開始:

 public Handler(boolean async) {
       this(null, async);
   }

 public Handler(Callback callback, boolean async) {
       if (FIND_POTENTIAL_LEAKS) {
           final Class<? extends Handler> klass = getClass();
           if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                   (klass.getModifiers() & Modifier.STATIC) == 0) {
               Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                   klass.getCanonicalName());
           }
       }
       //獲得當前執行緒儲存的Looper例項
       mLooper = Looper.myLooper();
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread " + Thread.currentThread()
                       + " that has not called Looper.prepare()");
       }
       //通過獲得的Looper例項獲取對應的MessageQueue,從而Handler和MessageQueue關聯上了
       mQueue = mLooper.mQueue;
       mCallback = callback;
       mAsynchronous = async;
   }

複製程式碼

Handler主要的功能是進行訊息的傳送和處理。傳送訊息的方法主要為sendMessage()和post()方法。處理訊息的方法主要為dispatchMessage()方法。

Handler傳送的主要有兩種型別:Message和Runnable。Message主要是通過sendMessage()的方式,Runnable主要是通過post()的方式。

public final boolean post(Runnable r)
   {
      return  sendMessageDelayed(getPostMessage(r), 0);
   }
複製程式碼

通過原始碼我們可以知道,post()的方法最終採用的還是sendMessage()的方式。

public final boolean sendMessage(Message msg)
   {
       return sendMessageDelayed(msg, 0);
   }
   
public final boolean sendMessageDelayed(Message msg, long delayMillis)
   {
       if (delayMillis < 0) {
           delayMillis = 0;
       }
       return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
   }
 
 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);
   }  

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
       msg.target = this;  //標記msg對應的handler
       if (mAsynchronous) {
           msg.setAsynchronous(true);
       }
       return queue.enqueueMessage(msg, uptimeMillis);
   }
複製程式碼

根據以上原始碼分析,我們可以看到sendMessage()方法最終是呼叫了MessageQueue的enqueueMessage()方法,實現了Handler將訊息傳送並加入到訊息佇列中。enqueueMessage()方法在前面已經講過了,這裡不再贅述。到現在已經比較清楚的知道,當訊息加入到訊息佇列後,Looper會呼叫prepare()和loop()方法,在當前執行的執行緒中儲存一個Looper例項,這個例項會儲存一個MessageQueue物件,然後當前執行緒進入一個無限迴圈中去,不斷從MessageQueue中讀取Handler發來的訊息。然後再回撥建立這個訊息的handler中的dispathMessage方法進行訊息的處理。

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {  //如果是採用post()方式時走此方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {  //採用Handler.Callback 為引數構造 Handler時走此方法 
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
       private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    //空實現
      public void handleMessage(Message msg) {
    }
複製程式碼

最後我們可以看到一個空實現的handlerMessage()方法,這是為什麼呢?因為訊息的最終回撥是由我們控制的,我們在建立handler的時候都是複寫handleMessage方法,然後根據msg.what進行訊息處理,例如:

private Handler mHandler = new Handler()
	{
		public void handleMessage(android.os.Message msg)
		{
			switch (msg.what)
			{
			case value:
				
				break;
 
			default:
				break;
			}
		};
	};
複製程式碼

好了,以上就是全部的原始碼探索啦,最後再總結一下:

1、Looper通過prepare()方法儲存一個本執行緒的例項,然後這個Looper例項儲存一個MessageQueue,因為prepare()方法只能呼叫一次,所以保證了每個執行緒只對應唯一一個Looper例項,從而保證了一個Looper對應唯一的MessageQueue。

2、當前執行緒會在Looper的帶領下進入一個無線迴圈從MessageQueue中去不斷取訊息,然後通過Message的target屬性找到對應的Handler,然後通過dispatchMessage()方法來進行訊息的分發。

3、Handler在構造的時候首先會找到當前執行緒的Looper例項,並通過該例項找多該Looper對應的唯一的MessageQueue,由此Handler和MessageQueue之間建立聯絡。Handler在sendMessage()的時候,會為每個訊息的target賦值自身,然後再加入到MessageQueue中。

4、在我們構造Handler時會同時複寫handleMessage()方法,所以在Looper找到Message對應的Handler後,由Handler呼叫dispatchMessage(),即最終回撥handleMessage()進行最終訊息的處理。

最後本次Handler原始碼分析結束~

相關文章