美文网首页Android进阶Android程序猿Android开发经验谈
Handler通信 - 源码分析和手写Handler框架

Handler通信 - 源码分析和手写Handler框架

作者: 红橙Darren | 来源:发表于2017-09-12 21:06 被阅读2765次

    记得第一次接触 handler 是用来更新 UI,在线程中用 handler.sendMessage(message),只知道这么做就能在线程中更新 UI 了。第二次接触是为了面试,当时反正也看不懂源码,就在网上找各种资料背它一背,笔试还好,当着面试官说的时候往往不知道怎么说。第三次接触是因为公司要引入人才,发现大家功夫还不错,经常就问问 MessageQueue 为什么要采用链表的方式,ThreadLocal 是怎么保证线程安全的,Activity 的生命周期为什么用 Handler 发送执行等等。

    有的时候还真不是为难别人,而是怕别人上班的时候吃不消。总之一个字 “变”, 岗位会 “变” ,项目会不断的“变”,新知识也会不断的“变”。但是基本的程序设计理论和算法不会变,如果你接触过 iOS 其实发现很多是相通的,尤其 android 出了 Kotlin 而 iOS 也有 Swift。良好的编码习惯永远都不会变,强大的学习能力和旺盛的求知欲永远都不会变,积极乐观的心态永远都不会变。鸡汤就灌到这里,下面我们从源码的角度出发,加上手写,来稍微了解一下 handler ,不能说是彻底。

    1. MessageQueue 消息队列

    线程中更新 UI 的时候经常是调用 sendMessage() 和 sendMessageDelayed() 这样 ,我跟踪代码进入到 Handler 的 sendMessage() 方法:

    public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        // SystemClock.uptimeMillis() + delayMillis   当前时间加上延迟时间
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        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;
        // new Handler 源码的时候是 false 
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    最后我们发现调用的是 MessageQueue 的 enqueueMessage() 方法:

    boolean enqueueMessage(Message msg, long when) {
            // 判断有没有 target 
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            // 有没有在使用 
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            // 对当前消息队列加锁。
            synchronized (this) {
                // 判断消息队列是否弃用(通常因为线程已死)
                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;
                // 第一次添加数据到队列中,或者当前 msg 的时间小于 mMessages 的时间
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    // 把当前 msg 添加到链表的第一个
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    // 不是第一次添加数据,并且 msg 的时间 大于 mMessages(头指针) 的时间
                    // 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 插入到列表中
                    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;
        }
    

    上面的这些代码,我们始终没有看到 Handler 调用 handleMessage() 方法,但是有了一个重要的结论,我们每发送一个消息都被保存到了 MessageQueue 消息队列中,消息队列中采用的是单链表的方式。上面一看就能看出来,如果不是特别了解这种方式,可以看下我之前写的 Android图片压缩加密上传 - JPEG压缩算法解析。也就是说我如果用下面这段代码发送消息,在 MessageQueue 中应该是如图所示。

    // 发送 Message1
    Message message1 = new Message();
    mHandler.sendMessageDelayed(message1, 500);
    
    // 发送 Message2
    Message message2 = new Message();
    mHandler.sendMessage(message2);
    
    // 发送 Message3
    Message message3 = new Message();
    mHandler.sendMessageDelayed(message3, 1000);
    
    2. Loop 消息循环

    我们始终没有看到 Handler 调用 handleMessage() 方法,到底什么时候会执行这个方法。待会解释,先看一种现象,有时候我们像下面这么写会报错:

    new Thread(){
        @Override
        public void run() {
            Handler handler = new Handler();
        }
    }.start();
    

    必须要下面这样写才正常:

    new Thread(){
        @Override
        public void run() {
            Looper.prepare();
            Handler handler = new Handler();
            Looper.loop();
        }
    }.start();
    

    我们在 Activity 中从来都没有调用过 Looper.prepare(); 这行代码,为什么就从来不报错呢?我想你可能要去了解一下应用的启动流程,或者看下我之前写的 Android插件化架构 - Activity的启动流程分析 ,这里我直接把 main 方法的代码贴出来:

    public static void main(String[] args) {
        // ... 省略部分代码
        
        Looper.prepareMainLooper();
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
    
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
    
        Looper.loop();
    
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    其实不是不报错,而是在 ActivityThread 的 main() 方法中系统早就帮我们写好了,接下来我们岂不是只需要知道 Looper.prepareMainLooper() 和 Looper.loop() 这两行源码岂不就好了。先来看下 Looper.prepareMainLooper():

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    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));
    }
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    这些代码相对就比较简单了,主要还是 ThreadLocal 的 set 方法,用来保证一个线程只有一个 Looper 对象,这样就保证了线程的安全。接下来看一下 Looper.loop() 这行:

    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;
            }
    
            try {
                // 通过 target 去 dispatchMessage 而 target 就是绑定的 Handler
                msg.target.dispatchMessage(msg);
            } finally {
                // 消息回收循环利用
                msg.recycleUnchecked();
            }
        }
    }
    

    看到这里我们总算清楚了,Looper.prepareMainLooper() 创建了一个 Looper 对象,而且保证一个线程只有一个 Looper;Looper.loop() 里面是一个死循环,不断的从 消息队列 MessageQueue 中取消息,然后通过 Handler 执行。很多细节代码我就不做过多的讲解,相信学习能力强的肯定能自己去看,如果不太明白的我只能通过视频直播的方式给你讲解然后手写。不要小看了这些细节,有的时候我们可以把部分代码 copy 出来用到我们自己的项目中。下篇文章正式进入到设计模式的讲解,我将 copy 部分代码用到自己的架构中。下面我用一个 Java 工程,自己手写一下,我们对整个系统的源码会更加深刻,这里写几个简单的类来验证一下:

    整体目录.png 子线程更新UI.png Handler发送消息更新UI.png

    所有分享大纲:Android进阶之旅 - 系统架构篇

    视频讲解地址:http://pan.baidu.com/s/1i5f5AWt

    相关文章

      网友评论

      • Surko:该文章上方的MessageQueue流程错了,我看了你的第一集视频,视频上讲完后是msg1.next--->msg3.next--->msg2.next--->null,而且我根据源码自己看了一下确实是这样,但是上面MessageQueue图上是msg3.next--->msg1.next--->msg2.next--->null,而且对4楼也产生了误解
        咸鱼正翻身:上图现在是2-1-3-null,这应该是没问题的。Message链表的排序除了null以外就是按when去排列。when小的在链的前面,这很正常并且when越小说明先被取出执行。(when是当前系统时间+延迟时间)。上述的例子,3一定是when最大,其次是1,when最小的是2。
      • 无名指666:请教,看了视频,有个地方不是很明白,就是虽然message3.next=message1,messageq.next=message2,那messagequeue又是如何吧这些message进行链表是保存的呢??,我看了全局的变量mMessages,也没看明白哪里是queue对message进行操作,还是说像上面那样有prev,有next就已经处理过了????,望解答,谢谢了
      • c47635154c67:猛的,做着java后台却来看安卓:joy:
      • etrnel: private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
        msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
        }

        我的咋是这样子的啊,下面的return那的enqueueMessage方法是红的。点不进去:joy:
        etrnel::joy: :joy: 点不进去。要自己去MessageQueue里面搜这个方法

      本文标题:Handler通信 - 源码分析和手写Handler框架

      本文链接:https://www.haomeiwen.com/subject/dnxhsxtx.html