美文网首页
Android Handler 机制

Android Handler 机制

作者: VanceKing | 来源:发表于2022-02-24 14:08 被阅读0次

源码基于 Sdk 30(Android 11.0/R)。

简介

Handler 是标准的事件驱动模型。存在一个消息队列 MessageQueue,它是一个基于消息触发时间的优先级队列,还有一个基于此消息队列的事件循环 Looper,Looper 通过循环,不断的从 MessageQueue 中取出待处理的 Message,再交由对应的事件处理器 Handler 来处理。
Handler 用于线程间通讯,常用于主线程更新 UI 和发送延迟消息。

主要涉及的角色:

  • Handler:处理器,主要向消息队列发送和接收消息。
  • MessageQueue:消息队列,主要用来向消息池添加消息和获取消息。
  • Looper:消息循环器,为线程提供 Looper 对象和开启消息循环。
  • Message:消息的封装,单链表结构,缓存 50 个;

整个消息的循环流程如下:

  1. 使用 Handler 发送 Message 到 MessageQueue 中,并将 Message.target 设置为发送该消息的 Handler;
  2. MessageQueue 收到消息后,根据时间插入到 Message 的单链表中;
  3. Looper 通过 loop() 方法不断的从 MessageQueue 中提取符合条件的 Message,并将 Message 交给对应的 target 来处理;
  4. Handler 调用自身的 handleMessage() 方法来处理 Message。

Handler

初始化

@Deprecated
public Handler() {
    this(null, false);
}

@Deprecated
public Handler(@Nullable Callback callback) {
    this(callback, false);
}

public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}

@hide
public Handler(boolean async) {
    this(null, async);
}

@hide
public Handler(@Nullable Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    //如果从当前线程获取不到 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;
}

@hide
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

构造 Handler 时需指定 Looper 对象。获取主线程 Looper 的方法有 Context#getMainLooper(),返回的是 ActivityThread.mLooperView#getHandler()#getLooper() 返回的是 ViewRootImpl#ViewRootHandler.

通过 Looper.prepare() 方法给当前线程设置 Looper,有且只能调用一次。

//Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}

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

@Deprecated
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

主线程的 Looper 对象在 ActivityThread#main() 方法中创建。

判断当前是否在主线程: Looper.myLooper() == Looper.getMainLooper()

发送消息

发送消息分为两类:

  1. send Message
  2. post Runnable

post 方法也是把 Runnable 封装成 Message 再通过 send 方法发送。

public class Handler {
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

最终调用了 MessageQueue#enqueueMessage() 方法,把消息添加到消息队列中。

处理消息

public class Handler {
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

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

public final class Looper {

    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;
            }
            msg.target.dispatchMessage(msg);
            msg.recycleUnchecked();
        }
    }
}

  1. 发送消息时通过 msg.target 字段记录此 Handler;
  2. 获取线程关联的 Looper 对象,如果对象为空抛出异常;
  3. 获取关联此 Looper 的 MessageQueue 对象,循环调用 MessageQueue#next() 获取需要处理的消息。消息为空退出循环;
  4. 回调处理该消息。有如下三种回调方式:
    1. msg.callback.run()
    2. mCallback.handleMessage(msg),方法返回 true,不会回调 3。
    3. Handler#handleMessage(msg)
  5. 回收 Message,最多缓存 50 个。

Handler.mCallback 具有优先处理消息的权利。如果消息被 Callback 处理并拦截返回 true,那么 Handler#handleMessage(msg) 方法就不会被调用了;如果 Callback 没有拦截,那么消息可以同时被 Callback 和 Handler 处理。

MessageQueue

MessageQueue 顾名思义是消息队列。Message 在 MessageQueue 中的存储方式是使用 next 字段逐个向后指向的单链表结构来存储的,MessageQueue 中的 mMessages 指向链表的第一个元素。

添加消息

// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    // message 必须指定 Handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        // 队列退出,不能添加消息
        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;
        // 1. 首次添加消息; 2. 及时消息; 3. 延迟事件小于头节点的时间
        // 这三种情况才更新头节点
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {// 将消息插入到链表的非首部位置
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            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. 判断是否需要唤醒 nativeWake() 线程;

获取消息

// MessageQueue.java
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // 非首次调用
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 调用 native 方法阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // 如果遇到同步屏障,遍历查找之后的异步消息
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            // 链表首部消息或异步消息
            if (msg != null) {
                // 消息时间大于当前时间,计算阻塞时间
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    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;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // 初始化 IdleHandler
        }

        // 执行 IdleHandler。1. 消息队列空;2.首部消息不到执行时间

        // 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 = 0;
    }
}
  1. 首次进入循环 nextPollTimeoutMillis=0,阻塞方法 nativePollOnce() 会立即返回;
  2. 如果遇到同步屏障,跳过后面的同步消息,只查找异步消息;
  3. 时间到返回消息,否则重新计算阻塞时间;
  4. 没有可以处理的消息,则处理 IdleHandler。

移除消息

Handler 移除消息间接调用了 MessageQueue 中的方法。

// MessageQueue.java
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;
        }
    }
}

通过 Handler#removeCallbacksAndMessages(null) 方法移除所有消息。

Looper

线程只有通过 Looper 才能开启消息机制。

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;

    final MessageQueue mQueue;
    final Thread mThread;

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

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

    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

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

    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

    public static void loop() {
        // ...
    }
}
  1. Looper 在构造方法中实例化 MessageQueue。
  2. Looper 构造器是私有的,通过工厂方法 prepare() 把 Looper 的实例保存在 sThreadLocal 中。sThreadLocal 是 ThreadLocal< Looper > 类型,用于绑定当前线程私有对象。
  3. sThreadLocal 是静态变量,保存所有线程的 Looper 实例。

消息阻塞原理

通过 nativePollOnce(ptr, nextPollTimeoutMillis) 方法实现。
参数 nextPollTimeoutMillis 有 3 中含义:

  1. 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时;
  2. 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回;
  3. 如果 nextPollTimeoutMillis > 0,最长阻塞 nextPollTimeoutMillis 毫秒(超时),如果期间有程序唤醒会立即返回。

基于 Linux 的 pipe/epoll 机制,没有消息时阻塞线程并进入休眠释放 cpu 资源,有消息时唤醒线程,处理消息。
Java 层的阻塞是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。

同步屏障

Handler 中加入同步屏障来实现异步消息优先执行的功能,满足需要马上处理的消息。

// MessageQueue.java
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        // 没有设置 target

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            // 遍历查找消息插入位置
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        // 更新链表
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

@UnsupportedAppUsage
@TestApi
public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}
  1. 同步屏障无法被外部应用访问;
  2. 同步屏障消息的 target 为空;
  3. 同步屏障之后的所有同步消息都会被忽略,只处理异步消息。

添加异步消息的方式有两种:

  1. Handler 构造方法中传入 async 参数设置为 true,那么使用此 Handler 发送的 Message 都是异步的;
  2. 创建 Message 对象时,调用 Message#setAsynchronous(true) 方法。

ViewRootImpl.scheduleTraversals(),在绘制 View 之前会插入一个消息屏障,绘制完成之后移除。

IdleHandler

IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机。但它执行的时机依赖消息队列的情况,如果 MessageQueue 一直有待执行的消息时,IdleHandler 就一直得不到执行,也就是它的执行时机是不可控的,不适合执行一些对时机要求比较高的任务。

IdleHandler 何时执行:

  1. MessageQueue 为空,没有消息了;
  2. MessageQueue 中有消息,但都是延迟消息(when>currentTime),没到执行时间;
public final class MessageQueue {
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private IdleHandler[] mPendingIdleHandlers;

    public void addIdleHandler(@NonNull IdleHandler handler) {
        mIdleHandlers.add(handler);
    }

