深刻探索-Handler消息機制

概述:

經過Handler能夠輕鬆的將一個任務切換到Handler所在的線程中執行.
應用:在Android開發中,有時候須要在子線程中進行耗時的IO操做(讀取文件或訪問網絡),當 耗時操做完成之後可能須要再UI上作一些改變,因爲Android開發規範的限制,咱們並不能在 子線程中訪問UI控件,不然就會出發程序異常,此時經過Handler就能夠將更新UI的操做切換到主線程中執行.java

ThreadLocal

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).數組

官方的介紹是:該類提供線程局部變量.這些變量不一樣於它們的正常變量,即每個線程訪問自身的局部變量時,都有它本身的,獨立初始化的副本.該變量一般是與線程關聯的私有靜態字段,列如用於ID或事物ID.
經過ThreadLocal,每一個線程均可以獲取本身線程內部的私有變量.網絡

原理分析

ThreadLocal<String> mThreadLocal = new ThreadLocal<>(); //建立了一個ThreadLocal的對象
    mThreadLocal.set("這是一個測試文案"); //設置值
    mThreadLocal.get(); //獲取值
複製代碼

當咱們建立了ThreadLocal對象後就能夠調用set()/get()方法來進行數據的賦值與訪問了.async

set(T value)

public void set(T value) {
        Thread t = Thread.currentThread();  //獲取當前線程
        ThreadLocalMap map = getMap(t); //拿到線程的ThreadLocalMap
        if (map != null)    //對ThreadLocalMap作驗證,存在就直接賦值,不然建立並賦值
            map.set(this, value);   //賦值 this(key 當前ThreadLocal對象) value(所賦的值)
        else
            createMap(t, value);    //建立ThreadLocalMap並賦值
    }
複製代碼
//獲取ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    //建立ThreadLocalMap並賦值
        void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
複製代碼

get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); //拿到線程中的ThreadLocalMap
        if (map != null) {  //若是ThreadLocalMap爲null,建立新的ThreadLocalMap
            ThreadLocalMap.Entry e = map.getEntry(this);    //根據key值拿到Entry對象
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;  //獲取存儲的數據
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

咱們能夠看到,在set()/get()方法中,咱們首先拿到當前線程,並經過當前線程去獲取到對應的ThreadLocalMap,而後經過再調用的ThreadLocalMap的set()/get()方法.因此其實本質上來說,ThreadLocal是經過ThreadLocalMap來進行的數據的操做.ide

ThreadLocalMap

ThreadLocalMap是ThreadLocal中的一個靜態內部類,是一個定製散列映射,只用於維護線程私有值.oop

static class ThreadLocalMap {
    
        //存儲的數據爲Entry且key爲弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //初始容量
        private static final int INITIAL_CAPACITY = 16;
        //用於存儲數據
        private Entry[] table;
        //用於數組擴容 默認爲數組長度的2/3
        private int threshold;
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
        
        //第一次放入數據,初始化數組長度,定義擴容因子大小.
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];    //初始化數組長度爲16
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//根據哈希值計算位置
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY); //設置擴容因子爲當前數組的2/3
        }
}
複製代碼

set()

private void set(ThreadLocal<?> key, Object value) {
            //根據哈希值計算位置
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            
            for (Entry e = tab[i]; //獲取當前位置的數據
                 e != null; 
                 e = tab[i = nextIndex(i, len)]) {  //獲取下一個位置的數據
                ThreadLocal<?> k = e.get(); //獲取數據的key
            //若是key值相同,則直接覆蓋數據
                if (k == key) {
                    e.value = value;
                    return;
                }
            //若是key值爲null,則清空全部key爲null的數據
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //若是上述條件都不知足,則直接添加數據
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
複製代碼

get()

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);   //根據key值拿到位置
            Entry e = table[i]; //根據位置拿到數據
            if (e != null && e.get() == key)    //判斷是否有數據,成功則直接返回
                return e;
            else    //
                return getEntryAfterMiss(key, i, e);
        }
        
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)   //key值相同,直接返回
                    return e;
                if (k == null)  //key爲null,清楚當前位置下全部key爲null的數據
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;    //沒有數據返回null
        }
複製代碼

綜上所述,不管調用set()仍是get()方法,都會清楚key值爲null的數據.post

番外

1.賦值或取值時需在線程執行期間,不然會失敗.
/** * Returns a reference to the currently executing thread object. * * @return the currently executing thread. */
    @FastNative
    public static native Thread currentThread();
複製代碼

由於Thread t = Thread.currentThread();時可能會獲取不到當前線程,也就沒法拿到對應的ThreadLocalMap.測試

2.爲何使用弱引用

若是key使用了強引用,當引用ThreadLocal的對象被回收了,但ThreadLocalMap中還持有ThreadLocal的強引用,若是沒有手動清除,則會致使內存泄漏.ui

3.爲何清除key爲null的Entry

由於使用的弱引用,GC的時候ThreadLocal則會被回收,那麼此時ThreadLocalMap中就會出現key爲null的Entry,也就沒有辦法訪問這些Entry的value了.
若是當前線程遲遲不結束,這些key爲null的value就會一存在一條強引用鏈:Thread Ref(當前線程引用) -> Thread -> ThreadLocalMap -> Entry -> value,那麼就會致使這些Entry永遠沒法回收,形成內存泄漏.this

