4-Handler消息机制源码分析

    Handler是Android提供的一种异步回调机制,主要实现消息的线程间通信,那么问题来了,为什么需要Handler?主要是2个原因,1是为了避免耗时操作阻塞主线程(包括网络请求)造成UI卡顿或者ANR,Android系统要求用户把这些个操作放到子线程中,2是Android同时又要求在子线程不能更新UI(估计是基于线程安全问题),这就需要一种机制,在子线程中把消息传递到主线程,于是就有了Handler。

    要实现异步回调,单单有Handler是不够的,Android还提供了Looper,Message,MessageQueue,这些类共同实现了异步回调功能。通常情况,我们在Activity直接实例化一个Handler对象mHandler,在子线程中通过mHandler发送消息,然后在实例化Handler时传入的回调方法中接收处理方法,此时消息已经到了主线程上。所以,我们从实例化Handler开始进行深入分析。

1,开始实例化Handler

1,在主线程实例化Handler
private Handler mHandler=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            return false;
        }
    });

2,进入Handler内部实现,到了最终的构造方法中

   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;//取出Looper中的消息队列
        mCallback = callback;   //后面处理消息的回调
        mAsynchronous = async;
    }

3,取出保存好的Looper的过程
   public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

4,ThreadLocal是什么,起的作用是什么?具体的看下ThreadLocal中set/get()的实现
ThreadLocal.java

  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    public void set(T value) {
        Thread t = Thread.currentThread();//当前线程
        ThreadLocalMap map = getMap(t);//得到线程中的一个变量,作用类似于HashMap,可以通过k-v保存数据
        if (map != null)
            map.set(this, value);       //map以ThreadLocal为key,以Looper为value
        else
            createMap(t, value);        //创建一个以ThreadLocal为key,以Looper为value的map给thread
    }

   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//取出map中的value,也就是looper
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

所以,Looper中的sThreadLocal明着像是一个保存looper的容器,但实际却是自己为key,looper为value,保存在线程Thread中的map中

2,Handler的实例化后,我们看到内部把Looper,MessageQueue取出来,那就有必要知道这些是如何产生的

1,在主线程中取出来的,那肯定是在主线程实例的,而且是在主线程(ActivityThread)刚启动的时候

ActivityThread.java
   public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();//重点1
        ...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);//这里之前详细分析过了,主要为了application/activity的创建和启动
        ...
        Looper.loop();//重点2

        throw new RuntimeException("Main thread loop unexpectedly exited");//抛异常, 主线程的loop出乎意料的退出了
    }

2,重点1,Looper是如何产生的?
    Looper.java
   public static void prepareMainLooper() {
        prepare(false);//关键语句,实例一个Looper,并保存
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//实例化的Looper赋给sMainLooper
        }
    }   

  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));//通过threadLocad保存new出来的Looper
    }

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//取出来
    }

3,重点2 Looper是如何工作的?
     public static void loop() {
        final Looper me = myLooper();
        ...        
        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);
        ...
        }
    }


简化以后就很明了了,loop()中取出之前实例好的looper,并取出looper中的消息队列queue,开启死循环,不停的执行消息队列取消息操作,
message.target是私有变量handler

Handler.java

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {//一般常见于使用handler.postXX(Runnable)发送的消息
            handleCallback(msg);
        } else {
            if (mCallback != null) {这是之前实例化Handler传进去的回调监听类
                if (mCallback.handleMessage(msg)) {//回调类的处理方法,实际在activity上,这样就把消息队列的消息传到主线程上了
                    return;
                }
            }
            handleMessage(msg);//如果实例化Handler未传入回调类,则必须重新Handler本身的HandleMessage()方法,就是这个方法,同样的,把消息传到主线程上了
        }
    }

小结:通过查看Looper的实例化和Looper.loop()的调用,我们清楚的看到了消息的处理流程,
loop()依次从messageQueue中取出message,然后从message中取出handler去分配处理消息。

3,现在知道消息message是从消息队列messagequeue中取出来,那么必定有把消息放到消息队列的的过程

1,通过handler发送message的方法有2类,一种是send->Message系列,一种是post->Runnable系统

方法中带Message参数    
sendMessage()//常用
sendMessageDelayed()//延迟接收的消息
sendMessageAtTime()//准确时间接收的消息
sendMessageAtFrontQueue()//见名知义,放到消息队列最前面,也就是马上执行的消息
sendEmptyMessage()//空消息
sendEmptyMessageAtTime()
sendEmptyMessageDelayed()

方法中带Runnable参数,通过getPostMessage()生成Message,runnable参数传入到message中(处理消息时使用此回调)
post()
postDelayed()
postAtTime()
postAtFrontOfQueue()

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

上面所有发送消息的方法,最终都是通过sendMessageAtTime(Message msg, long uptimeMillis)来发送的(方法千千万,最终汇总),uptimeMillis为处理消息的时间    


 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;//这是Looper中的消息队列
        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(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;//this就是当前的handler,处理消息时就是用的msg中的target
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);//最终,handler把消息送到了消息队列中,然后消息队列进行入队操作
    }

4,击鼓传花,消息终于要传到消息队列去了

