美文网首页
Handler消息机制知识汇总

Handler消息机制知识汇总

作者: 社会我鹏哥 | 来源:发表于2019-08-20 23:45 被阅读0次

    1、概述

    在 Android 中,只有主线程才能操作 UI,但是主线程不能进行耗时操作,否则会阻塞线程,产生 ANR 异常,所以常常把耗时操作放到其它子线程进行。如果在子线程中需要更新 UI,一般是通过 Handler 发送消息,主线程接受消息并且进行相应的逻辑处理。除了直接使用 Handler,还可以通过 View 的 post 方法以及 Activity 的 runOnUiThread 方法来更新 UI,它们内部也是利用了 Handler。

    很多人认为Handler的作用是更新UI,这的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景。具体来说是这样的:有时候需要在子线程中进行耗时的IO操作,可能是读取文件或者访问网络等, 当耗时操作完成以后可能需要在UI上做一些改变,由于Android开发规范的限制,我们并不能在子线程中访问UI控件,否则就会触发程序异常,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。因此,本质上说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。

    由于Android中的View是线程不安全的,然而程序中异步处理任务结束后更新UI元素也是必须的。这就造成了一个矛盾,最简单的解决方法肯定是给View加同步锁使其变成线程安全的。这样做不是不可以,只是会有两点坏处:

    1. 会让UI访问的逻辑变得复杂
    2. 会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

    基于以上两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只是通过Handler切换一下UI访问的执行线程即可。

    2、基本用法

    step1:创建Handler实例
    //1.自定义Handler类
    static class CustomHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            //更新UI等操作
        }
    }
    
    CustomHandler customHandler = new CustomHandler();
    
    //2.内部类
    Handler innerHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //更新UI等操作
        }
    };
    
    //3.callback方式
    Handler callbackHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            //更新UI等操作
            return true;
        }
    });
    
    step2:发送消息
    //1.发送普通消息
     Message msg = Message.obtain();
     msg.what = 0; //标识
     msg.obj = "这是消息体"; //消息内容
     innerHandler.sendMessage(msg);
    
     //2.发送Runnale消息
     innerHandler.post(new Runnable() {
         @Override
         public void run() {
             //更新UI等操作,消息接收后执行此方法
         }
     });
    

    3、源码解析

    3.1 Handler中存在四种角色

    • Handler
      Handler用来向Looper发送消息,调用sendMessage等多个方法,在Looper处理到对应的消息时,Handler在对消息进行具体的处理。上层关键API为handleMessage(),由子类自行实现处理逻辑。

    • Looper
      Looper运行在目标线程里,不断从消息队列MessageQueue读取消息,分配给Handler处理。Looper起到连接的作用,将来自不同渠道的消息,聚集在目标线程里处理。也因此Looper需要确保线程唯一。

    • MessageQueue
      存储消息对象Message,当Looper向MessageQueue获取消息,或Handler向其插入数据时,决定消息如何提取、如何存储。不仅如此,MessageQueue还维护与Native端的连接,也是解决Looper.loop() 阻塞问题的 Java 端的控制器。

    • Message
      Message包含具体的消息数据,在成员变量target中保存了用来发送此消息的Handler引用。因此在消息获得这行时机时,能知道具体由哪一个Handler处理。此外静态成员变量sPool,则维护了消息缓存池以复用。

    这几个类的作用还可以用下图解释:


    Handler机制

    3.2 Handler
    使用Handler之前,我们都是初始化一个实例,比如用于更新UI线程,我们会在声明的时候直接初始化,或者在onCreate中初始化Handler实例。所以我们首先看Handler的构造方法,看其如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)怎么发送到MessageQueue中的。

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback) {
        this(callback, false);
    }
    
    
    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur");
            }
        }
    
        mLooper = Looper.myLooper();  // ①
        if (mLooper == null) {  // ②
            throw new RuntimeException
            ("Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;  // ③
        mCallback = callback;
        mAsynchronous = async;
    }
    

    在①处通过Looper.myLooper()获取与创建Handler线程绑定的Looper,如果 mLooper 为空,那么会抛出异常:"Can't create handler inside thread that has not called Looper.prepare()",意思是:不能在未调用 Looper.prepare() 的线程创建 handler,然后在③处又获取与Looper绑定的MessageQueue,因为一个Looper只能有一个MessageQueue,也就是与当前线程绑定的MessageQueue,这样就保证了Handler的实例与我们Looper实例中MessageQueue关联上了。

    到此,Android 消息机制的三个重要角色全部出现了,分别是 HandlerLooper 以及 MessageQueue。 一般在代码我们接触比较多的是 Handler ,但 Looper 与 MessageQueue 却是 Handler 运行时不可或缺的。

    Handler发送消息最常用的方式就是sendMessage(),除此之外也可以通过post()、postAtTime()、postDelayed()等方法发送消息,这几个方法均会执行到sendMessageAtTime()方法。

    下面以最常用的sendMessage方法为例

    发送消息的方法

    public final boolean sendMessage(Message msg){
            return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    

    发送空消息的方法

    public final boolean sendEmptyMessage(int what){
            return sendEmptyMessageDelayed(what, 0);
    }
    
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
            Message msg = Message.obtain();
            msg.what = what;
            return sendMessageDelayed(msg, delayMillis);
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    

    二者最终都会调用 sendMessageAtTime方法

    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);
    }
    

    在sendMessageAtTime方法中,接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。

    可以看到在①处为MessageQueue赋值mQueue,这个值是在Handler的构造方法中通过Looper获取,在获取MessageQueue然后调用了enqueueMessage方法

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;  // ① 
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);  // ②
    }
    

    在该方法内的①处,首先为meg.target赋值为this,此处的this就是当前Handler对象本身。在这就指明了该msg的来源——它是由哪个Handler发出的,与此同时也指明了该msg的归宿——它该由哪个Handler处理。不难发现,哪个Handler发出了消息就由哪个Handler负责处理。

    在②处,将消息放入消息队列中,距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端。若调用sendMessageAtFrontOfQueue()方法发送消息它会直接调用该enqueueMessage(msg,uptimeMillis)让消息入队只不过时间为延迟时间为0,也就是说该消息会被插入到消息队列头部优先得到执行。

    接下来我们看MessageQueue中的enqueueMessage()方法,该方法就是消息入队操作了。这个MessageQueue是一个消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表。MessageQueue主要包含两个操作,插入和读取,分别对应enqueueMessage()方法和next()方法。
    MessageQueue是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。

    boolean enqueueMessage(Message msg, long when) {
            // msg 必须有target也就是必须有handler
            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();
                //when 表示这个消息执行的时间,队列是按照消息执行时间排序的
                //如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // 这里三种情况:
                    // 1、目标消息队列是空队列
                    // 2、插入的消息处理时间等于0
                    // 3、插入的消息处理时间小于保存在消息队列头的消息处理时间
                    // 这三种情况都插入列表头
                    msg.next = p;
                    mMessages = msg;
                    //需要唤醒主线程,如果队列没有元素,主线程会堵塞在管道的读端,这时
                    //候队列突然有消息了,就会往管道写入字符,唤醒主线程
                    needWake = mBlocked;
                } else {
                  // 这里则说明消息处理时间大于消息列表头的处理时间,因此需要找到合适的插入位置
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    //将消息放到队列的确切位置,队列是按照msg的when 排序的
                    for (;;) {
                        prev = p;
                        p = p.next;
                        // 到链表尾,或处理时间早于p的时间
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                         // 如果插入的消息在目标队列中间,是不需要检查改变线程唤醒状态的
                            needWake = false;
                        }
                    }
                    // 插入到消息队列
                    msg.next = p; 
                    prev.next = msg;
                }
    
                if (needWake) {
                    // 唤醒线程
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    3.3 Looper
    在创建 Looper 的时候,通过构造方法会创建一个消息队列,代码如下:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    在Looper构造方法初始化时,就给当前线程创建了消息队列MessageQueue,并且让Looper持有MessageQueue的引用。所以在Handler构造方法里就可以为消息队列赋值mQueue = mLooper.mQueue;

    上面分析了 Handler 的构造方法,其中调用了 Looper.myLooper() 方法,下面是它的源码:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
    }
    

    这个方法非常简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果没有Looper存在自然就返回空了。

    在 Handler 的构造中,从抛出的异常可知,

    public Handler(Callback callback, boolean async) {
       ...
    
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
       ...
    }
    
    

    每个线程想要获得 Looper 需要调用 prepare() 方法,如下所示:

    public static void prepare() {
        prepare(true);
    }
    
    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));
    }
    

    可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper保存到ThreadLocal里面。不过需要注意的是如果 sThreadLocal 已经设置过了,那么会抛出异常,也就是说一个线程只会有一个 Looper。

    这里涉及到ThreadLocal的知识点!ThreadLocal是什么?它是一个用来存储数据的类,类似HashMap、ArrayList等集合类。它的特点是可以在指定的线程中存储数据,然后取数据只能取到当前线程的数据。

    举个例子:

    ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();
    private void testMethod() {
        mThreadLocal.set(0);
        Log.d(TAG, "main  mThreadLocal=" + mThreadLocal.get());
    
        new Thread("Thread1") {
            @Override
            public void run() {
                mThreadLocal.set(1);
                Log.d(TAG, "Thread1  mThreadLocal=" + mThreadLocal.get());
            }
        }.start();
    
        new Thread("Thread2") {
            @Override
            public void run() {
                mThreadLocal.set(2);
                Log.d(TAG, "Thread1  mThreadLocal=" + mThreadLocal.get());
            }
        }.start();
    
        Log.d(TAG, "main  mThreadLocal=" + mThreadLocal.get());
    }
    

    输出的log是

    main  mThreadLocal=0
    Thread1  mThreadLocal=1
    Thread2  mThreadLocal=2
    main  mThreadLocal=0
    

    通过上面的例子可以清晰的看到ThreadLocal存取数据的特点,只能取到当前所在线程存的数据,如果所在线程没存数据,取出来的就是null。ThreadLocal保证每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用。

    好了回到正题,prepare()创建Looper的时候同时把创建的Looper存储到了ThreadLocal中,通过对ThreadLocal的介绍,获取Looper对象就很简单了,sThreadLocal.get()即可,源码提供了一个public的静态方法可以在主线程的任何地方获取这个主线程的Looper(注意一下方法名myLooper(),多个地方会用到):

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    Looper创建完了,接下来开启循环,loop方法的关键代码如下:

    public static void loop() {
        final Looper me = myLooper();  // ①
        if (me == null) {              // ②
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        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 {
                msg.target.dispatchMessage(msg);    // ⑤
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);       
                }
            }
    
            msg.recycleUnchecked();       // ⑥
        }
    }
    

    在①处首先获取Looper,然后在②处判断如果Looper为空,说明在线程中必须关联一个Looper。在③处通过Looper获取消息队列,④处开始一个死循环,通过调用消息队列的next()方法queue.next()进行出队操作。 也就是说在该段代码中Looper一直在轮询消息队列MessageQueue。假若消息队列中没有未处理的消息(即queue.next()==null)则其进入阻塞状态,假若消息队列中有待处理消息(即queue.next()!=null)则利用⑤处msg.target.dispatchMessage(msg)将该消息派发至对应的Handler。最后⑥处将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。

    到了这,可能有的人会有一个疑问:系统怎么知道把消息发送给哪个Handler呢? 其实就是在enqueueMessage()中系统给msg设置了target从而确定了其目标Handler,所以只要通过msg.target.dispatchMessage(msg)就可以将消息派发至对应的Handler了。那在dispatchMessage()中又会对消息做哪些操作呢?我们继续跟进源码。

    在看dispatchMessage()方法之前先看一下queue.next() 的方法的源码(主要还是看注释):

    Message next() {
            final long ptr = mPtr;
            if (ptr == 0) {
                // 获取NativeMessageQueue地址失败,无法正常使用epoll机制
                return null;
            }
    
            // 用来保存注册到消息队列中的空闲消息处理器(IdleHandler)的个数
            int pendingIdleHandlerCount = -1; 
            // 如果这个变量等于0,表示即便消息队列中没有新的消息需要处理,当前
            // 线程也不要进入睡眠等待状态。如果值等于-1,那么就表示当消息队列中没有新的消息
            // 需要处理时,当前线程需要无限地处于休眠等待状态,直到它被其它线程唤醒为止
            int nextPollTimeoutMillis = 0;
            for (;;) {
                ......
    
                // 检查当前线程的消息队列中是否有新的消息需要处理,尝试进入休眠
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    // 当前时间
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    // mMessages 表示当前线程需要处理的消息
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        // 找到有效的Message
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    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;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // 没有更多消息,休眠时间无限
                        nextPollTimeoutMillis = -1;
                    }
    
                    ......
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        // 获取IdleHandler数
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
    
                    if (pendingIdleHandlerCount <= 0) {
                        // 没有IdleHandler需要处理,可直接进入休眠
                        mBlocked = true;
                        continue;
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // 如果没有更多要进行处理的消息,在休眠之前,发送线程空闲消息给已注册到消息队列中的IdleHandler对象来处理
                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 {
                        // 处理对应逻辑,并由自己决定是否保持激活状态
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        // 不需要存活,移除
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // 重置IdleHandler数量
                pendingIdleHandlerCount = 0;
    
                /**
                 * 这里置0,表示下一次循环不能马上进入休眠状态,因为IdleHandler在处理事件的时间里,
                 * 有可能有新的消息发送来过来,需要重新检查。
                 */
                nextPollTimeoutMillis = 0;
            }
        }
    

    继续跟进Handler的dispatchMessage()方法,代码如下:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    

    首先处理Message的回调callback,比如调用handler.post(Runnable runnable)时,该runnable就会被系统封装为Message的callback。

    然后处理Handler的回调callback,比如执行Handler handler=Handler(Callback callback)时就会将callback赋值给mCallback

    最后调用handleMessage()处理消息Message。

    The End!

    相关文章

      网友评论

          本文标题:Handler消息机制知识汇总

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