Handle源码分析

本文详细解析了Android中Handler机制的工作原理,包括消息传递、处理流程及关键组件的作用。介绍了主线程与UI线程的关系,Handler、Looper和MessageQueue之间的交互机制,并探讨了屏障消息、异步消息以及HandlerThread的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为只有主线程能修改UI组件,所以主线程又被称作UI线程;但是如果主线程进行耗时操作,那么会被阻塞(ANR异常,系统无法响应输入事件和BroadCast),而子线程又不能修改UI组件,只能在新进程里发出一通消息,这时就需要采用handle的传递机制;

Handle的作用有两个:在新启动的线程发送消息(一般采用sendEmptyMessage);在主线程接受,处理消息(重写handleMessage);

handler机制相当于一个快递流水线公司:

handler:快递员

Message:快递

MessageQueue:快递分拣传送带

Looper:快递公司


总结
Handler机制可以简述为:
Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。

Message:

创建方式:

1.new Message()

2.Messasge.obtain()  (更优)

优先采用第二种方法创建message,会优先在message池中获取,如果池中没有就new一个新的message;

message在池中以链表形式串在一起,通过pool指针指向头部的message并取出,需要回收的时候就重新将pool指针指向回收的message;

Handler:

1.handler的构造方法:

1.获取handler所在线程的looper对象:mLooper = Looper.mylooper();

2.对looper判空,不为空就去拿looper的消息队列messagequeue :   mQueue = mLooper.mQueue

3.设置callback回调: mCallBack = callback

Handler只是消息的处理者,听命于大佬Looper(领导),Handler将消息通过messagequeue上报Looper,Looper轮询完这些消息后,给Handler进行分发处理;

需要注意的几点:

1.新创建的线程就是在主线程中,这样就无需创建Looper对象,系统已经自动创建了;

2.新创建的线程是自创的子线程(Thread),这样就需要自己获取Looper对象并启动它;通过调用Looper的prepare()方法即可获取Looper对象,然后在调用Looper的静态方法loop()来启动它;子线程创建Handler之前:保证调用了Looper.prepare创建Looper对象 如果子线程中new Handler也想更新UI,那只能通过new Handler(Looper.getMainLooper())方法强行切换到主线程进行UI更新;

这个prepare和myLooper()流程是通过threadLocal实现,下面有讲

2.Handler sendMessage()  Post()等相关API方法

1.不管有没有延时去sendmessage,最后对会调用sendMessageAtTime,并调用enqueueMessage将message发送到消息队列中,根据延时时间决定message在消息队列中的指针顺序。

  • msg.target = this  发送到消息队列之前绑定message和handler,用于Looper分发消息。

2.如果采用的是post一个runnable,那么会先将runnable封装成一个message,然后再进行sendmessage;如果采用post runnable的方式,那么就会在把runnable封装成message的时候,设定callback,在最后dispatch(见下面)去判断是否含有callback,有的话就回调handleCallBack否则就是普通的handleMessage()回调;

3.Handler dispatchMessage()方法

Looper处理完message,会调用handler的dispatchMessage方法分发消息,内核其实是通过我们重写的handleMessage方法来更新UI任务;

MessageQueue

前面讲到Handler在发送消息时会调用MessageQueue的enqueueMessage()方法

说明:

1.messagequeue是一个先进先出的队列,内部采用了单向链表的数据结构来储存message

2.Looper通过调用 Message.next()方法,取出MessageQueue中的消息;

Looper

Looper在Handler机制中扮演最关键的一环,它不断地从消息队列中取出消息并进行分发处理事件(dispatchmessage)。

prepare:

ThreadLocal

ThreadLocal可以提供我们一个局部变量,而且这个变量与一般变量还不同,他是每个线程独有的,与其他线程互不干扰的。

ThreadLocal是一个线程内部的数据存储(这里存放的就是Looper对象)entry 类,key就是当前线程的ThreadLocal,一个线程只有一个ThreadLocal,value就是要存储的数据。

通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据,通过一个entry 来存储线程和looper的对应关系

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
  static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            //维护了一个类似map(key是当前线程绑定的threadlocal, v是要存取的值)
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
...
}

Looper.prepare方法作用是将Looper和当前线程进行绑定(也就是将当前线程存入threadLocal)

prepare之后紧接着调用handler构造方法并传入myLooper(),这里则可以获取绑定或称之存储到ThreadLocal的looper

