Handler源码笔记

165 阅读5分钟

先讲一下Handler到底是怎么跨线程的:

本质上,就是利用了同一进程中的线程间资源共享这一机制。例如,我们想在子线程完成一项任务后发送一个表示成功的消息到主线程。可以直接定义一个全局变量 public static volatile boolean message = false,然后等待子线程任务完成后给message赋值为true,这样主线程发现message的值为true就明白任务已经完成可以进行下一步操作了。这就是最简单的线程间消息传递了,当然为了主线程不至于傻等下去,我们得在主线程开启轮询不停的判断message的值是否为true,所以就有了Looper里面的无限for循环。由于同一时刻一个线程只能处理一个消息,为了应对多个线程同时发送多条消息的问题于是就有了messageQueue来按照时间顺序存放这些消息。不知道大家读到这里是否发现了根本没有Handler什么事,其实任何线程只要可以拿到messageQueue变量就可以把自己的消息添加到messageQueue,这事实上就完成了一次跨线程消息传递。但谷歌为了消息添加和消息处理的统一,专门构建了Handler类,message持有Handler的引用,哪个Handler发送最后就在哪个handler的回调方法中处理。

关于Looper核心逻辑总结​​:

  • ​线程(Thread)层面​​:
    每个线程有一个ThreadLocalMap,其中存储了多个线程本地变量(例如LoopersThreadLocal)。
  • ​ThreadLocal层面​​:
    sThreadLocal是全局唯一的键(Key),用于在每个线程的ThreadLocalMap中存储当前线程的Looper实例(Value)。
  • ​Looper层面​​:
    prepare()方法通过检查null确保每个线程只存一个LooperLooper在构造时创建唯一的MessageQueuemQueue)。
  • ​结果​​:
    ​每个线程 → 一个ThreadLocalMap → 一个Looper(通过sThreadLocal键) → 一个MessageQueue(通过mQueue字段)

一、主要涉及到的类和方法:

944365-a6a41fa7961184e2.png

二、Message类

  • 此类包含一个以sPool为头结点的单链表,链表最大长度为50

  • obtain()方法:从头结点取出一个message节点,sPool后移一位

    if (sPool != null) {
       Message m = sPool;//局部变量保存头结点
       sPool = m.next;   //sPool指向新的节点(原头结点的next节点)
       m.next = null;    //重置取出来的节点next指针
       m.flags = 0; 
       sPoolSize--;      ////节点数量减1
       return m;
    }
    
  • recycleUnchecked()方法:以头插法插入一个message节点:

    if (sPoolSize < MAX_POOL_SIZE) {//MAX_POOL_SIZE = 50
       next = sPool;    //当前使用完毕的新节点指向sPoll(以头插法插入复用链表)
       sPool = this;    //sPoll指向新的节点
       sPoolSize++;     //节点数量加1
    }
    

三、enqueueMessage方法分析:

    boolean enqueueMessage(Message msg, long when) {
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;//当前将要被处理的msg
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

在if中,将待插入的message和当前将要被处理的msg比较,按照时间先后插入链表中。 else中,将待插入的msg和链表中元素依次比较,按照时间顺序插入其中。

四、MessageQueue类

  • quit(boolean safe) 方法的理解,直接看代码:

    if (safe) {
        removeAllFutureMessagesLocked();//移除所有延迟消息
    } else {
        removeAllMessagesLocked();//移除所有消息
    }
    
  • IdleHandler:当线程将要进入堵塞,等待更多消息时,会回调这个接口。

    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();//返回值为false,使用完后会在idleHandler集合中直接删除,反之则不会
    }
    

    使用方式:

    WechatIMG16.png

    // 监控帧率下降
    Looper.myQueue().addIdleHandler {
        val frameTime: Long = SystemClock.uptimeMillis() - lastFrameTime
        if (frameTime > 32) { // 超过32ms/帧
            reportJank(frameTime)
        }
        true // 持续监控
    }
    
    // 性能检测模板
    void addInstrumentedIdleHandler(Runnable task) {
        Looper.myQueue().addIdleHandler(() -> {
            long start = SystemClock.uptimeMillis();
            task.run();
            long duration = SystemClock.uptimeMillis() - start;
            if (duration > 5) {
                logSlowTask(duration);
            }
            return false;
        });
    }
    

    在 Android 主线程中使用 IdleHandler 时,​​严格限制执行时间不超过 5 毫秒​​。这是基于 Android 框架设计的硬性要求:

    时间范围评级对系统的影响
    ​0-5ms​✅ 安全无感知影响
    ​5-16ms​⚠️ 警告可能影响下一帧渲染
    ​>16ms​❌ 危险必定造成卡顿掉帧
    • 一帧渲染周期 = 16ms (60FPS)
    • 系统预留 10ms 给应用处理UI/逻辑
    • IdleHandler 必须在 ​​帧间空闲期​​ 执行完毕

    各场景耗时参考:

    操作类型平均耗时备注
    创建简单对象0.01msnew Object()
    HashMap put(10次)0.1ms小数据操作安全
    SharedPreferences读取2~5ms避免使用
    View.invalidate()0.5ms简单视图
    解析JSON(1KB)1.5ms接近临界值
    SQLite查询(10条记录)3~20ms​禁止在IdleHandler中使用​

    五、同步屏障机制

    同步屏障其实是一条不持有handler引用的message,用于临时阻塞同步消息的处理,优先执行异步消息。可以通过postSyncBarrier()方法插入,移除同步屏障消息可以用removeSyncBarrier()方法。

    在next()方法中先找到同步屏障消息即target为null,找到后循环找到第一条异步消息并处理。如果没有找到即msg==null则会进入阻塞,代码见下面:

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

    使用流程

    // 获取主线程消息队列
    MessageQueue queue = Looper.getMainLooper().getQueue();
    
    // 1. 插入同步屏障
    int barrierToken = queue.postSyncBarrier();
    
    // 2. 发送异步消息(高优先级)
    asyncHandler.sendEmptyMessage(MSG_CRITICAL_UPDATE);
    
    // 3. 发送同步消息(普通优先级)
    normalHandler.post(() -> {
        Log.d("SyncTest", "常规任务执行");
    });
    
    // 4. 执行高优先级任务后移除屏障
    asyncHandler.postDelayed(() -> {
        queue.removeSyncBarrier(barrierToken);
        Log.d("SyncTest", "同步屏障已移除");
    }, 100);