中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么掌握Handler消息機制

發布時間:2021-10-21 16:55:20 來源:億速云 閱讀:166 作者:iii 欄目:編程語言

本篇內容主要講解“怎么掌握Handler消息機制”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么掌握Handler消息機制”吧!

一、題目層次

  1. Handler 的基本原理

  2. 子線程中怎么使用 Handler

  3. MessageQueue 獲取消息是怎么等待

  4. 為什么不用 wait 而用 epoll 呢?

  5. 線程和 Handler Looper MessageQueue 的關系

  6. 多個線程給 MessageQueue 發消息,如何保證線程安全

  7. Handler 消息延遲是怎么處理的

  8. View.post 和 Handler.post 的區別

  9. Handler 導致的內存泄漏

  10. 非 UI 線程真的不能操作 View 嗎

二、題目詳解

代碼分析基于 Android SDK 28

大家可以先看上面的問題思考一下,如果都清楚的話,下面的文章也沒必要看了~

1. Handler 的基本原理

關于 Handler 的原理,相比不用多說了,大家都應該知道,一張圖就可以說明(圖片來自網絡)。

怎么掌握Handler消息機制

2. 子線程中怎么使用 Handler

除了上面 Handler 的基本原理,子線程中如何使用 Handler 也是一個常見的問題。 子線程中使用 Handler 需要先執行兩個操作:Looper.prepare 和 Looper.loop。 為什么需要這樣做呢?Looper.prepare 和 Looper.loop 都做了什么事情呢? 我們知道如果在子線程中直接創建一個 Handler 的話,會報如下的錯誤:

"Can't create handler inside thread xxx that has not called Looper.prepare()

我們可以看一下 Handler 的構造函數,里面會對 Looper 進行判斷,如果通過 ThreadLocal 獲取的 Looper 為空,則報上面的錯誤。

    public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
    }

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

那么 Looper.prepare 里做了什么事情呢?

    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));
    }

可以看到,Looper.prepare 就是創建了 Looper 并設置給 ThreadLocal,這里的一個細節是每個 Thread 只能有一個 Looper,否則也會拋出異常。 而 Looper.loop 就是開始讀取 MessageQueue 中的消息,進行執行了。

這里一般會引申一個問題,就是主線程中為什么不用手動調用這兩個方法呢?相信大家也都明白,就是 ActivityThread.main 中已經進行了調用。 通過這個問題,又可以引申到 ActivityThread 相關的知識,這里就不細說了。

3. MessageQueue 如何等待消息

上面說到 Looper.loop 其實就是開始讀取 MessageQueue 中的消息了,那 MessageQueue 中沒有消息的時候,Looper 在做什么呢?我們知道是在等待消息,那是怎么等待的呢?

通過 Looper.loop 方法,我們知道是 MessageQueue.next() 來獲取消息的,如果沒有消息,那就會阻塞在這里,MessageQueue.next 是怎么等待的呢?

    public static void loop() {
        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;
            }
        }
    }
    Message next() {
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // ...
        }
    }

在 MessageQueue.next 里調用了 native 方法 nativePollOnce。

// android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    // ...
    mLooper->pollOnce(timeoutMillis);
    // ...
}

// Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    // ...
    result = pollInner(timeoutMillis);
    // ...
}

int Looper::pollInner(int timeoutMillis) {
    // ...
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}

從上面代碼中我們可以看到,在 native 側,最終是使用了 epoll_wait 來進行等待的。 這里的 epoll_wait 是 Linux 中 epoll 機制中的一環,關于 epoll 機制這里就不進行過多介紹了,大家有興趣可以參考 https://segmentfault.com/a/1190000003063859

那其實說到這里,又有一個問題,為什么不用 java 中的 wait / notify 而是要用 native 的 epoll 機制呢?

4. 為什么不用 wait 而用 epoll 呢?

