• Handler-源码分析


    1.简介

    Handler 是一套 Android 消息传递机制,主要用于线程间通信。

    为什要有Handler机制?

    解决在子线程更新UI的问题

    由于在Android机制中,为了保证UI操作是线程安全的,规定只允许在原始线程更新UI,但在实际开发中存在多个线程并发操作UI组件的情况,会导致线程不安全,所以采用Handler机制,当子线程需要操作UI组件时,通过Handler通知主线程,从而在主线程中更新UI。

    2.介绍

    Hnadler机制重要组成部分

    • Meesgae:需要传递的消息,可以传递数据及对象;
    • MessageQueue:消息队列,但是它的内部实现并不是用的队列,而是通过单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能是向消息池投递消息(enqueueMessage)和和取走消息池的消息(next)
    • Handler:消息的真正处理者,主要功能是向消息队列发送消息(sendMessage)和处理对应的消息(handleMessage)
    • Looper:消息轮训器,重要功能是,不断的从消息队列中取出消息交与Handler处理

    Handler流程介绍

    发送消息 --> 添加消息到队列 --> 从队列中获取消息 --> 处理消息
    
    • 1

    由Handler发送Messgae开始,将Message发送到MessageQueue中,由Looper不断的轮训去除MessageQueue中的Message,交给Handler处理 ,大致流程如下

    WechatIMG36.png

    源码解读

    发送消息的方式主要有sendXXX()postXXX()两种,

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    handler.sendXXX()handler.postXXX()最终都会调用到sendMessageAtTime()方法。

    public boolean sendMessageAtTime(@NonNull 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后调用 enqueueMessage()方法。

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里将Hanlder本身赋值给Messgaetarget,让后调用MessageQueueenqueueMessage(),将Message加入到MessageQueue的队列当中,接下来就到MessageQueue的enqueueMessage()方法当中去看。

    boolean enqueueMessage(Message msg, long when) {
        ......
        
        synchronized (this) {
           ......
    
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 当消息队列为空或者将要入队的消息(msg)的时间(when)在所有消息队列的消息最前面,则把msg插入到队头,最先执行
                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) {
                    // 当p为空(遍历到最后一个了),或者新消息的时间在p的时间之前,就不用再遍历了,因为可以确定新消息要插在消息p前面了
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // break跳出for循环后执行到这里,将msg插入到p前面,prev后面
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
    
            ......
        }
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    Message类的实现可以看出它是个单链表,消息队列中的消息都是按照时间先后顺序链接起来的。

    接下来就详细的分析一下上述的代码,首先我们看一下这个if (p == null || when == 0 || when < p.when) 判断:

    • p == null:我们可以从上面看到p 就是 mMessages;而mMessages 是在我们if判断里面赋值的,所以我们第一次进入时,p也就是mMessages为空,表示MessageQueue中没有添加过消息。
    • when == 0 :when是我们从Hanlder当中传过来的值,表示当前时间SystemClock.uptimeMillis()加上延迟时间delayMillis
    • when < p.when:判断新Messgae.when是否小于p.when

    以上三个判断条件满足一个就会进入if代码块中,将新消息放在p消息的前面,当满足p==null时,即为队头,就是以下情况:

    WechatIMG39.png 当满足p!=nullwhen == 0 || when < p.whentrue时,就是以下这种情况:

    WechatIMG42.png 当以上三个条件不满足时,就会执行else的代码块,先来看一下下面这段代码的意思:

     Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    ......
                }
                // break跳出for循环后执行到这里,将msg插入到p前面,prev后面
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    第一行代码中的P消息上在if代码块中添加的消息,说明指针指向P消息,第二行p消息指向下一个消息,指针小猴移动了一位。 从第二行代码可以看出,再次添加消息时,遍历消息都是从添加if代码块中的消息开始的,因为从if代码块中添加的消息要么就是第一个消息,要么就是when小于p消息的wnen的,也就是说可确保前面的消息都是时间有序递增的。而系统的时间是一直在增加的,所以enqueueMessage()方法的第二个参数when一直大于mMessageswhen,所以要把指针向后移一位,从后面一个消息开始比对。

    WechatIMG40.png 以上两种都是在队头队尾添加的,再看一个从插入到队列中间的情况:

    image.png 总结

    • 添加第一个消息时,将消息添加到消息队列的队头
    • 再次添加消息时,先判断待添加消息的whenp.when的大小关系,小于p.when,则把消息添加到p的前面,否则循环消息队列找出合适的插入位置
    • 如果遍历完整个消息队列都没有满足条件的位置,则把新消息插入到队尾
    • 消息队列的消息按照时间从先到后排序
    取出消息

    文章开始已经说了Looper是消息轮询器,Looper.loop**不断的从MessageQueue中取出Message,那么Looper.loop()是在哪里调用的呢?由于Android是由消息驱动的,一定是在app的入口ActivityThreadmain()方法中调用了,下面看下Looper.prepare()的创建和Looper.loop()轮训.

    Looper.prepare()

    对于app的入口ActivityThread调用的是Looper.prepareMainLooper()实际上也是调用了Looper.prepare()。下面一步步来看一下:

    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));
    }
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    可以看到在Looper.prepareMainLooper()中调用了Looper.prepare(),在Looper.prepare()中首先判断当前ThreadLocal中是否已经存在Lopper对象,如果存在则抛出异常,一个线程对应一个一个Looer对象。如果不存在Looper对象则 new一个出来,在Looper的构造方法中创建了MessageQueue

    ThreadLocal保证了一个线程只有一个Looper对象,一个Looper对象只有一个MessageQueue。看一下ThreadLocal是如何做到的。回头看下sThreadLocal.get()方法。

    public T get() {//这里的泛型指的是Looper
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //获取当前线程中的Looper
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果map==null||looper==null执行以下方法
        return setInitialValue();
    }
    private T setInitialValue() {
      //初始化value(Looper)当然为空,在后面ThreadLocal.set()方法会赋上具体的值
        T value = initialValue();
        //获取当前线程ThradLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //添加进map
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    protected T initialValue() {
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    首先获取当前线程,根据当前线程获取到ThreadLocalMap对象,ThreadLocalMapThreadLocal中的一个内部类,表明一个Thread只存在一个ThreadLocalMap,然后ThreadLocal.get()方法中判断ThreadLocalMap是否为空、ThreadLocalMap中是否存在Looper对象,如果存在则返回该Looper对象,如果ThreadLocalMap为空或者ThreadLocalMap中不存在Looper对象,则创建ThreadLocalMap,赋值一个初始的空对象,等待ThreadLocal.set()方法赋具体的值。

    Looper.loop()

    public static void loop() {
        //获取到Looper对象
        final Looper me = myLooper();
        
        ......
    
        me.mInLoop = true;
        //根据Looper对象获取MessageQueue
        final MessageQueue queue = me.mQueue; 
        
         ......
    
        for (;;) {
        // 从MessageQueue中取出消息,无消息时可能会阻塞
            Message msg = queue.next(); 
            
            ......
            
            try {
                //交与Handler处理消息
                msg.target.dispatchMessage(msg);
                ......
            } catch (Exception exception) {
               ......
                throw exception;
            } finally {
               ......
            }
           
             ......
           //回收消息
            msg.recycleUnchecked();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    首先获取Looper对象,根据Looper对象获取到MessageQueue队列,不断的调用next()方法来获取Message,交与Handler去处理,最后回收该消息。

    下面看一下MessageQueue.next()做了些什么

    // MessageQueue.java 中的 next 方法源码
    Message next() {
            // 判断 native 层的 MessageQueue 对象有没有正常创建
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
            // 消息执行需要等待的时间
            int nextPollTimeoutMillis = 0;
            for (;;) {
                // 执行 native 层的消息延迟等待,调 next 方法第一次不会进来
                nativePollOnce(ptr, nextPollTimeoutMillis);
                synchronized (this) {
                    // 获取当前系统的时间
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    ...
                    if (msg != null) {
                        if (now < msg.when) {
                            // 需要延迟, 计算延迟时间
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // 不需要延迟获取已经过了时间,立马返回
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            // 标记为已在使用状态
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // 如果队列里面没有消息,等待时间是 -1
                        nextPollTimeoutMillis = -1;
                    }
                    // 有没有空闲的 IdleHandler 需要执行,一般我们没关注这个功能
                    // 后面内容有专门解释,这里目前分析是 == 0 ,跳出
                    if (pendingIdleHandlerCount <= 0) {
                        mBlocked = true;
                        continue;
                    }
                    ...
                }
                pendingIdleHandlerCount = 0;
                nextPollTimeoutMillis = 0;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    通过源码分析我们发现消息的处理过程,是通过当前消息的执行时间与当前系统时间做比较,如果小于等于当前系统时间则立即返回执行该消息,如果大于当前系统时间则调用 nativePollOnce 方法去延迟等待被唤醒,当消息队列里面为空时则设置等待的时间为 -1。

    总结

    • 调用Looper.loop()之前必须调用Looper.prepare()
    • 消息队列中没有消息的时候会堵塞在next()方法处让CPU休眠,不会ANR
    • 取到了消息后要判断当前时间和消息执行直接的先后关系,没到执行时间则继续休眠等待,否则就直接处理消息
    • 处理消息前,会将该消息从消息队列中去除

    处理消息

    Looper.loop()中调用 msg.target.dispatchMessage(msg)处理消息, msg.targetHanlder,在enqueueMessage()中赋值的。

    下面看下dispatchMessage()

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    msg.callback是在Message.obtain()方法中赋值的

    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;
    
        return m;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果msg.callback不为空的话,就会执行handleCallback(msg)方法,就是去处理这个Runnable对象

    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    • 1
    • 2
    • 3

    如果msg.callback为空的话,就会判断Handler中的回调是否为空,这个callback对象是在Handler创建时选择参数,如果不为空的情况下,就会执行callback中的handleMessage()方法

    public Handler(@Nullable Callback callback, boolean async) {
         .......
        mCallback = callback;
       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    最后执行到我们常用的handleMessage(msg),这个方法就是一个空的模版方法,交由我们去实现具体的逻辑。

    public void handleMessage(@NonNull Message msg) {
    }
    
    • 1
    • 2

    至此Handler整个流程执行完毕。

    拓展

    Message创建

    Message创建的方法主要有两种

    • 通过构造方法new对象
    • 通过obtain()从池子里去Message对象
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过obtain()从池子里去Message对象,这里才用了享元设计模式,从池子里取出Message对象复用,避免了不必要的对象创建,减少了内存开销,题高了资源的利用率.因此个人认为使用这种方法创建对象比较好。

  • 相关阅读:
    PyTorch与torchvision、torchaudio、python版本对应关系
    python二次开发Solidworks:画砂轮
    使用 Webpack 从 0 到 1 构建 Vue3 项目 + ts
    解决:python调用偏移side找不到dll load failed问题
    【现代密码学原理】——哈希函数(学习笔记)
    短视频矩阵系统源码/源头搭建技术交付
    sql server 设置字段自增
    西门子CT重建算法
    【机器学习】详解回归(Regression)
    python爬虫大作业爬取豆豆影评
  • 原文地址:https://blog.csdn.net/m0_62167422/article/details/125492619