简单理解Handler消息机制

前言

我们都知道在UI线程中不能进行耗时操作,例如数据读写、网络请求。Android 4.0开始,在主线程中进行网络请求甚至会抛出Android.os.NetworkOnMainThreadException。这个时候,我们就会开始依赖Handler。

后来随着AsyncTask、EventBus、Volley以及Retrofit 的出现,Handler的作用似乎被弱化,逐渐被大家遗忘。其实不然,AsyncTask其实是基于Handler进行了非常巧妙的封装,Handler的使用依然是其核心。Volley同样也是使用到了Handler。因此,我们有必要了解一下Handler的实现机制。

实现机制解析

Handler的工作原理是Handler发送消息给MessageQuee给形成Handle MessageQuee再由Handle MessageQuee传递消息给Looper(一个对象)最后由Looper递交给 Handler处理(一个Handler里面必须要有一个Looper)

Handler、Looper、MessageQueen、Message的关系 
           Message: Handler接收和处理的消息对象

MessageQueen: 存储消息对象的队列

Looper: 负责读取MessageQueen中的消息,读到消息之后就把消息交给Handler去处理。

Handler:发送消息和处理消息

首先看下Handler如何与当前线程绑定?我们看下Handlerd的构造方法:


 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()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

这是有所删减的构造方法,保留重点。看第二行和第七行,这都和Looper有关,而Handler和线程的绑定就是通过Looper。在这里mLooper = Looper.myLooper();是获得当前线程相关的Looper(因为是构造方法,所以就是创建Handler线程相关的Looper), mQueue = mLooper.mQueue;是获得该Looper对象的消息队列(这又说明了Looper对象持有消息队列的引用),因为Looper和线程关联,所以消息队列和线程关联。

这样一看,是不是觉得天晴朗了许多?Looper和当前线程关联,消息队列又在Looper中,Handler一旦得到了对应线程的Looper引用,也就和当前线程和消息队列关联上了,不是么?但是,Looper本身又是如何关联上当前线程的?

       点进去看mLooper = Looper.myLooper();的源码:

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

就一行,这个ThreadLocal对象又是什么鬼?

 

        之前官方说创建一个线程的Looper使用Looper.prepare(),点进去看一下:


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

要获取当前线程相关的Looper是在ThreadLocal对象取出来的,而要创建一个前线程相关的Looper则是通过ThreadLocal对象set出来的(注意到异常信息,说明一个线程关联的Looper只有一个),是不是隐隐约约觉得Looper和当前线程关联的阴谋就隐藏在这个ThreadLocal类中?

ThreadLocal

关于他我看了几篇博客,很大一部分写的是错的,所以 
注意

  • 他并不是解决共享对象的多线程访问问题的!!!
  • 他并没有创建对象的拷贝或副本!!!

目的:他只是为了保证每个线程都拥有同一个类的不同对象 
实质:每个线程里都new了同一个类的对象 
作用:你或许觉得这样做很傻,但是如果使用全局变量就要考虑线程安全问题,而线程安全是以消耗性能为前提的,所以这种设计可以说很巧妙 
场景:每个线程都需要相同类型的对象,又各自独立,并且与他相关的操作不少时,如,Looper,ActivityThread,AMS 等

我们看一下ThreadLocal的源码,主要分析get()和set()

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

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

所以过程就很清晰了,我们来总结一下:

  1. 声明一个全局公用的ThreadLocal实例作为key
  2. 在线程中new一个或取出已经存了的对象作为value
  3. 将此key-value放入ThreadLocalMap中
  4. 将当前线程作为key,ThreadLocalMap作为值放入ThreadLocal中

看起来不好懂。首先必须明确一点,一个线程只能一个Looper对象,从而只有一个MessageQueue(在Looper的构造方法初始化),从上面代码当ThreadLocal对象get方法返回不为空则抛出的异常信息可以看出。大概就是所有的线程共享同一个ThreadLocal对象不同的线程在该对象存储某个变量的值是不同的。比如我们的Looper的prepare()中,当在不同的线程中ThreadLocal对象将新创建的Looper对象set进去的时候,不同线程get出来的Looper都是各自set进去的Looper对象。所以mLooper = Looper.myLooper();的意思就是“把我现在所在的线程的Looper取出来给我吧”的意思(至于ThreadLocal如何实现这看起来很神奇的功能,是因为它内部为不同的线程维护一个数组,每次存取数据都是从对应线程的数组操作的)

所以,Handler关联创建自己对象的线程的过程就是这样子~

           那Handler对象是如何将消息发送到绑定的消息队列(即创建Handler对象的线程关联的消息队列)呢?

           大家都知道Handler发送消息的方法是sendMessage(),看源码:

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

          再看下sendMessageDelayed的源码:

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

     调用了sendMesaageAtTime()再进去:


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

重点是enqueueMessage方法,而这个方法会调用消息队列的enqueueMessage方法。前面说过,消息队列实质是一个链表,当handleMessage被调用之后,Message对象会被插入链表,具体源码如下:


boolean enqueueMessage(Message msg, long when) {
        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;
            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 {
                // 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.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
 
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

因为消息可以设置delay 参数,所以在插入链表前还要判断下延时参数when,会根据延时由短到长按顺序插入。

 

      好了,消息已经插入队列,剩下的工作就是将消息取出来执行。前面说到Looper的消息循环机制,当我们prepare创建了一个当前线程Looper后,调用loop开启消息循环。所以看下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;
 
        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
 
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
 
            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
 
            msg.target.dispatchMessage(msg);
 
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
 
            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
 
            msg.recycleUnchecked();
        }
    }

 抛开抛异常和Log代码,其实很简单。就是进入了死循环。不断从自己的消息队列取出消息然后调用 msg.target.dispatchMessage(msg);(取不到则阻塞),而这个msg.target,就是Message对象持有的指向发送的Handler对象的引用,所以现在是调用了发送该Message的Handler对象的dispatchMessage方法去处理这个Message对象。

看下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或者Handler本身是否有设置回调接口(Message的callBack就是Handler所post的Runnable对象),有则调用回调接口去处理Message对象,没有则调用我们熟悉的handleMessage处理Message,至于怎么处理大家都清楚吧。

 

       总结一下:我们在创建Handler对象的线程外其他的线程通过一个Handler对象发送消息(也可以在本线程去post一个任务),将消息(post出去的Runnable也会被封装为Message)发送到创建Handler对象的线程所关联的Looper对象所持有的消息队列中,然后等待正在消息循环过程中的Looper对象去从队列中取出这个Message,一旦取出该Message,则调用发送该Message的Handler对象的handleMessage方法去处理这个Message。

       可以举一个通俗点的例子。设想一个场景,教室(线程)里的学生(Handler)有很多,只有一个老师(Looper),学生去不同教室(即不是创建该Handler对象的线程)学习写作业(创建Message),然后将作业带到自己教室里按顺序放到老师的桌子上(sendMessage插入消息队列),老师不断按顺序批改作业,每批改完一份作业就叫对应的学生过来拿作业本去修改错误(handleMessage)。

总结

  1. Looper.prepare()在本线程中存入ThreadLocalMap中一个Looper并创建一个MessageQueue对象
  2. Looper.loop()循环取Message中的消息,回调msg.target.dispatchMessage(msg)
  3. Handler构造得到Looper并与MessageQueue关联
  4. Handler.sendMessage会给msg.target赋值为自己并加入MessageQueue中
  5. 重写handlMessage()处理

借鉴了俩大佬的博客

https://blog.csdn.net/sinat_23092639/article/details/52334398

https://blog.csdn.net/qijinglai/article/details/80685226

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值