說起來 java 中的 wait / notify 也能實現阻塞等待消息的功能,在 Android 2.2 及以前,也確實是這樣做的。 可以參考這個 commit https://www.androidos.net.cn/android/2.1_r2.1p2/xref/frameworks/base/core/java/android/os/MessageQueue.java 那為什么后面要改成使用 epoll 呢?通過看 commit 記錄,是需要處理 native 側的事件,所以只使用 java 的 wait / notify 就不夠用了。 具體的改動就是這個 commit https://android.googlesource.com/platform/frameworks/base/+/fa9e7c05c7be6891a6cf85a11dc635a6e6853078%5E%21/#F0

Sketch of Native input for MessageQueue / Looper / ViewRoot

MessageQueue now uses a socket for internal signalling, and is prepared
to also handle any number of event input pipes, once the plumbing is
set up with ViewRoot / Looper to tell it about them as appropriate.

Change-Id: If9eda174a6c26887dc51b12b14b390e724e73ab3

不過這里最開始使用的還是 select,后面才改成 epoll。 具體可見這個 commit https://android.googlesource.com/platform/frameworks/base/+/46b9ac0ae2162309774a7478cd9d4e578747bfc2%5E%21/#F16

至于 select 和 epoll 的區別,這里也不細說了,大家可以在上面的參考文章中一起看看。

5. 線程和 Handler Looper MessageQueue 的關系

這里的關系是一個線程對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。

6. 多個線程給 MessageQueue 發消息,如何保證線程安全

既然一個線程對應一個 MessageQueue,那多個線程給 MessageQueue 發消息時是如何保證線程安全的呢? 說來簡單,就是加了個鎖而已。

// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        // ...
    }
}
7. Handler 消息延遲是怎么處理的

Handler 引申的另一個問題就是延遲消息在 Handler 中是怎么處理的?定時器還是其他方法? 這里我們先從事件發起開始看起:

// Handler.java
public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    // 傳入的 time 是 uptimeMillis + delayMillis
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // ...
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 調用 MessageQueue.enqueueMessage
    return queue.enqueueMessage(msg, uptimeMillis);
}

從上面的代碼邏輯來看,Handler post 消息以后,一直調用到 MessageQueue.enqueueMessage 里,其中最重要的一步操作就是傳入的時間是 uptimeMillis + delayMillis。

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        // ...
        msg.when = when;
        Message p = mMessages; // 下一條消息
        // 根據 when 進行順序排序,將消息插入到其中
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 找到 合適的節點
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
            }
            // 插入操作
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // 喚醒隊列進行取消息
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

通過上面代碼我們看到,post 一個延遲消息時,在 MessageQueue 中會根據 when 的時長進行一個順序排序。 接著我們再看看怎么使用 when 的。

Message next() {
    // ...
    for (;;) {
        // 通過 epoll_wait 等待消息,等待 nextPollTimeoutMillis 時長
        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) { // 說明需要延遲執行,通過; nativePollOnce 的 timeout 來進行延遲
                    // 獲取需要等待執行的時間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else { // 立即執行的消息,直接返回
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                        // 當前沒有消息要執行,則執行 IdleHandler 中的內容
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 如果沒有 IdleHandler 需要執行,則去等待 消息的執行
                mBlocked = true;
                continue;
            }

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

        // 執行 idle handlers 內容
        for (int i = 0; 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 = 0;

        // 如果執行了 idle handlers 的內容,現在消息可能已經到了執行時間,所以這個時候就不等待了,再去檢查一下消息是否可以執行, nextPollTimeoutMillis 需要置為 0
        nextPollTimeoutMillis = 0;
    }
}

通過上面的代碼分析,我們知道了執行 Handler.postDelayd 時候,會執行下面幾個步驟:

  1. 將我們傳入的延遲時間轉化成距離開機時間的毫秒數

  2. MessageQueue 中根據上一步轉化的時間進行順序排序

  3. 在 MessageQueue.next 獲取消息時,對比當前時間(now)和第一步轉化的時間(when),如果 now < when,則通過 epoll_wait 的 timeout 進行等待

  4. 如果該消息需要等待,會進行 idel handlers 的執行,執行完以后會再去檢查此消息是否可以執行

