Android中Handler機(jī)制淺談

Android中的Handler一般是用于異步任務(wù),和Handler相關(guān)的一些概念有Looper,MessageQueue,下面先通過一張圖來展示三者只之間的關(guān)系。


MessageQueue主要是維護(hù)消息隊(duì)列,Handler主要是消息的發(fā)送和處理,Looper扮演著管理者這么一個角色,由它來維護(hù)這個流程的正常執(zhí)行。
首先來分析一下這個管理者Looper,它是一個線程獨(dú)享的變量,所以再此之間還得先了解一個概念ThreadLocal,這是Android系統(tǒng)提供的一個類,用于實(shí)現(xiàn)線程獨(dú)享。

  • ThreadLocal的使用 。
    //存儲 ThreadLocal<Integer> tl = new ThreadLocal<>(); Integer var = new Integer(1); tl.set(var); //使用。在哪個線程中調(diào)用,就會使用哪個線程中的備份。 Integer local = tl.get();

  • ThreadLocal原理。
    在使用ThreadLocal時(shí)主要會用到兩個方法,set()和get()方法。直接從源碼看起。
    public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
    values = initializeValues(currentThread);
    }
    values.put(this, value);
    }
    set的代碼很簡單,首先獲得當(dāng)前線程,再獲得一個Values變量,最后是調(diào)用了values.put(this,value)方法。
    public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
    Object[] table = values.table;
    int index = hash & values.mask;
    if (this.reference == table[index]) {
    return (T) table[index + 1];
    }
    } else {
    values = initializeValues(currentThread);
    }

      return (T) values.getAfterMiss(this);
    }
    

同樣get的邏輯也很清楚,首先獲得當(dāng)前線程,然后獲得一個Values變量,通過values中的table屬性來獲得所需的值。
set和get中都出現(xiàn)了一個類Value,接下來看一下Value這個類。Values是ThreadLocal的靜態(tài)內(nèi)部類,我們主要關(guān)注一下其中的table屬性,put和value方法。
Values values(Thread current) {
return current.localValues;
}
values方法非常簡單,就是獲得當(dāng)前線程的localValues變量。而localValues變量就是Thread類中的一個屬性。
Thread { ThreadLocal.Values localValues; } 想必看到這就明白為什么在不同線程環(huán)境下能夠獲得各自線程的本地變量了。因?yàn)閷?shí)質(zhì)上就是在每個Thread內(nèi)部存儲了一個本地變量,通過空間來換時(shí)間達(dá)到同步的作用。
static class Values {
/**
* Map entries. Contains alternating keys (ThreadLocal) and values.
* The length is always a power of 2.
*/
private Object[] table;

      void put(ThreadLocal<?> key, Object value) {
        cleanUp();

        // Keep track of first tombstone. That's where we want to go back
        // and add an entry if necessary.
        int firstTombstone = -1;

        for (int index = key.hash & mask;; index = next(index)) {
            Object k = table[index];

            if (k == key.reference) {
                // Replace existing entry.
                table[index + 1] = value;
                return;
            }

            if (k == null) {
                if (firstTombstone == -1) {
                    // Fill in null slot.
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;
                    return;
                }

                // Go back and replace first tombstone.
                table[firstTombstone] = key.reference;
                table[firstTombstone + 1] = value;
                tombstones--;
                size++;
                return;
            }

            // Remember first tombstone.
            if (firstTombstone == -1 && k == TOMBSTONE) {
                firstTombstone = index;
            }
        }
      }
  }

