天天看點

Android Handler機制 源碼解析0. 前言1. 總覽2. 從投遞消息入手3. 從取消息回看總結參考

0. 前言

Handler

Android

開發過程可以說是必不可少的一枚棋,它保證了系統運作過程中的消息有序進行傳遞和處理。此文将在

Android 6.0

源碼層面對

Handler

的運作機制進行簡要剖析。

1. 總覽

Handler的内部實作主要涉及到這三個類: Thread、MessageQueue和Looper。它們之間的關系可以用如下的圖來簡單說明:

Android Handler機制 源碼解析0. 前言1. 總覽2. 從投遞消息入手3. 從取消息回看總結參考
Thread是最基礎的,Looper和MessageQueue都建構在Thread之上,Handler又建構在Looper和MessageQueue之上,我們通過Handler間接地與下面這幾個相對底層一點的類打交道。

2. 從投遞消息入手

2.1 最小突破口

首先建立Handler對象,來看構造方法:

// 預設構造方法
public Handler() {
        // 調用雙參構造方法
        // 參數為 無回調 同步
        this(null, false);
    }

// 回調接口
public interface Callback {
    public boolean handleMessage(Message msg);
}

// 回調參數 是否異步參數 構造方法
public Handler(Callback callback, boolean async) {
    // FIND_POTENTIAL_LEAKS 為常量 false
    // if内代碼 可能為預留接口代碼
    if (FIND_POTENTIAL_LEAKS) {
        // 恒為false 不會執行
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == ) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
        }
    }
    // 取得目前線程中準備好了的Looper 
    // 下文會介紹
    mLooper = Looper.myLooper();
    // 如果為空 說明沒有調用 Looper.prepare()
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // 取得Looper中的消息隊列 便于投遞消息的入隊操作
    mQueue = mLooper.mQueue;
    // 參數指派
    mCallback = callback;
    mAsynchronous = async;
}
           

然後是投遞消息:

// 發送即時消息
public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, );
}
// 發送延時消息
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < ) {
        delayMillis = ;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
           

其實,經過觀察,多個發送消息的方法其實最終都是調用的一個方法:

sendMessageAtTime(...)

,如圖所示:

Android Handler機制 源碼解析0. 前言1. 總覽2. 從投遞消息入手3. 從取消息回看總結參考

2.2 準備投遞

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);
}
           

2.3 包裝消息

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 每個Message有一個指向Handler自身的Target
    // 用于後期對handleMessage()方法的回調
    msg.target = this;
    // 設定是否異步
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
           

關于異步

異步消息表示的是中斷或者相對于同步消息而言不需要全局排序的事件。

2.4 入消息隊列

接下來将目光移向

MessageQueue

的實作。

public final class MessageQueue {
    ...
    boolean enqueueMessage(Message msg, long when) {
        // 首先確定Handler存在
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // 確定該消息是否被标記為已使用
        // 與msg.markInUse()方法呼應
        // 采用與運算判斷,采用或運算置位
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        // 取得同步鎖
        synchronized (this) {
            // 如果處于銷毀狀态
            // 由Looper控制
            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;
            // < mMessages是按照時間順序排列的消息連結清單 >
            Message p = mMessages;
            boolean needWake;
            // 如果消息連結清單為空 或者時間無效
            // 或者目前即時消息将最先發生處理:
            if (p == null || when ==  || 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; 
                prev.next = msg;
            }

            // 調用本地方法喚醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    ...
}
           

至此,消息的入隊列過程就完成了,也可以說是消息投遞完成了,接下來就是郵差

Looper

來取消息并且回調收件人

handleMessage()

方法了。

3. 從取消息回看

3.1 用法回顧

在尋找

Looper

的突破口時,我們先來回顧一下

Looper

Handler

的配合用法:

// 一個新的線程
class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        // 首先調用Looper初始化操作
        Looper.prepare();
        // 建立Handler對象
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // 處理消息回調
            }
        };
        // Looper循環
        Looper.loop();
    }
}
           

可以看到,要使用

Handler

