Android消息機制全面解析(Handler,MessageQueue,Looper,Threadlocal)

(1),Android消息機制概述

Android中的消息機制主要指 Handler的運行機制 以及 MessageQueue,Looper的工做過程 ,三者相互協做,保證着消息的接收,發送,處理,執行。html

​ 圖片來自郭神的《第一行代碼》java

先簡單的介紹一下 Android 中 消息機制你們庭的主要成員 :android

  • Handler : 是Android消息機制的上層接口,最爲你們經常使用,至關於Android消息機制的入口,咱們經過使用Handler發送消息來引發消息機制的循環。一般用於:在子線程執行完耗時任務完後,更新UI。程序員

  • MessageQueue : 存儲 消息(Message) 對象的消息隊列,實則是單鏈表結構.安全

  • Looper : 用於無限的從 MessageQueue中取出消息,至關於消息的永動機,若是有新的消息,則處理執行,若沒有,則就一直等待,堵塞。Looper** 所在的線程是 建立 Handler 時所在的線程。bash

    主線程建立Handler時,會自動建立一個Looper,可是子線程並不會自動建立Looper多線程

  • ThreadLocal : 在每一個線程互不干擾的存儲,提供數據,以此來獲取當前線程的Looperide

  • ActivityThread : Android 的主線程,也叫UI線程,主線程被建立時 自動初始化主線程的 Looper對象。oop

問題 : 你們都知道只有在UI線程才能對UI元素進行操做,在子線程更改UI就會報錯,爲何?

看完《Android藝術開發探索》 這本書的第10章以後我也才明白post

  1. Android中的UI控件不是線程安全的,若是在子線程中也能修改UI元素,那多線程的時,共同訪問同一個UI元素,就會致使這個UI元素處於咱們不可預知的狀態,這個線程讓它往左一點,那個線程讓它往右一點,UI該聽誰的,好tm亂。。 乾脆我就只聽主線程的把。

問題 : 那爲何不經過對訪問UI控件的子線程加上鎖機制呢 ?

這個很簡單了,若是爲不一樣的線程訪問同一UI元素加上鎖機制,那咱們程序員寫相關代碼的時候會變得超級麻煩。。。 改個UI還得考慮它是否是已經被別的線程佔用了,被佔用了,還得讓那個線程釋放鎖。。。線程再多一點的話,大大地加大了程序員地工做量.

並且加上鎖機制無疑會因爲線程堵塞地緣由下降訪問UI的效率,幀率下降,體驗也會不友好。

讓UI元素只能再主線程訪問就會省下不少事,建立一個Handler就好了。

下面從總體概述一下 消息機制的整個工做過程 :

  1. Handler建立時會採用當前線程的 Looper來構建內部的消息循環系統,若是Handler在子線程,則一開始是沒有Looper對象的(解決方法稍後介紹),主線程ActivityThread默認有一個Looper。

  2. Handler建立完畢,經過 post方法傳入Runnable對象,或者經過sendMessage(Message msg)發送消息。

    post()方法裏也是經過調用send()實現的

  3. send()方法被調用後,調用 MessageQueue的enqueueMessage()方法將消息發送到消息隊列中,等待被處理。

  4. Looper對象運行在Handler所在的線程,從MessageQueue消息隊列中不斷地取出消息,處理,因此業務邏輯(一般是更新UI)就運行在Looper的線程中。

接下來從局部來分析消息機制的每一個成員。

(2),ThreadLocal 工做原理

1, 什麼是ThreadLocal?

ThreadLocal是一個線程內部的數據存儲類,經過它能夠在指定的線程中得到存儲數據,得到數據,線程之間的ThreadLocal相互獨立,且沒法得到另外一個線程的TheadLocal.

  • 相對整個程序來講,每一個線程的ThreadLocal是局部變量。

  • 相對一個線程來講,線程內的ThreadLocal是線程的全局變量

ThreadLocal是一個泛型類,能夠存儲任意類型的對象。

示例:

