系统源码解析——Handler

本文深入解析Android中的Handler机制,从Looper、MessageQueue到Handler的工作原理,详细阐述了异步通讯的实现过程,以及消息的发送、接收和处理机制。

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

解析背景

常见于异步线程持有主线程 handler 对象,借助于 handler 发送 message 回调其 dispatchMessage 方法完成异步通讯 。 再者,Android 源码中大量使用 handler 用于 UI 线程间/线程内通讯。比如 Messener,ViewRootImpl 等等。最近在整理以前源码笔记, 就把整个源码过程都分享下 。

源码解析

涉及核心类

  • Looper 类, 负责在 messageQueue 中读取 message 并调用对应 target (handler) 的处理方法; 一个线程对应一个 looper,同时初始化 messageQueue
  • MessageQueue 类,单向链表
  • Handler 类,持有 looper 对象,可存在多个 handler 对象对应一个 looper 和一个messageQueue
  • Message 类,持有 target(handler)和 callback

解析思路


先 “剧透” 一些剧情, 对整个 Handler 体系有很大的帮助: “每一个线程有且仅有一个 looper 用于轮询该线程需要处理的消息, 所以也只有一个 messageQueue 用于存放消息。 消息可由不同的发送者进行发送, 所以允许有多个 handler 发送 message 对象到 messageQueue 中,但是取出的消息要怎么处理呢?就需要 message 得持有 handler ,已便取出 message 之后可以获取 handler 进行分发处理。
所以,按照下面源码阅读有助于理解。
Looper -> MessageQueue -> Handler -> Message

Looper


主要作用是:在线程中初始化 looper 对象和 messageQueue 对象, 并且保证一个线程有且仅有一个 looper 对象; 轮询读取 messageQueue 消息。

Android 默认提供了 sMainLooper - UI 线程 looper 对象方便任何地方获取。

值得留意:在 Android 8.0 源码中 prepareMainLooper 方法会在 ActivityThread#main 和 SystemServer#run 方法中被调用, 这也说明了 Android UI 线程为何不需要我们主动初始化 looper 的原因 。

//私有静态变量
private static Looper sMainLooper;

//初始化主线程 looper
public static void prepareMainLooper() {
    //核心初始化
	prepare(false);
	synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //获取当前线程 looper 
        sMainLooper = myLooper();
        }
}

//获取主线程 looper
public static Looper getMainLooper() {
    synchronized (Looper.class) {
    	return sMainLooper;
    }
}

上述代码中,prepare 方法为初始化 looper 对象的入口。

>Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//所有 prepare 最终调用该方法
private static void prepare(boolean quitAllowed) {
	if (sThreadLocal.get() != null) {
		throw new RuntimeException("Only one Looper may be created per thread");
	}
    //根据当前线程设置对应的Looper
	sThreadLocal.set(new Looper(quitAllowed));
}

//调用构造器
private Looper(boolean quitAllowed) {
	mQueue = new MessageQueue(quitAllowed);
	mThread = Thread.currentThread();
}

