天天看點

Android消息機制源碼解讀

版權聲明:轉載前請留言獲得作者許可,轉載後标明作者 張拭心 與 原文連結。大家都是成年人,創作不易,感謝您的支援! https://blog.csdn.net/u011240877/article/details/72892321

  • 不要心急,一點一點的進步才是最靠譜的。

讀完本文你将了解:

    • 前言
    • Message
      • 如何擷取一個消息
      • Message.obtain()
      • 消息的回收利用
    • MessageQueue
      • MessageQueue 的屬性
      • 何時初始化
      • 消息入隊的過程
      • 消息出隊的過程
    • Looper
      • 線程相關 ThreadLocal
      • 無限循環排程
      • 如何停止
    • Handler
      • Handler 的屬性
      • 發送消息
      • 處理消息
      • 移除消息
    • 主線程消息機制
    • 總結
    • Thanks

前言

本來我以為自己很了解 Handler,在印象中 Android 消息機制無非就是:

  1. Handler 給 MessageQueue 添加消息
  2. 然後 Looper 無限循環讀取消息
  3. 再調用 Handler 處理消息

整體的流程有了,但是一直沒有結合源碼捋一捋。

直到有一天在使用 Handler 發送消息時遇到了一個問題:

This message is already in use.

這才去翻了翻源碼,今天總結一下。

Android 消息機制主要涉及 4 個類:

  • Message
  • MessageQueue
  • Handler
  • Looper

我們依次結合源碼分析一下。

Message

“消息機制”,其中要傳遞的就是 Message,官方對它的描述是:

包含任意類型的對象和描述資訊,可以被發送給 Handler。

Message

的主要屬性如下:

//用來辨別一個消息,接收消息方可以根據它知道這個消息是做什麼的
    public int what;

    //如果你的消息要傳遞的資料是整型的,可以直接使用 arg1 和 arg2,而不需要使用構造一個 Bundle
    public int arg1;
    public int arg2;

    //一個任意類型的對象,在使用 Messenger 跨程序傳遞消息時,通常使用它傳遞給接收者
    //在其他場景下我們一般使用 setData() 方法
    public Object obj;

    //負責回複消息的 Messenger,有的場景下(比如接受者、發送者模型)需要使用它
    public Messenger replyTo;

    //目前消息的标志,隻在被 Messenger 傳遞消息時使用,其他情況下都是 -1
    public int sendingUid = -;

    //辨別目前消息是否在被使用
    //當一個消息入隊時這個标志會被改變,在被重新擷取後重置
    //當一個消息已經在被使用時,二次入隊或者回收會報錯(這就是我前言中提到的錯誤原因)
    /*package*/static final int FLAG_IN_USE =  << ;

    //辨別目前 消息是否是異步的
    /*package*/static final int FLAG_ASYNCHRONOUS =  << ;

    //在 copyFrom 方法中要清除的标志
    /*package*/static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
    /*package*/int flags;

    /*package*/long when;

    //很關鍵的資料部分
    /*package*/Bundle data;

    //發送和處理消息關聯的 Handler
    /*package*/Handler target;

    //消息的回調
    /*package*/Runnable callback;

    //在有些場景下還會以連結清單的形式關聯後一個消息
    /*package*/Message next;

    //消息池
    private static final Object sPoolSync = new Object();
    private static Message sPool;    //回收消息連結清單
    private static int sPoolSize = ;

    private static final int MAX_POOL_SIZE = ;

    private static boolean gCheckRecycle = true;
Pasted from: http://book2s.com/java/src/package/android/os/message.html#a940ebe121994bfb7f9629bca79beab6           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

可以看到,Message 中比較關鍵的屬性有:

  • 辨別幹什麼的 what
  • 兩個簡易的整型資料存儲對象 arg1 arg2
  • 存儲複雜點的對象 Bundle
  • 跨程序通信綁定資料的 object
  • 與之關聯的 Handler
  • 還有一些标志位,和消息池什麼的

如何擷取一個消息

消息機制最開始肯定需要建構一個消息。

雖然 Message 的構造函數是

public

的,但是官方還是建議我們使用以下 2 種方式擷取一個 Message 對象:

  1. Message.obtain()

  2. Handler.obtainMessage()

原因是這兩個方法會從一個消息回收池裡擷取消息,而不是建立一個,這樣可以節省記憶體。

Handler.obtainMessage()

也是調用的

Message.obtain()

