[Framework] Android Handler 工作原理

[Framework] Android Handler 工作原理

Android 中的 Handler 都被人说烂了,但是还是想多说一次,因为在 Android 的系统中它真的非常重要而且它的机制并没有很复杂,无论是新手和老手都可以好好学习下,这对理解 Android 系统很重要,所以说学习的性价比非常高。

Android 中 IPC 跨进程通信主要依靠 Binder (参考我之前的文章 Android Binder 工作原理), 而同一个进程的通信就主要依靠 Handler

这里先简单描述下 Handler 的工作流程:

首先在 Handler 处理任务的线程需要调用 Looper#prepare() 方法,来为当前线程创建一个 Looper 实例,这个实例通过 ThreadLocal 存放,也就是一个线程只能有一个这个实例;Looper 构造函数中会初始化一个 MessageQueue 对象,MessageQueue 对象就是用来封装任务的队列,它是用的链表实现,而任务队列的 Item 就是 Message 对象,也就是单个的任务;任务处理线程调用 Looper#prepare() 方法完成后,会调用 Looper#loop() 方法,这个时候任务线程就会阻塞去处理对应 Looper 中的 MessageQueue 中的任务,处理的方式就是一个死循环从 MessageQueue 中去获取最新的 Message 获取到后,然后调用对应的 Handler#handleMessage() 方法去执行它,执行完成后又去获取下一个任务,如果没有新的任务就会陷入阻塞,等有新的任务来的时候会唤醒这个阻塞(这个唤醒机制是用的 Linux 中的 epoll 机制),继续执行新的任务。

上面大致描述了 LooperMessageQueue 如何从队列中获取新的任务和执行任务,这里再简单描述下怎么插入任务,首先要自定一个 Handler 对象,构造函数中需要传入上面所创建的 Looper 对象,其中自定义的 Handler#handleMessage 方法就是用来执行任务的方法,其他的线程需要向对应的 Looper 线程添加任务时,就调用上面 Handler 实例的 sendMessage 方法来添加任务,也就是向 MessageQueue 队列中添加任务,最终会在 Looper 所对应的线程执行,这样就完成了一次跨线程的通信。

下面是一个简单的 Handler 使用的代码:

Kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    var handlerLooper: Looper? = null
    val handlerThread = object : Thread() {
        override fun run() {
            super.run()
            Looper.prepare()
            handlerLooper = Looper.myLooper()
            Looper.loop()
        }
    }
    handlerThread.start()
    while (handlerLooper == null) {
        // 等待 HandlerThread 初始化完成
    }
    val handler = object : Handler(handlerLooper!!) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            if (msg.what == 0) {
                // TODO: 处理任务,该方法最终工作在 HandlerThread 线程
            }
            
        }
    }
    
    val msg = handler.obtainMessage(0)
    // 发送的代码工作在主线程
    handler.sendMessage(msg)
}

Android 中有现成的 HandlerThread,不用自定义,我这里是为了展示这个过程。

Tips: 我后续的源码分析都是基于 andorid 31

Looper

Looper 初始化

Java 复制代码
// ...
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));
}
// ...
Java 复制代码
// ...
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
// ...

初始化的过程非常简单,如果当前线程已经有 Looper 对象就直接报错,如果没有新建一个 Looper 对象放在 ThreadLocal 中,在 Looper 的构造函数中,创建了 MessageQueue 实例和保存了当前的 Thread 对象。

Looper 任务处理

Java 复制代码
// ...
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }

    me.mInLoop = true;

    // ..

    me.mSlowDeliveryDetected = false;

    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}
// ...

Looper#loop() 方法也非常简单,修改了当前的 Looper 的一些状态,然后在死循环中无限调用 loopOnce() 方法去执行任务,如果该方法返回 false 就表示该 Looper 已经退出,就跳出循环。

Java 复制代码
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }

    // 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);
    }
    
    // ...
    
    Object token = null;
    if (observer != null) {
        token = observer.messageDispatchStarting();
    }
    long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
    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);
        }
    }
    
    
    if (logging != null) {
       logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }
    
    // ...

    msg.recycleUnchecked();

    return true;
}

直接调用 MessageQueue#next() 方法去获取 Message 对象,如果返回空就表示 Looper 已经退出了。这里有一个 Printer 对象,在 Message 执行前后都会打印对应的日志,可以通过调用 Looper#setMessageLogging() 方法来自定义这个对象,根据打印的日志内容我们就可以分辨是开始前还是开始后,在 APM 中经常通过这个方法来判断主线程的任务执行是否超时。执行任务会调用的是 handler.target#dispatchMessage() 方法,其实这个 target 对象就是 Handler,后续再分析这个方法。

这次看代码还有新发现,这里还多了一个 Observer 对象,我记得以前的旧版本是没有的,可以通过 Looper#setObserver() 方法来设置,它的接口如下:

Java 复制代码
public interface Observer {
    /**
     * Called right before a message is dispatched.
     *
     * <p> The token type is not specified to allow the implementation to specify its own type.
     *
     * @return a token used for collecting telemetry when dispatching a single message.
     *         The token token must be passed back exactly once to either
     *         {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
     *         and must not be reused again.
     *
     */
    Object messageDispatchStarting();

    /**
     * Called when a message was processed by a Handler.
     *
     * @param token Token obtained by previously calling
     *              {@link Observer#messageDispatchStarting} on the same Observer instance.
     * @param msg The message that was dispatched.
     */
    void messageDispatched(Object token, Message msg);

    /**
     * Called when an exception was thrown while processing a message.
     *
     * @param token Token obtained by previously calling
     *              {@link Observer#messageDispatchStarting} on the same Observer instance.
     * @param msg The message that was dispatched and caused an exception.
     * @param exception The exception that was thrown.
     */
    void dispatchingThrewException(Object token, Message msg, Exception exception);
}

Observer 可以监听任务开始,结束和异常,看上去它可以替换 Printer,不过它是对应用层隐藏的,需要骚操作。

Handler

Handler 初始化

Java 复制代码
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

其中 LooperMessageQueue 就不用多说了;其中 mCallback 对象就是可以优先于 Handler#handleMessage 方法处理任务,也可以拦截任务不让 Handler#handleMessage 执行;async 是表示发送的消息是否是异步消息,这个和消息屏障关系密切,后续会再分析。

Handler 发送消息

Java 复制代码
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);
}

无论是调用 Handler 的哪个方法发送消息,最终都会到 enqueueMessage() 方法里面去,在 Looper 中我们说到 target 就是 Handler 对象,在这里就得到验证了,如果 Handler 的构造函数中设置为异步,Message 也会被设置为异步,然后直接将处理好的 Message 通过 MessageQueue#enqueueMessage() 方法添加到队列中。

Handler 处理消息

Looper 下发消息时说到最后会调用 HandlerdispatchMessage() 方法:

Java 复制代码
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

如果 Message 中有设置 Callback 就直接在对应的 Callback 中处理,不继续下发; 如果 Message 中没有设置 Callback,会先检查 Handler 是否有设置 mCallback,如果有设置,调用 mCallback 处理,mCallbackhandleMessage 返回 true 就表示要拦截该消息,如果不拦截就交由 HandlerhandleMessage() 方法处理。

MessageQueue

MessageQueue 插入任务

在讲 Handler 的时候说到插入消息会直接调用 MessageQueue#enqueueMessage() 方法:

Java 复制代码
boolean enqueueMessage(Message msg, long when) {
    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;
        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;
}

如果 Message 没有设置 Handler 直接抛出异常;如果 Message 已经是 inUse 状态,直接抛出异常;如果 MessageQueue 已经退出,就直接回收 Message;按照任务的触发时间由小到大,插入到 mMessage 链表中,最后如果需要唤醒 next() 方法中的 nativePollOnce() 方法,就会调用 nativeWake 方法,这是一个 C++ 实现的方法(内部实现其实是向一个管道 fd 中写入了一个数字 1,通过 epoll 机制,会监听到管道 fd 有数据写入,然后会唤醒 nativePollOnce() 方法,然后去读取新的 Message)。

MessageQueue 读取任务

在讲 Looper 时讲到最后获取任务是通过 MessageQueue#next() 方法:

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();
        }
        // 等待 epoll 唤醒,唤醒是等待 nativeWake() 方法调用或者超时(超时时间为 nextPollTimeoutMillis)
        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;
            // target 为空,就表示这是一个屏障的消息
            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.
                    // 当前消息的时间大于当前的时间,表示还没有到达执行的时间,计算间隔,等待重新进入 nativePollOnce 方法
                    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


            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            // 如果 pendingIdleHandlerCount 小于 0 就表示当前的这一次 next() 方法调用,还没有执行过 IdleHandler,就需要去读取 IdleHandler 的数量,在一次 next() 方法调用时最多执行一次 IdleHandler。
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 这里就表示 IdleHandler 为空,或者已经执行过了。
                // 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 {
                // 执行 idleHandler
                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.
        // 这里设置为 0 后,IdleHandler 就不会再次执行,需要等待下次的 next() 方法调用
        pendingIdleHandlerCount = 0;

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

通过 nativePollOnce() 方法等待唤醒(通过调用 nativeWake() 方法)或者超时(超时时间是 nextPollTimeoutMillis),唤醒后直接获取最新的一条 Message(先跳过同步消息逻辑),然后判断任务执行的时间,如果大于当前时间就继续等待,直到可以执行,如果小于当前时间直接返回,同时把这条消息从队列中移除(先跳过 IdleHandler 逻辑)。

同步消息和消息屏障