,必先調用

Looper

的相關方法,也就是說我們使用到安卓主線程中,必定在某一個地方悄悄地為我們調用了

Looper

的相關方法,使得我們可以直接使用

Handler

。那麼接下來我們就去看看到底在哪裡調用了?

3.2 主線程

在主線程

ActivityThread

中,可以找到Main函數,這也是一個應用啟動的入口,在主函數中不難發現對

Looper

的相關方法調用:

public final class ActivityThread {
    ...
    public static void main(String[] args) {
        ...
        // 準備Looper
        Looper.prepareMainLooper();
        // 建立Handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        // Looper循環
        Looper.loop();
        ...
    }
}
           

接下來就該對這幾行代碼分别展開作分析了。

3.3 Looper準備工作

準備分為兩種情況,一種是非主線程,一種是主線程。

非主線程:

public static void prepare() {
    // 直接調用prepare方法 參數為允許銷毀
    prepare(true);
}
           

主線程:

public static void prepareMainLooper() {
    // 調用prepare方法 參數為不允許銷毀
    // 見下面方法
    prepare(false);
    synchronized (Looper.class) {
        // 如果sMainLooper不為空
        // 說明已經初始化 抛出異常
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        // 擷取Looper并指派
        sMainLooper = myLooper();
    }
}

// 存放Looper容器
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

// 準備函數 參數為是否運作過程中銷毀
private static void prepare(boolean quitAllowed) {
    // 如果容器中已經有Looper
    // 說明已經建立 抛出異常
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 建立Looper并存入容器
    sThreadLocal.set(new Looper(quitAllowed));
}

// 傳回容器中的Looper
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
           

3.4 Looper循環

Looper

準備好了,就要開始循環取出消息了。

// 循環方法
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中的消息隊列
    // 這和Handler中取得目前線程Looper中的消息隊列是同一個
    final MessageQueue queue = me.mQueue;
    // 確定IPC身份為本地程序 同時記錄身份
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    // 死循環
    for (;;) {
        // 從消息隊列中取出消息
        // 時間未到時會阻塞
        Message msg = queue.next(); 
        if (msg == null) {
            // 如果為空 表示處于銷毀狀态
            return;
        }
        // mLogging是外部可對Looper設定Printer以輸出Log
        Printer logging = me.mLogging;
        // 列印Log日志
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        // target為目标Handler 将Message分發過去
        // 下文細解
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        // 列印日志
        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);
        }
        // 回收消息
        msg.recycleUnchecked();
    }
}
           

這裡讀者可能會有疑惑,既然loop()方法中包含死循環,那程式執行到調用loop()不就卡住了,不會執行後續語句啊?對,沒錯,是以Looper.loop()方法,是ActivityThread主方法中的最後一條語句啊:

public final class ActivityThread {
    ...
    public static void main(String[] args) {
        ...
        Looper.loop();
        // 如果再執行到下面 說明循環意外終止了
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}
           

最後,再對消息的分發作分析:

public class Handler {
    ...
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 觸發Runnable回調
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                // 否則 觸發Callback回調
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 再否則 直接回調handleMessage()方法
            handleMessage(msg);
        }
    }
}
           

這樣的話,當消息觸發後就發生回調,回調完成後消息被釋放,一次消息傳遞之旅就圓滿完成了。

總結

最後的總結,我想用一張圖檔來表達我對

Handler

機制的了解,

THread

中包含多個

Handler

和一個

Looper

Looper

中有一個消息隊列,裡面儲存着需要觸發的消息,每個消息包含了投遞該消息的

Handler

對象,

Looper

中的

loop()

方法循環取出消息,并回調

Handler

,這就是

Handler

機制的工作流程。希望筆者的文章對大家有所幫助,也祝大家學習進步!

Android Handler機制 源碼解析0. 前言1. 總覽2. 從投遞消息入手3. 從取消息回看總結參考

參考

  1. 深入源碼解析Android中的Handler,Message,MessageQueue,Looper
  2. 《Android開發藝術探索》 任玉剛