Handler你真的懂了嗎

一、簡介

你在網上看了不少handler的原理,是否是有迷茫的地方,爲何是那個樣子的?java

老是聽你們侃侃而談,談的內容仍是千篇一概,流程原理說的也是那麼個回事,可是再往深問一點,又啥啥不會。。。android

若是你以爲有必要深深的理解它,記住它,請給我一塊兒看看它是如何實現的緩存

handler是android提供的一個能夠在線程間傳遞消息的機制,主要涉及類有:Looper,MessageQueue,Message; 咱們先從消息對象來看源碼吧bash

二、消息Message

不只是一個數據類,還實現了Parcelable接口,並且也包含了一個單鏈表的數據結構,還經過對象池,實現了複用數據結構

2.1 數據類

public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
public int sendingUid = UID_NONE;
public int workSourceUid = UID_NONE;
int flags;
public long when;
Bundle data;
Handler target;
Runnable callback;
Message next;
複製代碼

主要包含一下數據內容異步

  • 消息內容:
  1. 消息標誌what
  2. 整數型數據arg1,arg2
  3. 其它類型數據obj
  4. 進程間使用攜帶相關數據,data、sendingUid、workSourceUid,Messenger
  5. 消息發送對象target
  6. 消息執行時間戳when
  • 實現單鏈表結構下個節點 next
  • 消息到期時執行任務 callback
  • flags標誌:0有效狀態,低位1使用過可重複利用狀態,次低位異步狀態

對於跨進程使用,則主要涉及輕量級跨進程通訊Messenger機制,有興趣的能夠自行了解,這裏暫時不介紹了ide

2.2 池技術使用

池是啥?簡單理解:池是使用過對象進行緩存,須要使用對象時,從緩存拿實例,進行變量從新設置便可;好處就是減小對象的建立,減小內存劇烈波動oop

這裏是池技術相關變量大數據

Message next;
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;
    private static boolean gCheckRecycle = true;
複製代碼

gCheckRecycle變量這裏不可直接操做能夠經過反射來設置;其值改變方法被hide了並且這個方法只有在L版本一下,纔會改變;另外池的複用主要是這個變量,爲true表示已經使用過的對象ui

/** @hide */
    public static void updateCheckRecycle(int targetSdkVersion) {
        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            gCheckRecycle = false;
        }
    }
複製代碼

池數據使用了單鏈表結構,先入後出的操做邏輯嗎,sPool鏈表頭;池中最大數據爲50個,每次回收對象時,則重置對象的全部相關數據

public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    void recycleUnchecked() {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
複製代碼

從鏈表的表頭拿出數據

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; 
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
複製代碼

三、消息隊列MessageQueue

主要使用了隊列進行消息的排隊,也是單鏈表結構,按照時間遞增排序;仍是用了原生方法進行等待,喚醒操做

3.1 隊列邏輯

是一個符合消費者模式的隊列 相關變量

Message mMessages; // 消息隊列對頭
private boolean mQuitting; // 是否結束
private boolean mBlocked; // 是否被阻塞
複製代碼

入隊操做

入隊前,先檢查狀態

  • 消息處理對象handler是否不爲空
  • 消息是不是有有效狀態
  • 是否已經退出,退出,則直接回收message對象
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;
                prev.next = msg;
            }

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

入隊過程

  1. 對消息處理,標記已經使用,並對觸發時間賦值
  2. 加入隊列頭部
  • 隊列爲空
  • 新加入的消息時間戳爲0
  • 新加入的時間戳小於隊列頭部消息的時間戳
  1. 若是2不步驟中條件不知足,則從隊列中找到最後一個時間戳小於when的節點,並插入其後面
  2. 若是隊列爲空,加入成功後,則進行喚醒

出隊操做

出隊操做是一個阻塞操做

Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
                if (mQuitting) {
                    dispose();
                    return null;
                }
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; 

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            pendingIdleHandlerCount = 0;

            nextPollTimeoutMillis = 0;
        }
    }
複製代碼

大體流程以下

  1. 判斷原生地址,有效,則進行原生阻塞方法調用
  2. 若是是同步,則查找第一個有效的消息對象;這裏有效的消息對象知足下面兩點
  • 消息自己不爲空
  • 消息的處理handler不爲空
  1. 若是消息不爲空且當前時間戳大於消息時間戳,則返回當前消息
  2. 若是不知足3,首先若是已經退出,則銷燬原生對象,返回空消息
  • 若是消息對象爲空,則原生等待,直到等待被喚醒
  • 若是消息不爲空,喚醒時長爲int最大正整數和消息時間戳減去當前時間戳的最小值