4.避免使用static的ThreadLocal

使用static修飾的ThreadLocal可能致使內存泄漏(Java虛擬機在加載類的過程當中爲靜態變量分配內存,static變量的生命週期取決於類的生命週期,也就是說類被卸載時,靜態變量纔會被銷燬並釋放內存空間,而類的聲明週期與下面的三個條件相關).

  1. 該類全部的實例都已經被回收.
  2. 加載該類的ClassLoader已經被回收.
  3. 該類對應的java.lang.class對象沒有任何地方被引用,沒有任何地方經過反射訪問該類的方法.

執行流程:

  1. 構造Handler對象.
  2. 構造Message消息對象.
  3. 使用Handler對象發送Message消息對象.
  4. Message對象入隊到MessageQueue消息隊列.
  5. Looper對象到MessageQueue消息隊列中輪詢消息,有新消息就拿出給到Handler處理.

1.構造Handler對象

Handler mHandler = new Handler(); 
複製代碼
//無參構造方法調用有參構造
    public Handler() {
        this(null, false);
    }
    //有參構造
    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());
            }
        }
        
        mLooper = Looper.myLooper();    //經過ThreadLocal獲取Looper對象
        if (mLooper == null) {  //若是爲空,則拋出異常
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
複製代碼

若是當前線程中不存在Looper實例則會觸發異常,此時須要執行Looper.prepare()方法來初始化Looper實例.

public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {   //若是已經有Looper實例,則會拋出異常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));  //建立Looper對象實例
    }
複製代碼

執行Looper.prepare()方法時會檢測當前線程中是否已經存在Looper對象,已經存在則會觸發異常,每一個線程中只容許有一個Looper對象實例存在,若是不存在就建立一個.

private Looper(boolean quitAllowed) { //Looper的構造方法
        mQueue = new MessageQueue(quitAllowed); //建立MessageQueue對象實例
        mThread = Thread.currentThread();   //將mThread設置爲當前線程
    }
複製代碼
Looper的主要做用:
  • 與當前線程綁定,保證一個線程只會有一個Looper實例,同時一個Looper實例也只有一個MessageQueue

在不一樣線程中建立Handler

主線程
ActivityThread.java public static void main(String[] args) {
        ...
        Looper.prepareMainLooper(); //初始化Looper對象
        ...
    }
複製代碼
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
複製代碼

在ActivityThread的main()方法中執行了Looper.prepareMainLooper()方法,此方法中又調用了Looper.prepare()方法完成了Looper對象的初始化.

子線程

在子線程中建立Handler時,由於沒有Looper對象,因此須要咱們主動調用Looper.prepare()方法進行初始化.

2.構造Message消息對象

Message msg = new Message(); //構造Message對象
    //爲msg添加須要攜帶的數據
    msg.setData(Bundle data);
    msg.obj = xxx;
    msg.what = xxx ;
複製代碼

3.使用Handler對象發送Message消息

主要發送方法:

  • sendMessage(Message msg)
  • post(Runnable r) 除了sendMessageAtFromOfQueue()方法以外,其餘的方法最後都會調用到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);
    }
複製代碼

該方法接收兩個參數,一個是咱們發送的Message對象,另外一個表示發送的時間(值等於自系統開機到當前時間的毫秒數再加上延遲時間).

4.Message對象入隊到MessageQueue消息隊列

Handler.java private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
複製代碼
MessageQueue.java boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) { 
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            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;
            }

            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製代碼

入隊,使用mMessage對象保存當前待處理的消息,入隊時按照時間將全部的消息排序,使用msg.next來指定下一個消息對象,而後將這條消息的next指定爲剛纔的mMessage對象,此過程爲添加消息到消息隊列的頭部.

5.Looper對象到MessageQueue消息隊列中輪詢消息,有新消息就拿出給到Handler處理

public static void loop() {
        final Looper me = myLooper();   //拿到當前的looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;   //拿到當前的消息隊列

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {  //輪詢遍歷
            Message msg = queue.next(); //判斷當前MessageQueue中是否存在mMessage(待處理消息),若是存在就出隊,而後下一條消息做爲mMessage,不然就處於阻塞的狀態,一直等到有新的消息入隊.
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                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 {
                msg.target.dispatchMessage(msg); //拿到Handler對象,調用處理消息分發的方法
                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);
            }

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

MessageQueue.next方法判斷當前MessageQueue中是否存在mMessage(待處理消息),若是存在就出隊,而後下一條消息做爲mMessage,不然就處於阻塞的狀態,一直等到有新的消息入隊.

public void dispatchMessage(Message msg) {
        //根據設置的回調判斷不一樣的處理方法
        if (msg.callback != null) { //若是msg設置了Runnable則調用該方法
            handleCallback(msg);    
        } else {    //處理數據的回調
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

最後在咱們重寫的handleMessage()方法中處理咱們所要作的業務操做.

番外:

Adroid爲何主線程不會由於Looper.loop()裏的死循環卡死?

相關文章
相關標籤/搜索