如果 target 对象为空就表示该消息是一个同步屏障,如果是同步屏障的话他就会重新再去取该同步屏障消息后面的最新一个异步消息去执行,如果没有异步消息就会去等待。

通过以下方法添加屏障消息:

Java 复制代码
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;

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

其实就是在消息队列中添加了一个 target 为空的消息,同时在 Messagearg1 对象中添加 token 对象,然后这个 token 也会返回给调用方,这个 token 在移除屏障时需要用到。

通过以下方法可以移除这个消息屏障:

Java 复制代码
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);
        }
    }
}

我之前在 [Framework] Android Choreographer 工作原理 有简单介绍过,在绘制的时候就会用到同步消息和消息屏障来提高绘制相关的 Message 的优先级。

请求绘制最终会到 ViewRootImpl#scheduleTraversals() 方法中,在这个方法中就会添加一个屏障:

Java 复制代码
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

Choregorapher 收到 Vsync 信号后会发送一个 Handler 的异步 Message:

Java 复制代码
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
        VsyncEventData vsyncEventData) {
        // ..
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
       // ..
}

在绘制完成后会调用 ViewRootImpl#unscheduleTraversals() 方法移除这个屏障:

Java 复制代码
    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

IdleHandler

IdleHandler 就是在 next() 方法获取不到消息时会执行一次,next() 方法调用一次最多只能执行一次 IdleHandler,执行 IdleHandler 的时候就表示当前的 Looper 比较空闲,IdleHandler 的数量最多为 4 个 (在 ActivityonDestroy() 回调就是在 IdleHandler 中执行的,可能写这部分代码的人,认为 onDestroy() 相对其他的生命周期没有那么重要,但是由于 IdleHandler 的特性也会引发一些问题,后续有时间会分析这个问题) 。

可以通过以下方法添加 IdleHandler

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

epoll 在 Handler 中的工作方式

MessageQueue 的构造函数中会通过 Native 方法 nativeInit() 初始化 epoll,初始化流程会先通过 epoll_create() 方法创建一个 EpollFd,然后构建一个管道的 fd 用于 epoll 来监听它的读写状态,这个 fd 被命名为 WakeFd,然后通过 epoll_ctl() 方法将 WakeFd 添加到 EpollFd 中,以供后续通过 EpollFd 能够监听 WakeFd 的状态。

MessageQueuenext() 方法中会通过 Native 方法 nativePollOnce() 方法去监听上面所提到 WakeFd 的状态,Native 层是通过 epoll_wait() 方法去监听状态,这个方法会等到超时或者 WakeFd 有数据写入就会返回。

MessageQueueenqueueMessage() 方法中插入 Message 后,会判断是否需要唤醒 next() 中的 nativePollOnce() 方法,如果需要唤醒就会调用 Native 方法 nativeWake() 方法,Native 层就会向 WakeFd 中写入一个数字 1,然后 nativePollOnce() 间听到这个 WakeFd 写状态后就会返回,也就解除 next() 方法的阻塞去读取新的 Message

最后聊一聊主线程

在 Android Framework 中有两个主要的主线程 Handler,一个是 ActivityThread 中的 H,它主要来处理四大组建的各种生命周期;还有一个是 Choreographer 中的 FrameHandler,它主要负责绘制,动画,输入等操作。这两个 Handler 负责的工作和用户体验都极为密切,主线程也可以说非常忙。

在项目中我经常发现很多的代码都会在主线程执行,明明有的操作不需要主线程执行。比如说在一个网络请求的通用方法中,在网络完成的回调中就会主动切换到主线程,明明很多地方的代码不需要用到主线程,我个人的观点是一定需要切换主线程时再切换,在一定程度上能够缓解应用卡顿的问题。

参考文章

Android消息机制2-Handler(Native层)
select/poll/epoll对比分析

相关推荐
刘艳兵的学习博客2 小时前
刘艳兵-DBA033-如下那种应用场景符合Oracle ROWID存储规则?
服务器·数据库·oracle·面试·刘艳兵
用户31574760813513 小时前
成为程序员的必经之路” Git “,你学会了吗?
面试·github·全栈
布川ku子14 小时前
[2024最新] java八股文实用版(附带原理)---Mysql篇
java·mysql·面试
有趣的杰克21 小时前
移动端【01】面试系统的MVVM重构实践
面试·职场和发展·重构
saturday-yh1 天前
性能优化、安全
前端·面试·性能优化
前进别停留2 天前
206面试题(71~80)
面试
不二人生2 天前
SQL面试题——飞猪SQL面试 重点用户
数据库·sql·面试
dream_ready2 天前
四万字长文SpringBoot、Spring、SpringMVC等相关面试题(注:该篇博客将会持续维护 最新维护时间:2024年11月12日)
java·spring boot·后端·spring·面试·1024程序员节
知否&知否2 天前
Kafka面试夺命连环30问(一)
分布式·面试·kafka
百晓生说测试2 天前
15:00面试,15:08就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展