public final Message obtainMessage() {
        return Message.obtain(this);
    }           
  • 1
  • 2
  • 3

去看一下

Message.obtain()

源碼。

Message.obtain()

Message.obtain()

有 7 個重載方法,基本就是在擷取一個 Message 對象的同時直接指派:

我們選最複雜的一個看下源碼:

public static Message obtain(Handler h, int what, int arg1, int arg2,
            Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }
Pasted from: http://book2s.com/java/src/package/android/os/message.html#50de1a696587dcd5ae3c91eb63985af2           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

的确就是在

obtainI()

的基礎上加了一些指派。

Message.obtain()

源碼如下:

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = ; // 擷取一個複用的消息時,會重置标志位,之前它是 FLAG_IN_USE
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
Pasted from: http://book2s.com/java/src/package/android/os/message.html#62dadbbac34056e7ad4fad36d757d6ef           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到,這個方法會擷取前面提到的

private static Message sPool;

,如果

sPool

存在就從複用消息連結清單頭部取一個消息,然後重置它的标志位;如果不存在複用消息連結清單就建立一個消息。

那什麼時候往消息池中添加消息呢?

消息的回收利用

消息的回收在

Message.recyclerUnchecked()

方法:

void recycleUnchecked() {
        flags = FLAG_IN_USE;    //目前消息被回收時,會标志為 FLAG_IN_USE
        what = ;
        arg1 = ;
        arg2 = ;
        obj = null;
        replyTo = null;
        sendingUid = -;
        when = ;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

可以看到

recycleUnchecked

方法将目前 Message 标志為 FLAG_IN_USE,這樣如果這個方法在被入隊,就會報錯。此外它還清除了其他資料,然後把這個消息加入了回收消息的連結清單中。

recycleUnchecked()

什麼時候會被調用呢?

MessageQueue

Looper

中都有。

MessageQueue.removeMessages()

方法:

void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h && p.what == what
                    && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();    //這裡調用了
                p = n;
            }

            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.what == what
                            && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();    //這裡也調用了
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }
Pasted from: http://book2s.com/java/src/package/android/os/messagequeue.html#0dbba2b975383e8b440199e5d69c73de           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

Looper.loop()

方法:

public static void loop() {
        //...

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            //...

            msg.recycleUnchecked();
        }
    }
Pasted from: http://book2s.com/java/src/package/android/os/looper.html           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

至此我們可以看到,一個消息在被 Looper 處理時或者移出隊列時會被辨別為

FLAG_IN_USE

,然後會被加入回收的消息連結清單,這樣我們調用

Message.obtain()

方法時就可以從回收的消息池中擷取一個舊的消息,進而節約成本。

MessageQueue

MessageQueue

管理着一個

Message

的清單,

Handlers

為它添加消息,

Looper

從中取消息。

Android消息機制源碼解讀

MessageQueue

的屬性

// 隊列是否可以退出
    private final boolean mQuitAllowed;

    @SuppressWarnings("unused")
    private long mPtr; //底層使用的 code

    //消息連結清單的開頭
    Message mMessages;
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
    private IdleHandler[] mPendingIdleHandlers;
    private boolean mQuitting;
    //指出擷取下一個消息的方法 next() 是否阻塞
    private boolean mBlocked;

    // Barriers are indicated by messages with a null target whose arg1 field carries the token.
    //後一個屏障的 token 
    private int mNextBarrierToken;
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以看到,

MessageQueue

雖然叫“消息隊列”,持有的其實是一個消息連結清單的節點。

何時初始化

MessageQueue

一般不直接通路,都是通過

Looper.myQueue()

方法擷取一個消息隊列。

消息隊列在 Looper 的構造函數中初始化:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

MessageQueue 的構造函數中傳入一個是否允許中途退出的标志,然後調用 Native 方法初始化。

這篇文章暫不研究 Native 層源碼。

消息入隊的過程

消息入隊的方法是

enqueueMessage()