table變量很簡單,就是一個Object數(shù)組,用于存儲所需的值。接下來分析一下put方法。主體就是一個for循環(huán),遍歷所有的index,可以把index當(dāng)做映射關(guān)系中的鍵,通過對應(yīng)的鍵最終取到需要的值。
private static AtomicInteger hashCounter = new AtomicInteger(0); private final int hash = hashCounter.getAndAdd(0x61c88647 * 2); //Weak reference to this thread local instance. private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);
把table作為鍵值的存儲結(jié)構(gòu),index下標(biāo)所對應(yīng)的為鍵,index + 1下標(biāo)所對應(yīng)的為實(shí)際存儲的值。
如果k已經(jīng)存在,就直接更新它的值。如果k不存在,就把key.reference存入table中的index下標(biāo)位置,把值存在table中index + 1的下標(biāo)位置。

  • Looper-消息的管理者
    在理解了ThreadLocal的原理之后,就可以明白Looper為什么是線程獨(dú)享的了。一般的使用方法如下:
    Looper.prepare(); . . . Looper.loop();
    handler必須得在這個代碼塊中間初始化。
    首先看看prepare方法中的代碼:
    public static void prepare() {
    prepare(true);
    }
    就調(diào)用了一個重載方法:
    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));
    }
    sThreadLoca就是Looper內(nèi)部的ThreadLocal屬性。首先通過get方法獲得Looper變量,如果不為空,就會報(bào)錯,也就是Looper.prepare方法為什么只能調(diào)用一次的原因。如果不為空,就調(diào)用set方法,把Looper變量存儲ThreadLocal的table中。
    接下來分析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 (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
    // No message indicates that the message queue is quitting.
    return;
    }
    final long traceTag = me.mTraceTag;
    try {
    msg.target.dispatchMessage(msg);
    } finally {
    if (traceTag != 0) {
    Trace.traceEnd(traceTag);
    }
    }
    }
    }
    首先獲得Looper變量me,myLooper方法很簡單,如下:
    public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
    }
    就是通過sThreadLocal的get方法獲得當(dāng)前線程的Looper變量。
    然判斷me是否為空,為空就報(bào)錯,也就是說loop方法必須在prepare方法之后調(diào)用。
    接著拿到MessageQueue對象,這個對象是Looper的內(nèi)部屬性,在構(gòu)造函數(shù)中進(jìn)行初始化。
    隨后就是for循環(huán),這是一個無限循環(huán)體,退出的唯一條件就是獲取到的消息為null。
    通過queue去拿到需要處理的消息(這個消息是通過handler發(fā)出的),然后注意next方法是會阻塞的,也就是如果沒有消息,程序會一直停留在這一行代碼。
    當(dāng)取得消息不為空,就執(zhí)行msg.target.dispatchMessage(msg),msg.target其實(shí)就是handler,handler中有一個方法dispatchMessage(Message msg),這樣就把消息的處理交到了handler的手里,完成了Looper的交接任務(wù)。

  • MessageQueue-消息隊(duì)列
    MessageQueue主要就是維持一個消息隊(duì)列。主要的方法就是enqueueMessage和next。
    boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
    msg.markInUse();
    msg.when = when;
    Message p = mMessages;
    boolean needWake;
    if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
    } else {
    // Inserted within the middle of the queue. Usually we don't have to wake
    // up the event queue unless there is a barrier at the head of the queue
    // and the message is the earliest asynchronous message in the queue.
    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;
    }
    

mMessages是MessageQueue中的屬性,它指向消息隊(duì)列的頭部,即下一刻需要處理的消息。這里第一個if判斷需要添加新的頭部。條件1是當(dāng)前隊(duì)列為空,條件2是當(dāng)前加入的消息為立刻處理,條件3是當(dāng)前加入的消息比隊(duì)列首部的消息優(yōu)先級高(通過消息需要觸發(fā)時(shí)的時(shí)間先后來判斷)。三者任意一個滿足,就需要把當(dāng)前消息作為頭部加入消息隊(duì)列。
如果不滿足,則進(jìn)入else部分。通過一個for循環(huán),把消息插入到隊(duì)列的中間,具體的位置是根據(jù)優(yōu)先級來判斷。
這樣消息的插入就完成了。接下來分析如何取出消息(核心代碼):
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

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

