Android進階 - 消息處理機制探索

前言

Android消息機制,是Android中核心機制之一,進階路上的基礎知識,其主要指的是Handler運行機制,而Handler運行需要底層的MessageQueue、Looper的支撐,下面我們共同探索。

核心概念綜述

Handler

對於Handler,許多初學者認爲它基本用於更新UI。
而官方解析道:
There are two main uses for a Handler:
(1) to schedule messages and runnables to be executed as some point in the future;
(2) to enqueue an action to be performed on a different thread than your own.
翻譯過來:
(1) 調度message、runnable的執行
(2) 使動作在不同的線程中執行。
其並沒有提及更新UI的作用,從本質上看,Handler並不是專門用來更新UI的,只是對於開發者來說,它常被用於更新UI,實際Handler是Android消息機制的一個重要組成部分,更新UI不過是消息傳遞與處理的其中一部分。

Message

代表一個行爲或是一串動作,每一個消息加入消息隊列時,都有明確的Handler作爲目標。

MessageQueue

字面意思 - 消息隊列,以隊列形式對外提供插入、刪除工作,但內部以單鏈表存儲結構實現,並不是真正的隊列,而是採用單鏈表的數據結構來存儲消息列表。

Looper

消息循環,Looper會以無限循環形式去MessageQueue查找是否有新消息,如果有則處理,沒有則等待。
注意:
1. 線程中默認沒有Looper,如果使用Handler就必須爲線程創建Looper。創建Handler之前,需要Looper.prepare(),最後要補上Looper.loop();
2. 主線程(UI線程),ActivityThread創建時已經初始化Looper了,故可以直接使用

ThreadLocal

Handler創建時候需要獲取到當前線程中的Looper來構造消息循環系統,Handler內部正是通過ThreadLocal來獲取的。
ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命週期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的複雜度。
詳解可參見:解密ThreadLocal

消息機制流程解析

總體流程

創建 Handler時,會獲取當前線程的Looper來構建消息循環系統,如果當前線程沒有Looper,則會報錯。

//Handler無參構造方法(部分)
public Handler() {
   ...
    //從sThreadLocal中取出當前對象的Looper
    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 = null;
}

Handler創建完畢後,內部的Looper以及MessageQueue就可以與Handler一起協同工作了,Handler通過post投遞Runnable或者send方法發送消息(post方法最終也是通過send方法來完成的),Runnable與Message最終都會交給Looper中處理。

Send方法流程:通過MessageQueue的enqueueMessage方法將消息放入消息隊列,Looper發現有新消息到來,則處理該消息。最終消息中的Runnable或者HandlerMessage方法會被調用。
注意:
1. Looper運行在Handler所在線程中的,當消息交給Looper處理時候,業務邏輯已經成功切換到創建Handler所在的線程中了。
1. 每個線程都至多只可以有一個Looper對象,也只可以有一個與該Looper對象關聯的MessageQueue對象。
2. 每個線程可以有多個Handler對象,Looper將Message取出來後,會調用與該Message關聯的Handler的dispatchMessage方法。

下面引用一張關係來闡述:

解析:
- Runnable和Message可以被壓入某個MessageQueue。而需要注意的是,某種類型的MessageQueue一般只允許保存相同類型的Object
- 實際上,源碼中會對Runnable進行相應處理,轉換成Message再放入到對應MessageQueue中

流程總結:
1. Handler創建時,通過sThreadLocal取出當前對象的Looper構建消息循環系統,若當前線程沒有Looper則報錯
2. Handler通過post或send方法將Runnable投遞或者Message發送,Message入隊到MessageQueue,同時Looper死循環查看MessageQueue是否有消息,不斷取出新消息,交給對應Handler處理。(取出消息的時,內部實現了跨線程通信)
3. 即:Looper不斷獲取MessageQueue中的一個Message,然後由Handler來處理

深入理解 - 源碼探究