方法:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {    //這裡要求消息必須跟 Handler 關聯
        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 ==  || 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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

Message.enqueueMessage()

方法中會檢查入隊的消息是否在被使用,如果是的話會報錯:

if (msg.isInUse()) {    
        throw new IllegalStateException(msg + " This message is already in use.");
    }           
  • 1
  • 2
  • 3

現在我們清楚了,文章開頭我遇到的:

This message is already in use.

是因為我二次使用了已經在使用的消息,在入隊時 MessageQueue 檢查發現後報的錯。

是以每次調用

Handler.sendMessage()

時,都必須是

obtain()

或者 new 一個新的 Message 對象才行。

此外,如果消息隊列已經退出,還添加消息入隊就會報錯。

消息出隊的過程

消息出隊的方法是

MessageQueue.next()

方法:

Message next() {
    //如果消息的 looper 退出,就退出這個方法
    final long ptr = mPtr;
    if (ptr == ) {
        return null;
    }

    int pendingIdleHandlerCount = -; // -1 only during first iteration
    int nextPollTimeoutMillis = ;
    //也是一個循環,有合适的消息就傳回,沒有就阻塞
    for (;;) {
        if (nextPollTimeoutMillis != ) {    //如果有需要過段時間再處理的消息,先調用 Binder 的這個方法
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            //擷取下一個消息
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;    //目前連結清單的頭結點
            if (msg != null && msg.target == null) {
                //如果消息沒有 target,那它就是一個屏障,需要一直往後周遊找到第一個異步的消息
                                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 = -;
            }

            //如果收到退出的消息,并且所有等待處理的消息都處理完時,調用 Native 方法銷毀隊列
                        if (mQuitting) {
                dispose();
                return null;
            }

            //有消息等待過段時間執行時,pendingIdleHandlerCount 增加
            if (pendingIdleHandlerCount < 
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= ) {
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, )];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        for (int i = ; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

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

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

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = ;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = ;
    }
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

可以看到,

MessageQueue.next()

方法裡有一個循環,在這個循環中周遊消息連結清單,找到下一個可以處理的、target 不為空的消息并且執行時間不在未來的消息,就傳回,否則就繼續往後找。

如果有阻塞(沒有消息了或者隻有 Delay 的消息),會把 mBlocked這個變量标記為 true,在下一個 Message 進隊時會判斷這個message 的位置,如果在隊首就會調用 nativeWake() 方法喚醒線程!

其中看到個

IdleHandler

是什麼呢?

public static interface IdleHandler {

    //當消息隊列沒有消息時會回調這個方法,阻塞等待有消息進入
    //傳回 true 的話表示喚醒阻塞的線程,false 表示移除
    //如果消息隊列中有消息等待在将來執行,也會調用這個方法
    boolean queueIdle();
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

根據源碼和注釋我們可以知道

IdleHandler

是一個線程阻塞時回調的接口。

MessageQueue 中提供了監聽阻塞回調的注冊和移除接口:

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

當消息隊列阻塞時,會回調這些監聽阻塞的觀察者,告訴他們:我有空了!來找我玩啊!

queueIdle()

中做的操作,它會在 handler 空閑時被調用,可以充分利用 handler,我們可以在代碼裡這樣使用:

Handler handler = new Handler();
        try {
            Field field = Looper.class.getDeclaredField("mQueue");
            field.setAccessible(true);
            MessageQueue queue = (MessageQueue) field.get(handler.getLooper());
            queue.addIdleHandler(new MessageQueue.IdleHandler() {
                @Override
                public boolean queueIdle() {
                    //這裡做一些操作
                    return true;
                }
            });
        } catch (Exception e) {

        }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
這裡隻簡單介紹了 Java 層的 MessageQueue,關于 Native 層的可以看這篇文章:http://blog.csdn.net/innost/article/details/47317823

Looper

前面介紹了 Android 消息機制中消息和消息隊列,有了傳遞的消息和存儲的隊列,接下來我們結合源碼了解下進行排程的 Looper。

Android消息機制源碼解讀

官方文檔對 Looper 的介紹:

Looper 是用于運作一個線程中的消息的類。

Looper 的屬性很簡單:

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // 主線程中的 Looepr

final MessageQueue mQueue;    //與之管理的消息隊列
final Thread mThread;    //所在的線程

private Printer mLogging;
private long mTraceTag;           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

線程中預設沒有 Looper,我們需要調用

Looper.prepare()

方法為目前線程建立一個 Looper,然後就可以調用

loop()

方法排程消息。

樣例代碼如下:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

線程相關 ThreadLocal

前面講了在一個線程中需要調用

Looper.prepare()

方法建立一個 Looper:

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

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以看到,一個線程中隻能有一個 Looper。

建立一個或者或許目前線程的 Looper 都通過

ThreadLocal

,我們來了解下它的主要源碼。

ThreadLocal.get()

ThreadLocal.set()

源碼:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);    
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

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 getMap(Thread t) {
    return t.threadLocals;
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

ThreadLocalMap

中持有一個

Entry

的數組:

private Entry[] table;
static class Entry extends WeakReference<ThreadLocal> {
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可以看到,

ThreadLocal

先通過目前線程擷取

ThreadLocalMap

,然後在

ThreadLocalMap

中儲存

ThreadLocal

和 資料的關聯。

也就是一個類似 Map

無限循環排程

線上程中建立一個

Looper

以後,就可以調用

Looper.loop()

循環處理消息了,看下它的源碼:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {    //目前線程必須建立 Looper 才可以執行
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    //底層對 IPC 辨別的處理,不用關心 
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {    //無限循環模式
        Message msg = queue.next(); //從消息隊列中讀取消息,可能會阻塞
        if (msg == null) {    //當消息隊列中沒有消息時就會傳回,不過這隻發生在 queue 退出的時候
            return;
        }

        //...
        try {
            msg.target.dispatchMessage(msg);    //調用消息關聯的 Handler 處理消息
        } finally {
            if (traceTag != ) {
                Trace.traceEnd(traceTag);
            }
        }
        //...
        msg.recycleUnchecked();    //标記這個消息被回收
    }
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

可以看到,

Looper.loop()

也很簡單,就是調用

MessageQueue.next()

方法取消息,如果沒有消息的話會阻塞,直到有新的消息進入或者消息隊列退出。

拿到消息後調用消息關聯的 Handler 處理消息。

可以看到,Looper 并沒有執行消息,真正執行消息的還是添加消息到隊列中的那個 Handler,真應了那句:解鈴還須系鈴人啊!

如何停止

loop()

源碼中的注釋就提醒我們,開啟循環排程消息後不要忘記調用

quit()

方法結束循環。

Looper.quit() 和 quitSafely ()

源碼:

public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

兩種退出方式都調用的是

MessageQueue.quit(boolean)

方法:

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}
private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {    //`如果連結清單頭部的消息執行時間在将來(也就是一時半會兒沒有任務可執行)
            removeAllMessagesLocked();    //就直接強硬的全部回收了
        } else {        
            Message n;
            for (;;) {            //否則找到那個執行時間大于現在的消息,把它後面的消息都回收了
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}
private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {    //挨個周遊連結清單,把消息都回收了
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

可以看到 Looper 兩種結束方式的差別:

  1. quit()

    :立即把消息連結清單中的所有消息都回收了,比較強硬
    • 在停止後如果 Handler 還發送消息,會傳回 false,表示入隊失敗
    • 這個方法是不安全的,一般建議使用下面那個
  2. quitSafely()

    :比上面那個溫柔一點
    • 隻會将還未執行的消息回收掉
    • 在調用之後添加進入的消息不會被處理,Handler.sendMessage 也會傳回 false

當消息隊列被标記位退出狀态時,它的

next()

方法會傳回 null,于是

Looper.loop()

循環就結束了。

Handler

現在我們了解了消息隊列和 Looper,接着就該介紹消息機制中最關鍵的角色 – Handler。

Android消息機制源碼解讀

每一個 Handler 都和一個線程的 Looper 以及這個線程中的消息隊列關聯。

Handler 所做的就是 “線程切換”:

  • 在子線程将 Message 或者 Runnable 發送到 MessageQueue 中
  • 然後等待 Looper 排程這個消息後,再召喚 Handler 來處理消息
  • 這時消息已經在建立 Handler 的線程了

這個“線程切換” 是怎麼實作的呢?我們一步步揭曉。

Handler 的屬性

Handler 的屬性如下:

final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;           
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到 Handler 的屬性很簡單,其中 mCallback 可以作為構造函數的參數用于建立 Handler。

public Handler(Callback callback) {
    this(callback, false);
}
public interface Callback {
    public boolean handleMessage(Message msg);
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我們平時建立 Handler 都是建立一個 Handler 的子類然後重寫它的 handleMessage 方法,有了

Handler.Callback

接口我們可以用這種方式建立 Handler:

Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        //這裡處理消息
        return false;
    }
});           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最終效果和建立 Handler 子類一樣,但是需要注意,這裡建立了匿名内部類,還是會持有外部引用,導緻記憶體洩漏。

發送消息

Handler 發送的主要有兩種類型:

  1. Message
  2. Runnable

發送方法有

postXXX()

sendXXX()

兩種,postXXX 發送的是 Runnable,調用的也是 sendXXX,而 Runnable 也會被轉成 Message:

public final boolean post(Runnable r) {
        return sendMessageDelayed(getPostMessage(r), );
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

是以我們直接看 sendXXX 方法:

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);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

可以看到 Handler 發送消息最後還是調用了消息隊列的

enqueueMessage()

方法。

而我們常用的使用

Handler.sendMessageDelayed()

發送延遲消息,最後其實是在入隊時指定這個 msg.when,在

MessageQueue.next()

方法中,會對 msg.when > now 的消息設定延遲處理,具體實作是在 Native 層。

消息入隊後,Looper 如果啟動了就可以從隊列裡循環取消息,然後調用

msg.target.dispatchMessage(msg)``` 也就是

Handler.dispatchMessage()“ 方法處理消息。

處理消息

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
private static void handleCallback(Message message) {
    message.callback.run();
}
public void handleMessage(Message msg) {
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

可以看到,Handler 在處理消息時,會有三種情況:

  1. msg.callback 不為空
    • 這在使用

      Handler.postXXX(Runnable)

      發送消息的時候會發生
    • 這就直接調用 Runnable 的 run() 方法
  2. mCallback 不為空
    • 這在我們使用前面介紹的 Handler.Callback 為參數構造 Handler 時會發生
    • 那就調用構造函數裡傳入的

      handleMessage()

      方法
    • 如果傳回 true,那就不往下走了
  3. 最後就調用

    Handler.handleMessage()

    方法
    • 這是一個空實作,需要我們在 Handler 子類裡重寫
    至此 Handler 發送和處理消息的源碼我們就了解了。

移除消息

最後再看一下如果移除消息。

由于發送時可以發送 Callback 和 Message,是以取消也有兩種:

  1. removeCallbacks()
  2. removeMessages()

看一下源碼發現調用的其實就是消息隊列的出隊方法:

public final void removeCallbacks(Runnable r){
    mQueue.removeMessages(this, r, null);
}
public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

主線程消息機制

我們知道,在主線程中建立 Handler 時不用 papare Looper,這是因為在主線程中系統預設建立了 Looper,它在不停地排程分發消息,是以四大元件的排程、我們的輸入事件、繪制請求才能得到處理。

ActivityThread 就是我們說的主線程,而它的

main()

方法就是當主線程的入口:

public static void main(String[] args) {
    /...
    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

可以看到在主線程的

main()

方法中初始化了一個 Looper,然後調用了

Looper.loop()

方法開始排程消息。

發送和處理主線程中的消息的 Handler 叫做 H:

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = ;
    public static final int PAUSE_ACTIVITY          = ;
    public static final int PAUSE_ACTIVITY_FINISHING= ;
    public static final int STOP_ACTIVITY_SHOW      = ;
    public static final int STOP_ACTIVITY_HIDE      = ;
    public static final int SHOW_WINDOW             = ;
    public static final int HIDE_WINDOW             = ;
    public static final int RESUME_ACTIVITY         = ;
    public static final int SEND_RESULT             = ;
    public static final int DESTROY_ACTIVITY        = ;
    public static final int BIND_APPLICATION        = ;
    public static final int EXIT_APPLICATION        = ;
    public static final int NEW_INTENT              = ;
    public static final int RECEIVER                = ;
    public static final int CREATE_SERVICE          = ;
    public static final int SERVICE_ARGS            = ;
    public static final int STOP_SERVICE            = ;
    //...
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            case RELAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                handleRelaunchActivity(r);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            //...
        }
        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
    }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

H 的代碼很多,我們隻節選一部分。從上述代碼可以看到,H 内部定義了消息類型,然後根據消息的類型進行不同的處理。

主線程的消息機制如下:

  • ActivityThread 通過 ApplicationThread 與 AMS 進行程序通信
  • AMS 以程序通信的方式完成 ActviityThread 的請求後回調 ApplicationThread 的 Binder
  • 然後 ApplicationThread 向 H 發送消息,H 将消息切換到主線程,然後進行處理

總結

Android消息機制源碼解讀

這篇文章結合源碼完整的看了一遍 Message MessageQueue Handler Looper,現在看着上面的圖,可以自信地說我“熟悉 Android 消息機制”了哈哈。

結合 Android 性能優化:多線程 了解會更深一些!

Thanks

《Android 開發藝術探索》

http://book2s.com/java/src/package/android/os/handler.html#d0bdd75e69340e6c9e876ee32501beae

http://book2s.com/java/src/package/android/os/looper.html

http://book2s.com/java/src/package/android/os/message.html