8. View.post 和 Handler.post 的區別

我們最常用的 Handler 功能就是 Handler.post,除此之外,還有 View.post 也經常會用到,那么這兩個有什么區別呢? 我們先看下 View.post 的代碼。

// View.java
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

通過代碼來看,如果 AttachInfo 不為空,則通過 handler 去執行,如果 handler 為空,則通過 RunQueue 去執行。 那我們先看看這里的 AttachInfo 是什么。 這個就需要追溯到 ViewRootImpl 的流程里了,我們先看下面這段代碼。

// ViewRootImpl.java
final ViewRootHandler mHandler = new ViewRootHandler();

public ViewRootImpl(Context context, Display display) {
    // ...
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);
}

private void performTraversals() {
    final View host = mView;
    // ...
    if (mFirst) {
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        mFirst = false;
    }
    // ...
}

代碼寫了一些關鍵部分,在 ViewRootImpl 構造函數里,創建了 mAttachInfo,然后在 performTraversals 里,如果 mFirst 為 true,則調用 host.dispatchAttachedToWindow,這里的 host 就是 DecorView,如果有讀者朋友對這里不太清楚,可以看看前面【面試官帶你學安卓-從View的繪制流程】說起這篇文章復習一下。

這里還有一個知識點就是 mAttachInfo 中的 mHandler 其實是 ViewRootImpl 內部的 ViewRootHandler。

然后就調用到了 DecorView.dispatchAttachedToWindow,其實就是 ViewGroup 的 dispatchAttachedToWindow,一般 ViewGroup 中相關的方法,都是去依次調用 child 的對應方法,這個也不例外,依次調用子 View 的 dispatchAttachedToWindow,把 AttachInfo 傳進去,在 子 View 中給 mAttachInfo 賦值。

// ViewGroup
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
    super.dispatchAttachedToWindow(info, visibility);
    mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        child.dispatchAttachedToWindow(info,
                combineVisibility(visibility, child.getVisibility()));
    }
    final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    for (int i = 0; i < transientCount; ++i) {
        View view = mTransientViews.get(i);
        view.dispatchAttachedToWindow(info,
                combineVisibility(visibility, view.getVisibility()));
    }
}

// View
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    // ...
}

看到這里,大家可能忘記我們開始剛剛要做什么了。

我們是在看 View.post 的流程,再回顧一下 View.post 的代碼:

// View.java
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    getRunQueue().post(action);
    return true;
}

現在我們知道 attachInfo 是什么了,是 ViewRootImpl 首次觸發 performTraversals 傳進來的,也就是觸發 performTraversals 之后,View.post 都是通過 ViewRootImpl 內部的 Handler 進行處理的。

如果在 performTraversals 之前或者 mAttachInfo 置為空以后進行執行,則通過 RunQueue 進行處理。

那我們再看看 getRunQueue().post(action); 做了些什么事情。

這里的 RunQueue 其實是 HandlerActionQueue。

HandlerActionQueue 的代碼看一下。

public class HandlerActionQueue {
    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }
}

通過上面的代碼我們可以看到,執行 getRunQueue().post(action); 其實是將代碼添加到 mActions 進行保存,然后在 executeActions 的時候進行執行。

executeActions 執行的時機只有一個,就是在 dispatchAttachedToWindow(AttachInfo info, int visibility) 里面調用的。

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
}

看到這里我們就知道了,View.post 和 Handler.post 的區別就是:

  1. 如果在 performTraversals 前調用 View.post,則會將消息進行保存,之后在 dispatchAttachedToWindow 的時候通過 ViewRootImpl 中的 Handler 進行調用。

  2. 如果在 performTraversals 以后調用 View.post,則直接通過 ViewRootImpl 中的 Handler 進行調用。

這里我們又可以回答一個問題了,就是為什么 View.post 里可以拿到 View 的寬高信息呢? 因為 View.post 的 Runnable 執行的時候,已經執行過 performTraversals 了,也就是 View 的 measure layout draw 方法都執行過了,自然可以獲取到 View 的寬高信息了。