// Handler.java
public Handler(@NonNull Looper looper,@Nullable Callback callback, boolean async) {
    // 关键代码:通过ThreadLocal获取当前线程的Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()
                + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    // ...其他初始化
}

loop():

loop方法是启动Looper的方法,获取当前looper的消息队列通过for死循环取出消息message(若message为空则CPU进行休眠状态直到下一个message的到来),再msg.target拿到handler引用,通过

调用dispatchMessage进行消息分发,让handler处理消息;

主线程中已经在ActivityThread中的main方法里创建主线程的looper并loop启动

1.新创建的线程就是在主线程中,这样就无需创建Looper对象,系统已经自动创建了;

2.新创建的线程是自创的子线程(Thread),这样就需要自己获取Looper对象并启动它;通过调用Looper的prepare()方法即可获取Looper对象,然后在调用Looper的静态方法loop()来启动它;子线程创建Handler之前:保证调用了Looper.prepare创建Looper对象 

屏障消息和异步消息:

屏障消息

在next()方法中,有一个屏障的概念(message.target ==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#postSyncBarrier()


    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token
        synchronized (this) {
            final int token = mNextBarrierToken++;
            //从消息池中获取Message
            final Message msg = Message.obtain();
            msg.markInUse();
            
            //就是这里!!!初始化Message对象的时候,并没有给target赋值,因此 target==null
            msg.when = when;
            msg.arg1 = token;
            //...

屏障消息作用:出现屏障的时候,会过滤同步消息,返回异步消息;具体可以看queue的next拿消息方法;

                 //关键!!!
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
 //如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
                }

当消息队列开启同步屏障的时候(即标识为msg.target == null),消息机制在处理消息的时候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

下面用示意图简单说明:

如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙----同步屏障(红色部分)。有了同步屏障的存在,msg_2 和 msg_M 这两个异步消息可以被优先处理,而后面的 msg_3 等同步消息则不会被处理。那么这些同步消息什么时候可以被处理呢?那就需要先移除这个同步屏障,即调用removeSyncBarrier()

异步消息

在Hadnler无参的构造函数中,默认设置的消息都是同步的;异步消息通常代表着中断,输入事件和其他信号,这些信号必须独立处理,即使其他工作已经暂停;

也就是说异步和同步消息在队列中的情况下如果出现了屏障消息,优先取出异步消息进行处理;

如何设置异步消息:

直接调用Message的setAsynchronous()方法,方法如下:

Android有哪些常见的屏障消息发送时机:

1.ViewRootImpl.scheduleTraversals()

在scheduleTraversals方法中通过调用postSyncBarrier()方法发送一个屏障过去后再发送异步消息,因为绘制过程中为了保证绘制流程,防止后序同步消息的执行,直到视图绘制完成才执行同步消息;

 void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //开启同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //发送异步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

postCallback()最终走到了ChoreographerpostCallbackDelayedInternal()

2.view的invalidate(原理和上面一致)

PostDelay是怎么实现的?

消息的放置

消息入队:调用 sendDelayMessage() 或 postDelayed() 时,系统会创建一个 Message 对象并设置其 when 字段(执行时间戳),计算方式为:

when = SystemClock.uptimeMillis() + delayMillis

有序插入:MessageQueue 内部维护一个按 when 排序的单链表:

  1. 新消息会根据 when 值插入到合适位置
  2. 队列总是保持按执行时间从早到晚的顺序排列
  3. 如果消息没有延迟(delayMillis=0),when 就是当前时间,会尽可能早执行

消息的取出

消息轮询:Looper 通过 loop() 方法不断调用 MessageQueue.next() 获取下一条消息

延迟处理:

  • next() 方法会检查队首消息的 when 值
  • 如果 when > now(未到执行时间),计算需要等待的时间:
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE)
  • 线程会进入休眠状态(通过 nativePollOnce),直到:
  1. 到达消息执行时间
  2. 有新消息插入队列前面
  3. 有其他中断

并不是通过延时放入队列消息去做的,而是正常放入队列并标记延时时间msg.when

 for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
 
            nativePollOnce(ptr, nextPollTimeoutMillis);
    ···
                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;
                }
 
           
               
        ···
        }
}

很贴心的给出了注释解释“ Next message is not ready. Set a timeout to wake up when it is ready.”,翻译“下一条消息尚未准备好。设置一个超时,以便在准备就绪时唤醒。”