    public void removeIdleHandler(@NonNull IdleHandler handler) {
        mIdleHandlers.remove(handler);
    }

    Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        for (;;) {
            // 从 mMessages 链表中获取 Message

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // 首次执行 IdleHandler
            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);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        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.
        // 所有 IdleHandler 处理完毕,重置为 0,退出 IdleHandler 循环。
        // 如果还没有消息可以处理,休眠。
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        // 返回 true:保留 IdleHandler,重复使用;false:只使用一次
        boolean queueIdle();
    }
}
  1. 如果消息队列为空或所有消息没到执行时间,会执行 IdleHandler;
  2. 计算 IdleHandler 的个数,复制到 IdleHandler 数组(长度最小为 4)中;
  3. 遍历数组,处理 IdleHandler,回调 queueIdle() 方法,返回 false 移除 IdleHandler;

framework 中的 IdleHander:

  1. ActivityThread#Idler:handleResumeActivity() 方法最后调用;
  2. ActivityThread#GcIdler:垃圾和资源回收;
  3. ActivityThread#PurgeIdler:资源回收;

Handler 内存泄漏问题

在 Java 中,非静态内部类和匿名类内部类都会隐式持有它们所属的外部类的引用,但是静态内部类却不会。

示例代码如下:

public class MainActivity extends AppCompatActivity {

    private final Handler handler = new Handler() {
        @Override public void handleMessage(Message msg) {
            // 引用 Activity
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler.sendEmptyMessage(0x123);
        handler.post(new Runnable() {
            @Override
            public void run() {
                // 引用 Activity
            }
        });
    }
}

引用链:
Looper -> MessageQueue -> Message -> Handler -> Activity

匿名的 Handler 和匿名的 Runnable 都会引用 MainActivity。当 Activity 销毁时,未处理的消息会引用 Handler,而 Handler 又持有它所属的外部类也就是 MainActivity 的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了 MainActivity 被垃圾回收器回收,从而导致了内存泄漏。

Q & A

1. IdleHandler 有什么用?
当主线程没有消息处理时,提供处理任务的机会。

2. addIdleHandler() 和 removeIdleHandler() 是否需要成对使用?
不需要,当 IdleHander#queueIdle() 方法返回 false 时,会自动移除。

3. 当 mIdleHanders 一直不为空时,为什么不会进入死循环?
只有当 pendingIdleHandlerCount = -1 时,才会执行 mIdleHander,执行完毕后赋值为 0,就不重复执行了;

4. IdleHandler 的 queueIdle() 运行在那个线程?
IdleHandler#queueIdle() 运行的线程只和当前 MessageQueue 的 Looper 所在的线程有关;

5. 主线程需不需要调用 Looper.prepare() 方法?
在发送消息之前,当前线程必须调用 Looper.prepare() 方法,有且只能调用一次。
主线程的 Looper 对象是在 ActivityThread#main() 方法中调用 Looper#prepareMainLooper() 方法设置。

总结

  1. 创建 Handler 对象时需指定 Looper 对象,通过 Looper.prepare() 方法为当前线程绑定一个 Looper 实例。重写 Handler#handleMessage() 方法供 Message 回调。
  2. Handler 通过 send 或 post 方法,给 msg.target 赋值为 handler 自身,然后按消息执行时间升序加入 MessageQueue 中。单链表结构通过 Message 实现。
  3. 线程通过 Looper#loop() 进入无限循环,不断调用 MessageQueue#next() 获取可以处理的消息。然后回调 msg.target.dispatchMessage(msg)方法。
  4. 如果消息队列空或延迟消息都没到调度时间,会处理 IdleHander。
  5. MessageQueue.next() 无消息是会休眠,有消息时会唤醒。底层基于 Linux 的 epoll 机制。
Handler

参考

[1] 深入理解 MessageQueue
[2] android-weak-handler
[3] 一次性讲清楚 Handler 可能导致的内存泄漏和解决办法

相关文章

网友评论

      本文标题:Android Handler 机制

      本文链接:https://www.haomeiwen.com/subject/ybmzlrtx.html