Handler的原理

在使用Handler的过程当中主要涉及到如下几个类Looper、Handler、Message、还有一个隐藏的Message Queue,它直接与Looper交互,咱们不会直接接触。linux

Handler建立

Handler#Handler缓存

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: " +
                    klass.getCanonicalName());
            }
        }
        //关联Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //关联MessageQueue 同时也说明了MessageQueue 属于Looper
        mQueue = mLooper.mQueue;
       //注意这里的mCallback 它也是消息处理方式的一种,下文会有分析
        mCallback = callback;
        mAsynchronous = async;
    }复制代码

上面的代码展现了Handler如何与Looper、MessageQueue关联,下面咱们看下Looper是如何被建立得的,以及它的MessageQueue是怎么建立的。
Looper#myLooperbash

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }复制代码

能够看到Looper 是经过ThreadLocal.get获得的,那ThreadLocal又是什么呢?
经过注释咱们能够发现,ThreadLocal是一个跟线程绑定的数据存储类,它能够在指定的线程中存储数据,同时也只能在指定线程中才能获取数据,对于其余线程来时是无效的,既然是集合确定有set和get方法。下面咱们来看下
ThreadLocal#set并发

public void set(T value) {
        //获得当前正在运行的线程
        Thread t = Thread.currentThread();
        //ThreadLocalMap 是一个自定义的hash map 用来存储数据
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }复制代码

咱们说过ThreadLocal是跟指定线程绑定的,其实从下面代码就能看出来异步

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

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }复制代码

代码很简单,我就不解释了。
好了既然咱们知道了ThreadLocal,那接下来咱们就看下Looper。既然ThreadLocal.get()获得的是Looper,咱们就理由相信这个Looper是跟UI线程绑定的。但是这个Looper又是在哪初始化的呢? 并且初始化确定是经过ThreadLocal.set方式调用的。
Looper#prepareasync

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

private Looper(boolean quitAllowed) {
        //建立Looper的同时建立了MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }复制代码

那Looper#prepare又是在哪被调用了呢?最终咱们在ActivityThread#main函数中找到了,也就是程序启动时调用的。
ActivityThread#mainide

public static void main(String[] args) {
    //............. 无关代码...............
     此时跟UI线程绑定的Looper已经建立了
    Looper.prepareMainLooper();
    //开启无线循环来不断的从消息队列中拿消息
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}复制代码

Looper#loop(省略一部分代码)函数

public static void loop() {
    //得到一个跟UI线程绑定的 Looper 对象
    final Looper me = myLooper();
    // 拿到 looper 对应的 mQueue 对象
    final MessageQueue queue = me.mQueue;
    //死循环监听(若是没有消息变化,他不会工做的) 不断轮训 queue 中的 Message
    for (;;) {
        // 经过 queue 的 next 方法拿到一个 Message
        Message msg = queue.next(); // might block
        //空判断
        if (msg == null)return;
        //消息分发   此处的target其实就是绑定的Handler
        msg.target.dispatchMessage(msg);
        //回收操做  
        msg.recycleUnchecked();
    }
}复制代码

到这里的时候我先你的脑子确定会闪过一个念头进入死循环那程序不就卡死了吗,程序还在吗执行呢?这个问题咱们先放放,下面会简答,这里能够先说结论。首先MessageQueue不是传统的阻塞队列,由于它没有继承任何队列,同时内部也没有持有任何阻塞队列的对象,那它是如何实现阻塞队列的效果的呢?其实这里使用了linux的epoll技术,感兴趣的朋友能够深刻研究下,下次若是有时间的话我也会写一篇相关的博文来介绍。下面是摘自百度的epoll介绍:oop

epoll是Linux内核为处理大批量文件描述符而做了改进的poll,是Linux下多路复用IO接口select/poll的加强版本,它能显著提升程序在大量并发链接中只有少许活跃的状况下的系统CPU利用率。另外一点缘由就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就好了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减小epoll_wait/epoll_pwait的调用,提升应用程序效率post

Handler建立的另外一种姿式
上文咱们分析了在UI线程(主线程)中建立的Handler,并会获得一个UI Looper,那假如咱们新建一个线程并在其中建立Handler会发生什么?

public class LooperThread extends Thread {
    private Handler handler1;
    private Handler handler2;
    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();
        // 实例化两个handler
        handler1 = new Handler();
        handler2 = new Handler();
        // 开始循环处理消息队列
        Looper.loop();
    }
}复制代码