根据当前这条消息的when来判断,如果头部的这个Message是有延迟而且延迟时间没到的(now < msg.when),不返回message 而且会计算一下时间(保存为变量nextPollTimeoutMillis),然后再循环的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞;

如果在阻塞这段时间里出现 无延迟message加入MessageQueen中又是怎么实现立即处理这个message的呢?

 写的很清楚, New head, wake up the event queue if blocked

在这里p 是现在消息队列中的头部消息,我们看到| when < p.when 的时候它交换了放入message与原来消息队列头部P的位置,并且 needWake = mBlocked; (在next()中当消息为延迟消息的时候mBlocked=true),继续向下通过nativeWake 唤起线程;

(也就是说 有新的msg或者到了执行延时msg时就会唤醒线程)

IdleHandler(MessageQueue空闲执行的任务)

IdleHandler出现需要的场景:

MessageQueue 是一个基于消息触发时间的优先级队列,所以队列出现空闲存在两种场景。

  • MessageQueue 为空,没有消息;
  • MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行;

当MessageQueue在上述两种情况下,会触发IdleHandler创建并执行(如果有);

当前空闲任务IdleHandler完成后,会将nextPollTimeoutMillis(相当于loop的休眠时间)置为0,也就是不阻塞消息队列,后续有message将继续执行;当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的 message 执行。

HandlerThread:

原理流程:

1.创建一个HandlerThread类对象,即新开一个工作线程

2.为当前线程创建一个Looper对象和MessageQueue对象,同时启动线程进行工作,开启looper消息循环;

3.创建工作线程的Handler,并将Hanlder绑定到Looper对象,从而绑定到HandlerThread的工作线程;

4.Handler开始发Message(原理和Handler一致)

5.结束工作线程,暂停线程的消息循环(quit/quitsafetly,后者更安全,退出消息循环时在不在意队列正在处理消息);

如下: 可以看到handlerThread其实就是thread然后直接start,在它自身run方法中封装了loop获取和启动等逻辑,方便后续handler的操作。

// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

// 步骤2:启动线程
   mHandlerThread.start();

// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  Handler workHandler = new Handler( handlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息处理
                return true;
            }
        });

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环
  mHandlerThread.quit();

常见问题:

1.Handler明明在子线程中发送的消息了怎么会跑到主线程中?

在哪通过handler.sendMessage无所谓,重要的是Looper的位置,Looper在哪个线程执行的,handler的dispatchmessage回调就在哪个线程执行的,和在哪个线程发送消息无关;

2.Handler的消息如何在handleMessage中被接收到的呢?

在handler.sendmessage这一步中,通过msg.target = this,将handler和当前message进行绑定,这样就能在dispatchmessage(msg)找到对应的handler,并执行对应的handlemessage方法

3.new Handler(Looper.getMainLooper())和new Handler()有区别吗?

new handler()持有的是当前线程的Looper,而new handler(Looper.getMainLooper())持有的是主线程的Looper;

4.当next()没有消息了之后一直死循环,主线程会被卡死吗?

我们知道MessageQueue中,每次在next(),取message的时候,如果没有message了,他就会处于挂起状态(这样做的目的),当有消息到来,或者下一个消息的时间到达之后,都会唤醒线程,那么这个是如何实现的呢?

挂起线程

jni方法Java层定义挂起线程方法,方法位于MessageQueue#next()方法中

private native void nativePollOnce(long ptr, int timeoutMillis);

当消息队列为空或者delayTime还在等待时触发

  • 底层行为:线程阻塞在 epoll_wait 系统调用上,释放 CPU 资源(省性能)

线程唤醒

jni方法Java层定义唤醒线程方法,方法位于MessageQueue#enqueueMessage方法中

private native static void nativeWake(long ptr);
  • 触发条件

    • 新消息插入到空队列时(即休眠中被唤醒)。

    • 插入的消息是异步消息延迟消息到期

底层行为:向管道写入数据,epoll_wait 检测到可读事件,线程被唤醒。

这里用到了Linux pipe/epoll机制,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

主线程从休眠状态到唤醒状态,通过epoll机制;

选择 epoll 的原因是?

epoll机制好处

  • 高效:相比轮询,epoll 只在事件发生时唤醒线程。

  • 低耗:休眠时几乎不占用 CPU。

  • 精准:延迟消息通过 epoll_wait 的超时参数实现定时唤醒

总结:

整个Handler中使用到的Message复用过程:

Android消息机制之Message解析(面试) - 简书

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值