9. Handler 導致的內存泄漏

這個問題就是老生常談了,可以由此再引申出內存泄漏的知識點,比如:如何排查內存泄漏,如何避免內存泄漏等等。

10. 非 UI 線程真的不能操作 View 嗎

我們使用 Handler 最多的一個場景就是在非主線程通過 Handler 去操作 主線程的 View。 那么非 UI 線程真的不能操作 View 嗎? 我們在執行 UI 操作的時候,都會調用到 ViewRootImpl 里,以 requestLayout 為例,在 requestLayout 里會通過 checkThread 進行線程的檢查。

// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
    mThread = Thread.currentThread();
}

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

我們看這里的檢查,其實并不是檢查主線程,是檢查 mThread != Thread.currentThread,而 mThread 指的是 ViewRootImpl 創建的線程。 所以非 UI 線程確實不能操作 View,但是檢查的是創建的線程是否是當前線程,因為 ViewRootImpl 創建是在主線程創建的,所以在非主線程操作 UI 過不了這里的檢查。

三、總結

一個小小的 Handler,其實可以引申出很多問題,這里這是列舉了一些大家可能忽略的問題,更多的問題就等待大家去探索了~ 這里來總結一下:

1. Handler 的基本原理

一張圖解釋(圖片來自網絡) 怎么掌握Handler消息機制

2. 子線程中怎么使用 Handler
  1. Looper.prepare 創建 Looper 并添加到 ThreadLocal 中

  2. Looper.loop 啟動 Looper 的循環

3. MessageQueue 獲取消息是怎么等待

通過 epoll 機制進行等待和喚醒。

4. 為什么不用 wait 而用 epoll 呢?

在 Android 2.2 及之前,使用 Java wait / notify 進行等待,在 2.3 以后,使用 epoll 機制,為了可以同時處理 native 側的消息。

5. 線程和 Handler Looper MessageQueue 的關系

一個線程對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。

6. 多個線程給 MessageQueue 發消息,如何保證線程安全

通過對 MessageQueue 加鎖來保證線程安全。

7. Handler 消息延遲是怎么處理的
  1. 將傳入的延遲時間轉化成距離開機時間的毫秒數

  2. MessageQueue 中根據上一步轉化的時間進行順序排序

  3. 在 MessageQueue.next 獲取消息時,對比當前時間(now)和第一步轉化的時間(when),如果 now < when,則通過 epoll_wait 的 timeout 進行等待

  4. 如果該消息需要等待,會進行 idel handlers 的執行,執行完以后會再去檢查此消息是否可以執行

8. View.post 和 Handler.post 的區別

View.post 最終也是通過 Handler.post 來執行消息的,執行過程如下:

  1. 如果在 performTraversals 前調用 View.post,則會將消息進行保存,之后在 dispatchAttachedToWindow 的時候通過 ViewRootImpl 中的 Handler 進行調用。

  2. 如果在 performTraversals 以后調用 View.post,則直接通過 ViewRootImpl 中的 Handler 進行調用。

9. Handler 導致的內存泄漏

略過不講~

10. 非 UI 線程真的不能操作 View 嗎

不能操作,原因是 ViewRootImpl 會檢查創建 ViewRootImpl 的線程和當前操作的線程是否一致。而 ViewRootImpl 是在主線程創建的,所以非主線程不能操作 View。

到此,相信大家對“怎么掌握Handler消息機制”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

即墨市| 双城市| 云梦县| 阿克苏市| 武强县| 竹溪县| 韶关市| 尉氏县| 当阳市| 廊坊市| 叙永县| 调兵山市| 稷山县| 裕民县| 准格尔旗| 碌曲县| 莱芜市| 同仁县| 杭锦后旗| 汕头市| 青海省| 乌拉特后旗| 登封市| 谢通门县| 余干县| 团风县| 简阳市| 永登县| 常山县| 海宁市| 伊春市| 泗洪县| 蚌埠市| 滦平县| 河津市| 民乐县| 资中县| 临朐县| 海兴县| 九寨沟县| 丰城市|