0,在消息message进入消息队列messagequeue之前,我们需要对Message和MessageQueue的基本结构有初步的了解,方便了解入队的整个过程
Message:有个when字段,表示当前消息处理的时间,同样的,message关联指向了另一个message,字段名为next,所以,整个Message可以组成一个链表结构
MessageQueue:消息队列管理保存着消息,因为Message可以组成一个链表结构,所以MessageQueue只需要持有一个Message对象就能完成对Message的保存和管理,
因为消息队列有处理时间的要求,所以消息队列持有的头消息必定是最早处理的(when最小),所以在入队的过程就必然需要有对时间的比较。

1,enqueue的就是入队的意思
boolean enqueueMessage(Message msg, long when) {
        ...

        synchronized (this) {
           ...

            msg.markInUse();
            msg.when = 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;                            //头消息变成msg的next消息,这样就当前消息变成了头消息,原先的头消息变成第二个消息对象
                mMessages = msg;                        //msg赋值给消息对象持有的头消息
                needWake = mBlocked;                    //不需要唤醒,这个变量后面再讨论
            } else {                                    //下面的操作是msg要插入到消息链表中间去

                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;//临时变量
                for (;;) {//死循环遍历消息链表,查找msg需要插入的位置
                    prev = p;//当前查找的消息放到prev上,第一次当然是消息队列持有的头消息
                    p = p.next;//取出下一个消息为p   此时 prev->p
                    if (p == null || when < p.when) {//如果下一个消息为空(到链表尾巴上了) 或者时间戳小于下一个消息的时间戳 则位置找到了
                        break;//跳出死循环,位置找到了,msg要插在p前面,此时prev->p,执行插入后期望的顺序为 prev->msg->p
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;   //
                prev.next = msg;//这2步实现的功能就是链表顺序变为 prev->msg->p 此时头消息还是mMessage,但msg已经到它的链表上了
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);       //有消息来了,底层唤醒Looper
            }
        }
        return true;
    }

2,这样就很明了了,发送的消息经过比较和插入,终于进入到消息链表中了,结合前面的Looper.loop(),这样,整个流程就清楚了。

总结:大体流程是这样的,在主线程创建的起始阶段(ActivithThread.man()),会创建Looper的同时,Looper构造方法中创建一个消息队列MessageQueue,在main()的后面,调用Looper.loop(),loop()会调用Looper中实例化的Messagequeue,源源不断地通过queue.next()获取Message,然后通过message设置的target(也就是Handler)来调度处理消息,而我们也可以通过实例化好的Handler的一系列send/post的方法,将消息发送到消息队列中。举个现实中栗子:

只要地铁站运行正常,那么过安检的机器必定需要开启运行,地铁站可以类比成ActivityThread,安检机器为Looper,安检设备持续不断的运行(调用Looper.loop()),安检设备有个传输带(MessgaQueue),每一个行李(Message)都必须放到传送带上,放的过程是handler的send/post(),行李经过检测没问题后,可以从另一端取走(handler.dispatchMessage(message)),如果handler.send/post()操作是在另一个线程中执行的,等到消息取出来被调度处理时,已经是在主线程了,这就实现了异步回调。

 

Handler工作原理图大体如图

这样,Handler的整个工作流程就梳理清楚了。

当然,说到死循环,为什么looper.loop()不会因为死循环造成线程阻塞?

答案很简单,整个App因为主线程中loop.looper()的死循环而保持活动,如果跳出死循环,整个App会抛异常,
ActivityThread.main(){
...
    Looper.loop();//重点2
   throw new RuntimeException("Main thread loop unexpectedly exited");//抛异常, 主线程的loop出乎意料的退出了 这句话正常情况下不会被执行
}
而且,造成主线程堵塞是因为对Message处理的超时造成的,以之前举例说明,安检设备因为传输带不断循环而保持正常工作,一旦行李过重,用户从传送带上搬离行李过慢才会造成后续行李的卡住(各类事件不被处理,造成阻塞)。
同样的,在消息队列中没有消息时,主线程会进入休眠状态,即消息队列执行next()方式时,会根据下一个消息还有多久执行系统底层方法nativePollOnce()释放资源进入休眠状态
        MessageQueue.next(){
             for (;;) {
                if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);//直到下条消息到达或者有事务发生,
        }

而一旦有最新消息事件进入到消息队列时,一旦新事件需要马上执行或者到了执行时间的时间,会通过系统底层方法nativeWake()唤醒主线程进行工作
        MessageQueue.enqueueMessage(){

        ...
          if (needWake) {
                nativeWake(mPtr);//通过往 pipe 管道写端写入数据来唤醒主线程工作,这里采用的 epoll 机制
            }
        }

主线程在没有任务时执行的睡眠机制很大限度的优化了性能,同样的,在发送消息时,使用obtain比直接实例更有效率。

1,Message.java

  /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     *从全局池中返回一个新消息实例
     */
 public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;//Message类内部也有一个消息头,相当于也有一个消息链表 头消息置于m
                sPool = m.next;     //下一个消息置为表头 
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;//
                return m;//返回当初的表头消息
            }
        }
        return new Message();
    }

这样的话也不用每次都实例消息,最大限度的提高效率。
总结:Handler作为消息回调机制因为涉及的关联类比较少,整体结构是相对比较清晰的,但内部的逻辑还是很有学习价值的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr_Sun_01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值