消息機制相關類聯繫

  1. Thread - Looper 1對1,一個Thread最多隻能擁有一個Looper
  2. Looper - MessageQueue 1對1,一個Looper只能對應一個MessageQueue
  3. MessageQueue - Message 1對N,一個MessageQueue中有N個Message
  4. Message - Handler N對1,每個Message最多指定一個Handler
  5. Thread - Handler 1對N,由上述關係推斷

相關類的類關係圖:

Handler

源碼路徑:frameworks/base/core/java/android/os/Handler.java
Handler功能一 - 處理Message(本職工作)

//相關函數
public void dispatchMessage(Message msg);//對Message進行分發
public void handleMessage(Message msg);//對Message進行處理
//源碼-----------------------------------------------------------
public void dispatchMessage(Message msg) {
    //判斷消息是否攜帶callback(Runnable對象)
    if (msg.callback != null) {
        //消息攜帶callback,則直接執行callback
        handleCallback(msg);//方法實質:msg.callback.run();
    } else {
        //判斷Handler.mCallback(Runnable對象)是否爲空
        if (mCallback != null) {
            //使用自帶的mCallback處理
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //消息沒有攜帶callback,handler自身也沒有mCallback才調用
        handleMessage(msg);
    }
}

/**
 * Subclasses must implement this to receive messages.
 * 子類必須實現該方法去接收msg
 */
public void handleMessage(Message msg) {
}
  1. Looper從MessageQueue中取出一個Message後,首先會調用dispatchMessage方法進行消息派發
  2. dispatchMessage方法會根據具體策略來將Message分發給相應負責類處理(上述源碼中爲默認處理方式)
  3. Handler的擴展子類可以重寫上述兩個方法來改變它的默認行爲

Handler功能二 - 將Message壓入MessageQueue
注意:該功能的設計形成了一個神奇的“循環圈”,熟悉類關係圖(見上文),便很好理解了:
Handler -(壓入消息)-> MessageQueue -(Looper取出消息)-> Message -(傳遞消息給Handler處理)-> Handler

//相關函數(部分)
//Post:
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
...
//Send:
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
...
//源碼(部分)-------------------------------------------------------
public final boolean post(Runnable r)
   {
    //Post內部調用Send,需要先把Runnable轉成Message
    return  sendMessageDelayed(getPostMessage(r), 0);
   }

/**
 * 將Runnable轉成Message的主要方法
 */
private static Message getPostMessage(Runnable r) {
        /*Android系統內部維護一個全局Message池,當用戶需要用Message時,
        通過Message.obtain直接獲取即可,避免創建,節約資源*/
        Message m = Message.obtain();
        //轉換實質:將Runnable對象設置成Message的callback(回調函數)
        m.callback = r;
        return m;
    }

/**
 * 準備好Msg,發送消息,實現delay邏輯後,調用sendMsgAtTime
 */
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;
    //一般每個Thread都會有一個MessageQueue來承載消息
    if (queue == null) {
        //沒有Queue則拋異常
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    //壓入MQ
    return enqueueMessage(queue, msg, uptimeMillis);
}

MessageQueue

源碼:frameworks/base/core/java/android/os/MessageQueue.java

//構造方法
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    //本地創建一個NativeMessageQueue對象並賦值給mPtr(long類型)
    mPtr = nativeInit();
}

//本地方法(C++實現)
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    //創建NativeMessageQueue對象
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }
    nativeMessageQueue->incStrong(env);
    //返回地址
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
//對應的銷燬本地方法
static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->decStrong(env);
}

//主要方法聲明
boolean enqueueMessage(Message msg, long when)//入隊
Message next()//出隊
void removeMessages(Handler h, int what, Object object)//刪除元素
void removeMessages(Handler h, Runnable r, Object object)

Looper

源碼路徑:frameworks/base/core/java/android/os/Looper.java
Android源碼角度上看,消息機制設計思想更貼近下圖:

解析:從上文類關係圖可知,Looper含有mQueue、mThread,Handler含有mQueue、mLooper,MessageQueue含有mMessages。這樣,圖就簡化成Looper與Handler(其實還有Thread)之間的關係了。

Looper在普通線程中的工作過程:

//舉個一般的使用栗子
class TestThread extends Thread {
        public Handler mHandler;
        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    //通常需要重寫該方法來處理消息
                }
            };
            Looper.loop();
        }
    }

解析:
上述代碼關鍵有三個步驟:
1. Looper.prepare(); - Looper的準備工作
2. 創建處理消息的Handler
3. Looper.loop(); - Looper的開始運作

步驟一 - Looper.prepare()

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

 /** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

//prepare實質工作就是
private static void prepare(boolean quitAllowed) {
    //保證Looper在某個Thread的唯一存在
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //將Looper保存到sThreadLocal
    //(ThreadLocal下面再詳細闡述,這裏只要知道:其主要作用是爲每個Thread互不干擾保存獨有的數據即可)
    sThreadLocal.set(new Looper(quitAllowed));
}

//構造方法,構建MQ、獲取當前Thread
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

步驟二 - 創建處理消息的Handler

//Handler重要成員變量,具有多個初始化以下成員變量的構造方法
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;

//Handler構造邏輯
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());
        }
    }
    //本質通過sThreadLocal.get()得到
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //從Looper中得到,mQueue擔當Looper與Handler之間溝通的橋樑
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

步驟三 - Looper.loop()

 /**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    //獲取當前Looper(即sThreadLocal.get()獲得的Looper)
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //獲取Looper中的MQ
    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.
    //確保當前線程的id與本地進程相一致,保持追蹤實際id(翻譯自官方文檔,非本文討論重點,可忽略)
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        //開啓消息死循環
        Message msg = queue.next(); // 取出消息,可能會阻塞
        if (msg == null) {
            //沒有消息則退出循環
            return;
        }
        ...
        msg.target.dispatchMessage(msg);//調用Handler中的dispatchMessage分發消息,target本質爲Handler

        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.
        //確保id在分發消息過程中不發生錯誤變化(翻譯自官方文檔,非本文討論重點,可忽略)
        final long newIdent = Binder.clearCallingIdentity();
        ...
        msg.recycleUnchecked();//處理完畢,進行回收
   }
}

Looper總結:不斷從消息隊列中取出消息,再分發給對應的Handler,如果消息爲空,則跳出死循環,進入睡眠以讓出CPU資源。

拓展:具體事件處理過程中,程序會post新的事件進入隊列,其他進程也可能投遞新的事件到這個隊列中,APK應用程序本質上就是不停地執行“處理隊列事件”的工作,直至它退出。

ThreadLocal

ThreadLocal是線程內部的數據存儲類,各個線程相互隔離、相互獨立。
主要作用:
1. 實現Looper在線程中的存取,使得線程與Looper能夠一一對應
2. 實現負責邏輯下對象的傳遞,例如:讓監聽器作爲線程內的全局對象,通過ThreadLocal,線程內部只要通過get即可獲取到,簡單又強勢(當然也可以有其他方法解決:1、以函數參數方式傳遞;2、以監聽器作爲靜態變量供線程訪問;兩種方法都有其侷限性,綜合來看,採用ThreadLocal是最好解決方法)

源碼分析:

//爲ThreadLocal設置數據
public void set(T value) {
    //得到當前Thread
    Thread currentThread = Thread.currentThread();
    //得到當前Thead中的數據,如果爲空則先初始化
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}
//具體存儲過程,可不必深究算法
//簡單來說:ThreadLocal中有一個table數組,具體的值存儲在reference字段所標識的下一個位置
//例如:ThreadLocal的reference對象在table中索引爲index,那麼其值就存在數組的index+1的位置
void put(ThreadLocal<?> key, Object value) {
    ...
    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;
        }
    }
}
//取出ThreadLocal值,如果值爲空則返回初始值(null)
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);
    }

學習資源推薦

Handler Looper MessageQueue 詳解

Java併發包學習 - 解密ThreadLocal

Android消息處理機制(Handler、Looper、MessageQueue與Message)

Android消息機制 - 簡書

Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解 - 郭霖

Android應用程序消息處理機制分析 - 老羅

Android應用程序線程消息循環模型分析 - 老羅

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章