>ThreadLocal.java
    public void set(T value) {
        Thread t = Thread.currentThread();
        //获取映射,key:ThreadLocal value:Looper
        ThreadLocalMap map = getMap(t);
        //存在映射,则设置新 Looper 作为value;否则新建映射
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

sThreadLocal 内部实现为每个线程保留特定的信息, 这里即为每个线程保留 looper 对象。所以如果已经初始化过 looper 对象,则会异常。
了解的如果初始化之后,剩余的就是如果轮训消息。

//略去不影响主干的部分代码,包括一些判空行为
public static void loop() {
    final Looper me = myLooper();
    //获取消息队列
    final MessageQueue queue = me.mQueue;
    for (;;) {
        //取出消息队列中下一个要处理的消息
        Message msg = queue.next(); // might block
        //target 就是 Handler 对象,处理消息
        msg.target.dispatchMessage(msg);
        //回收可能正在使用的消息
        msg.recycleUnchecked();
    }
}

上述的流程精简之后是否干净,就是在代码调用线程获取当前线程 looper 对象, 取出 messageQueue 读取 message 消息, 并调用 Handler#dispatchMessage 进行消息分发 。

值得留意: 既然在 UI 线程循环,那是否意味着会发生阻塞呢? 是的 !但是 UI 线程是 Android 应用的基础,该线程通过 Looper#loop 不断接受事件和分发处理事件, 包括每一个点击, Activity的每个生命周期下的行为等, 如果一旦 UI 线程停止了, 应用也就挂了。 所以只会存在 某一个消息的分发处理会阻塞该循环, 这就意味着不能有耗时操作了。

(主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。)

MessageQueue


既然知道该类是用于存放消息对象, 核心功能在于存取。 整个类有 900+ 行,有部分 native 方法。 这里只看核心的两个方法。

  • next 读取消息
  • enqueueMessage 存放消息

Message next() {
	//native 判断,如果 loop 被停止。
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    
    //这两个变量对理解整个方法有很大的帮助。 
    //pendingIdleHandlerCount 用于计算 通知 “队列情况” 时 所调用的对象个数,比如我们可以注册 IdleHandler 用于接收 “looper 处理完所有 message” 这个行为的通知。
    //(PS:简单来说,就是你注册几个监听,pendingIdleHandlerCount=监听器数量)
    //发现在 Activity.onCreate 之前,Instrumentation#prePerformCreate 方法会添加监听
    //nextPollTimeoutMillis 表明要多久之后再次查询队列,用于当查询到的异步 message 还到时间点处理时记录下改时间。
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;

    for (;;) {
        synchronized (this) {
            // 获取开机到现在的时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
          	//如果遇到消息屏障,则会跳过同步 message,循环找出第一个异步消息
            if (msg != null && msg.target == null) {
                do {
                    //prevMsg=null 或者 prevMsg=同步message
                    //msg=null 或者 msg=异步message
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            //msg 不为null
            if (msg != null) {
                if (now < msg.when) {
					//消息还没有准备好,意味着该 message 还没有到时间点进行处理
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                	//获取 message 
                    mBlocked = false;
                    if (prevMsg != null) {
                        //因为msg要被处理,所以prevMsg的next指向msg的next,保证链表连接
                        //此时mMessages 还是队列头部消息,等待消息屏障没了再执行
                        prevMsg.next = msg.next;
                    } else {
                        //msg 要被取出执行,重新设置队列头部消息 mMessages=msg.next              
                        mMessages = msg.next;
                    }
                    //将next置空
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
          		//没有更多消息,设置 -1 
                nextPollTimeoutMillis = -1;
            }

            //终止处理
            if (mQuitting) {
               	//dispose(),这里我把调用的方法放进来,等价于下面三句代码
                if (mPtr != 0) {
            		nativeDestroy(mPtr);
            		mPtr = 0;
        		}
                return null;
            }

            //如果是首次进入,则获取 IdleHanlders 进行回调
            //IdleHandlers 当且仅当队列为空或者队列头部消息被延迟处理时会被调用。
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            //如果没有IdleHandlers 需要回调,则阻塞
            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);
        }

 		//调用 IdleHandler#queueIdle,其作用只是通知该队列的情况
        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 {
            	//如果外部返回 flase ,意味着不再继续监听队列状态
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                	//移除回调集
                    mIdleHandlers.remove(idler);
                }
            }
        }
        //重置
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

next 方法主要需要理解以下几点
值得注意的是: 在整个流程中有一个注释为 “遇到消息屏障”(即对应 message.target 是为null ),会循环找出第一个异步消息 ,所有的同步消息将在本次遍历中都会被忽略,直到下次没有消息屏障的时候才取出运行。

  • 如果没有异步消息,则直接阻塞
  • 如果有异步消息需要处理,先判断时间是否到了,如果没有则需要设置阻塞时间
  • 否则把消息返回被设置 mBlocked = false 表示没有阻塞。

那什么时候会出现消息屏障呢 ?

//这里会插入一个消息屏障,在这个屏障之后的所有同步消息都不会被执行,即使时间已经到了也不会执行。
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;
    }
}
//用于移除对应的屏障
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);
        }
    }
}

那消息屏障的运用有哪些呢 ? 在系统源码 ViewRootImpl 中可见。

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //插入消息屏障,此时由于没有异步消息,所以会堵塞
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //发送异步消息任务,上面设置消息屏障的原因,就是为了在处理系统更新视图 UI 时跳过当前队列中所有的同步消息,保证更新任务时最高优先级(16ms刷新一次UI)
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //解除消息屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
//移除消息屏障        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

就是为了在处理系统更新视图 UI 时跳过当前队列中所有的同步消息,保证更新任务时最高优先级。(16ms刷新一次UI)
接下来看如何插入消息