public class ThreadLocalTest {

	public static void main(String[] args) {
		
		ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();
		mThreadLocal.set(true);
		System.out.println("#Main Thread : ThreadLocal " + mThreadLocal.get());
		
		
		new Thread( new Runnable() {
			
			@Override
			public void run() {
				mThreadLocal.set(false);
				System.out.println("#1 Thread : ThreadLocal " + mThreadLocal.get());
			}
		}).start();
		
		new Thread( new Runnable() {
			
			@Override
			public void run() {
				System.out.println("#2 Thread : ThreadLocal " + mThreadLocal.get());
			}
		}).start();

	}

}
複製代碼

咱們在主線程建立一個 泛型爲Boolean的ThreadLocal,並.set(True),而後在第一個子線程中.set(False),在第二個子線程中不作修改,直接打印。 能夠看到,在不一樣的線程中得到的值也不一樣。

輸出 :

#Main Thread : ThreadLocal true
#1 Thread : ThreadLocal false
#2 Thread : ThreadLocal null
複製代碼

2,ThreadLocal的實現原理

首先每一個線程內部都維護着一個ThreadLocalMap對象

Thread.Java

/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
複製代碼

這個ThraedLocalMap 與Map相似,一個線程內能夠有多個ThreadLocal類型變量,因此經過ThreadLocalMap <ThreadLocal<?> key, Object value>.保存着多個<ThreadLocal , 任意類型對象>鍵值對。

看一下ThreadLocal的set()方法實現 :

/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */
    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對象,map.set(this,value) 設置了我這個ThreadLocal存儲的值.

get()方法實現 :

/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */
    public T get() {
        Thread t = Thread.currentThread();//得到當前線程
        ThreadLocalMap map = getMap(t);//根據根據得到它的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//得到<k,v>鍵值對
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;//經過<k,v>得到值
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

3,ThreadLocal的使用場景

通常,當某些數據是以線程爲做用域,而且不一樣的線程具備不一樣的數據副本時,能夠考慮用ThreadLocal

場景1:

對於Handler,它想要得到當前線程的Looper,而且Looper的做用域就是當前的線程,不一樣的線程具備不一樣的Looper對象,這時可使用ThreadLocal。

場景2:

複雜邏輯下的對象的傳遞,若是想要一個對象貫穿着整個線程的執行過程,可採用Threadlocal讓此對象做爲該線程的全局對象。

(3),MessageQueue的工做原理

單鏈表的形式,存儲着Handler發送過來的消息,再來一張圖加深印象

主要包含兩個操做:

  • 經過enqueueMessage(Message msg,long when),像隊列插入一個消息,這裏爲了節省篇幅,就不上源碼,貼上源碼鏈接,MessageQueue.enqueueMessage()
  • 經過next()從無限循環隊列中取出消息,並從消息隊列中刪除。MessageQueue.next()

雖然它叫作消息隊列,但內部實際上是以單鏈表的結構存儲,有利於插入,刪除的操做。

(4),Looper的工做原理

它的主要做用就是 不停地從消息隊列中 查看是否有新的消息,若是有新的消息就會馬上處理,沒有消息就會堵塞。

持有MessageQueue的引用,而且會在構造方法中初始化

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

問題: 如何在子線程建立它的Looper對象 ?

前面說到主線程本身會建立一個Looper對象,因此咱們在主線程使用Handler的時候直接建立就能夠了。

可是在子線程使用Handler的話,就須要咱們手動建立Looper了,

示例:

new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
    }
}.start();
複製代碼

prepare()源碼以下:

/** Initialize the current thread as a looper. 77 * This gives you a chance to create handlers that then reference 78 * this looper, before actually starting the loop. Be sure to call 79 * {@link #loop()} after calling this method, and end it by calling 80 * {@link #quit()}. 81 */
82    public static void prepare() {
83        prepare(true);
84    }
85
86    private static void prepare(boolean quitAllowed) {
87        if (sThreadLocal.get() != null) {
88            throw new RuntimeException("Only one Looper may be created per thread");
89        }
90        sThreadLocal.set(new Looper(quitAllowed));
91    }
複製代碼