方法的主體就是一個for無限循環(huán)體,這也就解釋了為什么在Looper中調(diào)用MessageQueue的next方法會阻塞了。now是系統(tǒng)當(dāng)前的時(shí)間,msg即當(dāng)前的頭部消息,如果不為空,且msg.when 大于 now,即說明當(dāng)前消息還沒有到需要處理的時(shí)候,那么讓線程掛起。等待nextPollTimeoutMillis 時(shí)間后,喚醒線程繼續(xù)處理消息。
如果消息已經(jīng)可以處理,那么移動頭部引用mMessage為msg的下一個,然后返回當(dāng)前消息。
至此,消息的添加和取出的邏輯已經(jīng)分析完畢。

  • Handler-消息的處理者
    首先看handler的使用代碼:
    public static Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
    //具體邏輯處理
    }
    };
    一般都是實(shí)現(xiàn)一個匿名內(nèi)部類,然后重寫handleMessage方法。下面分析一下,handler的構(gòu)造函數(shù)進(jìn)行了哪些操作:
    public Handler(Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    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有很多重載的構(gòu)造函數(shù),但最后都會調(diào)用此方法。首先獲得當(dāng)前所關(guān)聯(lián)的Looper,這也就是為什么handler的初始化必須在Looper.prepare()之后了。隨后獲取當(dāng)前的消息隊(duì)列。
    接下來分析一下dispatchMessage方法,想必大家還記得在Looper中取到消息后調(diào)用了msg.target,dispatchMessage(),這里就分析一下消息是怎么處理的:
    public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
    handleCallback(msg);
    } else {
    if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
    return;
    }
    }
    handleMessage(msg);
    }
    }
    首先是判斷msg.callback,如果 不為空則直接調(diào)用handleCallback方法。而msg的callback其實(shí)就是一個Runnable對象,handleCallback方法也就是簡單的調(diào)用了Runnable的run方法。
    private static void handleCallback(Message message) {
    message.callback.run();
    }
    如果msg.callback為空,則判斷mCallback是否為空,而mCallback就是Handler的一個內(nèi)部接口:
    public interface Callback {
    public boolean handleMessage(Message msg);
    }
    這個接口為用戶提供了另外一種實(shí)現(xiàn)handler的方法,就是實(shí)現(xiàn)一個Callback,最為參數(shù)傳入Handler的構(gòu)造函數(shù),然后具體的消息處理邏輯就在Callback的handleMessage方法中去實(shí)現(xiàn),代碼實(shí)例如下:
    public static Handler mHandler = new Handler(new Handler.Callback(){
    @Override
    public void handleMessage(Message msg) {
    //具體邏輯處理
    }
    });
    再回到dispatchMessage方法中,如果mCallback.handleMessage(msg)返回了true,那么消息處理就結(jié)束了,如果返回了false,或者mCallback為空,則就會調(diào)用Handler的handleMessage(msg)方法,該方法默認(rèn)是空方法,沒有任何的操作。
    消息的處理流程理清楚后再來看看消息的發(fā)送,主要有post和sendMessage兩大類方法。
    post方法主要是通過Runnable,設(shè)置為msg.callback。下邊來看一個post方法:
    public final boolean post(Runnable r)
    {
    return sendMessageDelayed(getPostMessage(r), 0);
    }
    跟蹤一下sendMessageDelayed和getPostMessage方法:
    private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
    }

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

這兩個方法很簡單,最終會進(jìn)入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);
}
這個方法同樣邏輯非常簡單,那么再進(jìn)入enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
這里終于出現(xiàn)了msg.target = this,原來handler是在發(fā)送消息的時(shí)候關(guān)聯(lián)到具體的msg上的。
最后調(diào)用了queue的enqueueMessage方法,這個方法已經(jīng)在上邊分析過了。
下邊來分析一下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);
  }

同樣,最終還是會調(diào)用sendMessageAtTime這個方法。

  • 注意事項(xiàng)
    1 Handler在哪個線程進(jìn)行的初始化,那么最終消息的處理線程就一定是這個線程。
    2 Handler一定要在Looper初始化以后才能進(jìn)行初始化。
    3 主線程默認(rèn)已經(jīng)調(diào)用了Looper.loop(),所以才可以直接實(shí)例化Handler,若是在其他線程需要實(shí)例化Handler,那么一定要先調(diào)用Looper.loop(),為當(dāng)前線程初始化自己的Looper對象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容