3.2 原生方法

原生對象映射java對象中,實際上是把地址java成員變量中,java方法經過把指針來操做原生對象

這裏mPtr爲原生對象地址

nativeWake方法

nativePollOnce方法

四、發起接收者Handler

消息的發送,是有Handler發起的,消息的接收也是由handler來處理;其過程經過代理來實現的

主要相關變量

final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
複製代碼

4.1 內部代理

  • 消息發送經過mQueue代理,實現消息查找、發送、刪除操做;其時間戳計算根據操做不一樣而不一樣
  1. 延時發送,時間戳爲:當前時間戳+延時時間
  2. 馬上發送,時間戳爲:當前時間戳
  3. 某個時間發送:爲自定義時間
  • 靜態生成Message對象,由Message代理

4.2 重要方法方法

dispatchMessage方法在消息被處理時在Looper類中調用

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

其處理方法的優先級: 消息對象的回調(callback) > 回調(mCallback成員變量) > handleMessage方法(空方法)

Handler靜態建立方法

public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        if (callback == null) throw new NullPointerException("callback must not be null");
        return new Handler(looper, callback, true);
    }

    public static Handler getMain() {
        if (MAIN_THREAD_HANDLER == null) {
            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
        }
        return MAIN_THREAD_HANDLER;
    }

    public static Handler mainIfNull(@Nullable Handler handler) {
        return handler == null ? getMain() : handler;
    }
複製代碼

給定looper的handler,主線程Handler;返回給定handler的非空版本

五、消息傳送帶 Looper

主要是取出隊列中消息,並分發給handler處理消息

5.1 主要變量

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;
    private static Observer sObserver;
    final MessageQueue mQueue;
    final Thread mThread;
複製代碼

sThreadLocal靜態實例,保存了線程的looper對象

sMainLooper:主線程looper對象

sObserver:監聽消息執行狀態:開始,結束,異常

mQueue:消息排隊隊列

mThread:looper對象相關的線程對象

5.2 構造器

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製代碼

構造器是私有方法,提供了靜態方法,來獲取主線成looper對象,當前線程looper對象

5.3 啓動消息分發處理

啓動要調用靜態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;
            }

            ......(省略一些代碼) 
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }

            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            ......(省略一些代碼) 

            msg.recycleUnchecked();
        }
    }
複製代碼

流程大體以下:

  • 首先取出當前線程的looper,若是爲空則拋出異常;因此須要須要設置當前線程looper對象到sThreadLocal中,這就須要prepare系列方法
  • 開始handler處理前監聽
  • 調用handler的dispatchMessage方法,若是出現異常,則調用監聽器異常處理,無異常調用監聽器正常結束狀態處理
  • 消息對象進行池回收

六、原理總結

這裏主要分析了線程間通訊的機制;進程間通訊機制依賴Messenger通訊機制;下面對線程間通訊進行一些總結

  • handler對象是消息發送者,和消息的處理者;
  • 消息可以運行,須要先調用Looper的prepare方法,再調用loop方法,啓動循環
  • Message攜帶消息信息,並且使用了池技術;對象構造時,應從池中獲取
  • 消息的處理方法並非固定,具備優先級關係:Message對象中callback > handler對象中mCallback > handler中handleMessage方法
  • 消息隊列退出後,looper循環也會跟着一塊兒退出
  • 消息隊列採用消費者模式,並根據時間戳進行等待;其等待喚醒採起native方法(暫時在androidRef和本身下載的framework源碼中未跟蹤到實現,暫時未能進行分析給與歉意)
  • 隊列阻塞爲什麼用原生方法,而不直接用線程掛起喚醒呢?很簡單,主線程是UI線程,若是是主線程的消息,還要不要刷新界面了,這裏的阻塞機制,相似與協程的實現;主線程的消息機制能夠直接用,由於在ActivityThread類中已經調用Looper的預加載,啓動方法了

技術變化都很快,但基礎技術、理論知識永遠都是那些;做者但願在餘後的生活中,對經常使用技術點進行基礎知識分享;若是你以爲文章寫的不錯,請給與關注和點贊;若是文章存在錯誤,也請多多指教!

相關文章
相關標籤/搜索