能夠看到最終是調用了 此 Looper 所在線程的 **ThreadLocal.set()**方法,存了一個Looper對象進去。

除了prepare(),還有一些其餘方法,咱們也須要知道

  • loop() : 啓動消息循環,,只有當Looper調用了loop()以後,整個消息循環才活了起來

  • prepareMainLooper() : 給主線程建立Looper對象

  • getMainLooper() : 得到主線程的Looper對象

  • quit() : 通知消息隊列,直接退出消息循環,不等待當前正在處理的消息執行完,quit以後,再向消息隊列中發送新的消息就會失敗( Handler的send()方法就會返回false )

    public void quit() {
         mQueue.quit(false);
     }
    複製代碼
  • quitSafety() : 經過消息隊列,再也不接收新的消息,等當前的消息隊列中的消息處理完就退出。

    public void quitSafely() {
            mQueue.quit(true);
     }    
    複製代碼

下面分析loop()的實現:

/** 119 * Run the message queue in this thread. Be sure to call 120 * {@link #quit()} to end the loop. 121 */
122    public static void loop() {
123        final Looper me = myLooper();
124        if (me == null) {
125            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
126        }
127        final MessageQueue queue = me.mQueue;
128
129        ...//省略部分代碼
133		  //從這裏開啓無限循環,直到 沒有消息
134        for (;;) {
135            Message msg = queue.next(); // might block
136            if (msg == null) {
137                // No message indicates that the message queue is quitting.
138                return;
139            }
140
141            // This must be in a local variable, in case a UI event sets the logger
142            Printer logging = me.mLogging;
143            if (logging != null) {
144                logging.println(">>>>> Dispatching to " + msg.target + " " +
145                        msg.callback + ": " + msg.what);
146            }
147
148            msg.target.dispatchMessage(msg);
149			   ...//省略部分代碼
166        }
167    }
複製代碼

在 for 循環裏 :

  1. 經過queue.next()一直讀取新的消息,若是沒有消息 則退出循環。
  2. 接下來,msg.target.dispatchMessage(msg);,target是發送此消息的 Hander對像,通知Handler調用dispatchMessage()來接收消息。

(5),Handler的工做原理

Handler的主要工做就是 發送消息,接收消息。

發送消息的方式有post(),send(),不過post()方法最後仍是調用的send()方法

  • 發送消息的過程:

    send類型的發送消息方法有不少,而且是嵌套的

    sendMessage()

    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);
    }
    複製代碼

    三種send的發送消息方式,最後都會經過enqueueMessage()來通知消息隊列 插入這條新的消息。

    Handler.enqueueMessage

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
          msg.target = this;
          if (mAsynchronous) {
               msg.setAsynchronous(true);
          }
          return queue.enqueueMessage(msg, uptimeMillis);//調用消息隊列的enqueueMessage()
    }
    複製代碼
  • 接收消息的過程

    接收消息由dispatchMessage(Message msg)爲入口

    dispatchMessage()

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

    這裏的callback是咱們調用 post(Runnable runnalbe) 時傳入的Runnable對象,若是咱們傳入了Runnable對象

    就會執行Runnable的run方法:

    private static void handleCallback(Message message) {
            message.callback.run();
    }
    複製代碼

    若是沒有經過postRunnable,就會看建立Handler時的構造方法中有沒有傳Runnable參數,傳了的話由mCallback存儲。

    這個mCallback是Handler內部的一個接口

    public interface Callback {
            public boolean handleMessage(Message msg);
    }
    複製代碼

    若是構造Handler時也沒有傳Runnable對象,最終會執行handleMessage(msg),這個 方法就是咱們建立handler時重寫的handleMessage()方法.


參考資料: Android藝術開發探索

www.cnblogs.com/luxiaoxun/p…

(完~)

相關文章
相關標籤/搜索