//handler.sendMessage()内部调用了MessageQueue.enqueueMessage()
//插入消息
boolean enqueueMessage(Message msg, long when) {
    
    synchronized (this) {
    	//如果已经暂停,则回收消息
        if (mQuitting) {
        	msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;	//以开机时间到此刻的时长尺子,小于该值则立刻执行,大于则延迟
        //队列头部消息赋值给p
        Message p = mMessages; 
        //是否需要唤醒队列
        boolean needWake;
        //如果当前队列为空或者when小于列表头节点when或者when为0,则直接插入头部,唤醒队列
        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 {
            //按照 when 从小到大规则在当中插入msg消息
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                //当p==null 或者 p的执行时间 大于 当前传入的消息执行的时间(PS:即p要在msg的后面执行)结束for循环
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            //当前传入的消息的next指向p,因为p的when大于传入的when,即p在msg的后面执行
            msg.next = p; // invariant: p == prev.next
            //prev的next指向msg,因为prev的when小于传入的when,即prev要在msg之前执行
            prev.next = msg;
        }

        // 唤起队列,mPtr != 0,mQuitting 为 false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

enqueueMessage 插入代码指定了一个 message 按照 when 规则插入消息队列。

  • 如果当前队列为空或者when为0(立刻执行)或者when比头部链表节点when小,则插在头部
  • 其他场景下按照 when 从小到大规则查到相邻节点(when比他大),插在它前面。

Message


如果仔细看了前两节,相信对整个存取过程会有清晰的理解。
接下来看一下在 messageQueue 中存储的消息 message 是怎样的存在。

//支持跨进程层/intent传递
public final class Message implements Parcelable {
    public int what;				//message标示,可理解id
    long when;						//何时被取出处理
    Handler target;					//发送者
    Message next;					//下一节点
    int flags;				 		//标示 message 的使用状况
    
    public int arg1;				//数据传输携带的辅助数据
    public int arg2;
    public Object obj;
    
    public Messenger replyTo;						//跨进程通讯使用
    public int sendingUid = -1;
    
    private static final int MAX_POOL_SIZE = 50;	//缓存相关
    private static int sPoolSize = 0;
    private static final Object sPoolSync = new Object();
    private static Message sPool
    
    static final int FLAG_IN_USE = 1 << 0;		
	static final int FLAG_ASYNCHRONOUS = 1 << 1;
	static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
	
	//空构造器,通过静态方法获取
	public Message() {
    }
}

上述是绝大部分成员变量,主要涉及定义 message 的状态及获取回收 message 时缓存的处理。

//所有获取方法最后的入口
public static Message obtain() {
	//线程安全
    synchronized (sPoolSync) {
        if (sPool != null) {
            //将缓存的链表头部消息返回
            Message m = sPool;
            //重新指向原头部消息的next
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可见缓存池实际上是一个 message 链表,sPool 为链表头部。

void recycleUnchecked() {
    flags = FLAG_IN_USE;		//回收的时候设置 FLAG_IN_USEs
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
    	//如果缓存池没有满,则缓存 *message* 对象
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

获取和回收流程比较简单, 看这里细心的读者可能有个疑惑 “那 message 的 flags 何时才会设置 FLAG_ASYNCHRONOUS” 呢 ?

int flags;
static final int FLAG_ASYNCHRONOUS = 1 << 1;
public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

这里的同步异步可能和我们理解线程同异步有所偏差。
实际上无论是同步消息还是异步消息,都是放到 messageQueue 中按照 when 规则进行获取分发 。但是当有一些 “紧急事务” 需要在队列中优先被执行,则可以通过 “同步屏障” 来过滤掉原来本在排队执行的同步消息,直接运行最接近运行时间的异步任务 。上述 ViewRootImpl 绘制就是一个很好的例子 

Handler

final Looper mLooper;		
final MessageQueue mQueue;		//消息队列,mLooper一一对应
final Callback mCallback;		//callback 接口
final boolean mAsynchronous;	//是否是异步消息

//不指定 looper 的构造器
public Handler(Callback callback, boolean async) {
	//默认获取当前线程 looper 对象
    mLooper = Looper.myLooper();
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

//指定 looper 的构造器
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;		//如果没有设置,则默认为 null
    mAsynchronous = async;		//如果没有设置,则默认为 false
}

上述指明了核心的成员变量及两种构造器 。
在 Looper 的 loop 方法可知, 通过 MessageQueue 的 next 方法获取异步消息之后调用 Handler#dispatchMessage 分发消息。

public void dispatchMessage(Message msg) {
	//如果存在 message.callback 不为空则调用 handleCallback
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
    	//如果设置了 Callback 对象,则使用 Callback 处理
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //否则默认使用 handleMessage处理
        handleMessage(msg);
    }
}
//mCallback是一个内部定义的接口
//也就是说如果有已知 handler ,可以通过设置 Callback 对象拦截并实现自己的逻辑。
public interface Callback {
    public boolean handleMessage(Message msg);
}

//handleMessage 是一个空方法,默认子类覆盖处理自己的分发逻辑
public void handleMessage(Message msg) {
}

//handleCallback 直接调用 callback#run 方法
private static void handleCallback(Message message) {
    message.callback.run();
}

其内部允许设置 mCallback 拦截 handler 的处理逻辑,另外如果一个 message 携带有 callback ,则直接调用 Callback#run 完成分发处理 。
上述代码为处理消息,以下代码为发送消息。

//发送代码会调用
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    //这里延迟处理使用的是开机到现在的时间长度加上我们需要延迟的毫秒数
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//再调用
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    return enqueueMessage(queue, msg, uptimeMillis);
}
//最终调用
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	//设置 target 为发送者
    msg.target = this;
    //设置发送的消息是否是异步消息
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

也就是说,handler 决定了发送的 message 是否为异步消息 。
至此,整个 Handler 体系解析完毕 。

如果错误,请不吝指出 !

参考文献

你知道android的MessageQueue.IdleHandler吗?

出处: yummyLau原文链接(http://yummylau.com/2018/09/26/源码解析_2018-09-26_Handler/)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值