在子线程中咱们能够建立多个Handler,可是必须手动调用Looper.prepare();和Looper.loop();并且Looper.prepare()必须在Handler建立以前,这是为何呢?咱们回到上文中的代码。由于在建立Handler的时候会检查mLooper 是否为null,为null会抛出异常,而且此处的Looper是跟当前线程绑定的。

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

要保证mLooper 不为null,必需要先调用Looper.prepare()进行Looper的建立并绑定当前线程。

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));
    }复制代码

在建立完Handler以后还须要手动调用Looper.loop()开启消息循环队列。

阶段总结
好了到这,咱们已经分析完Handler以及Looper和MessageQueue的建立和关联,而且还知道,建立完Looper和MessageQueue以后会进入一个死循环一直等待消息的到来并拿出消息进行分发处理,不然会一直阻塞,其中这里的阻塞队列利用了linux的epoll技术。另外,咱们还知道一个线程能够有多个Handler,可是只能有一个Looper!

Handler发送消息

有了handler以后,咱们就可使用 post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long)和 sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。从上面这些方法你可能会觉得咱们能够发送2中消息类型:Message和Runnable,但其实发出的Runnable最终也会被封装成Message,下面咱们来看代码:
Handler#xxx

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

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


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 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方法,并执行enqueueMessage入队操做。
Handler#enqueueMessage

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //注意看这行代码 咱们将Handler赋值给Message的target 
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }复制代码

阶段总结
经过上面的代码获得一条原则就是:消息发送和处理遵循『谁发送,谁处理』的原则。过程仍是比较简单的,下面有2点要注意的地方:

  1. 咱们发送的2中类型消息最终都会被封装成Message对象进行发送
  2. 在建立完消息以后咱们会将Message和Handler经过msg.target = this进行绑定,方便下面进行处理。

Handler处理消息

Looper#loop

public static void loop() {
    //得到一个 Looper 对象
    final Looper me = myLooper();
    // 拿到 looper 对应的 mQueue 对象
    final MessageQueue queue = me.mQueue;
    //死循环监听(若是没有消息变化,他不会工做的) 不断轮训 queue 中的 Message
    for (;;) {
        // 经过 queue 的 next 方法拿到一个 Message
        Message msg = queue.next(); // might block
        //空判断
        if (msg == null)return;
        //消息分发   
        msg.target.dispatchMessage(msg);
        //回收操做  
        msg.recycleUnchecked();
    }
}复制代码

MessageQueue的工做方式是当有消息被放入的时候MessageQueue.next()会返回Message对象,不然就会阻塞在这,拿到消息之后会调用Message绑定的Handler来出来消息,而后回回收消息。下面让咱们看下消息是如何被处理。
Handler#dispatchMessage

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

private static void handleCallback(Message message) {
        message.callback.run();
    }

 public void handleMessage(Message msg) {
    }复制代码

能够看到,消息处理分红了2中方式,一种是咱们传递的Runnable对象,另外一种是普通的Message对象。代码很简单,handleCallback直接调用了Runnable.run,而handleMessage是空实现,须要咱们重写而且实现它。还有一种处理方式就是mCallback,是在建立Handler的时候经过都找参数传入的,你们回去最上面经过注释能够看到。

阶段总结
经过上面获得代码,咱们了解到在处理消息的时候有三种方式,而且是有顺序的。

  1. 若是有Runnable消息就直接处理Runnable消息,而后忽略其余消息,因此Runnable的优先级是最高的。
  2. 没有Runnable消息,查找时候有经过构造函数传入的Callback对象,有就处理并检查时候处理成功,成功就直接returan,不然才调用最普通的Message对象处理。
  3. 普通Message的优先级是最低的,而且须要咱们本身来实现handleMessage方法。
  4. 从上面的代码中咱们也验证了Handler谁发送就谁处理的原则,实现方式是经过将Handler赋值给Message.target来实现的。

epoll的真相

到此关于Handler的建立、消息发送以及消息处理都分析完毕了。如今还剩下那个死循环的问题一直困扰着咱们,那就是linux底层epoll究竟是如何处理消息的呢。原本这段代码想本身分析的,但在查找资料的时候发现已经有好多人分析过了,而且分析的比较透彻,其中MessageQueue涉及到不少native方法,我这里就不分析,下面放上2篇分析的比较好的博文供你们本身来参考。
深刻理解 MessageQueue
Looper 中的 loop() 方法是如何实现阻塞的

相关文章
相关标签/搜索