Handler的原理

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

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#myLooper

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

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

可以看到Looper 是通过ThreadLocal.get得到的,那ThreadLocal又是什么呢?
通过注释我们可以发现,ThreadLocal是一个跟线程绑定的数据存储类,它可以在指定的线程中存储数据,同时也只能在指定线程中才能获取数据,对于其他线程来时是无效的,既然是集合肯定有set和get方法。
首先我们来看下线程和Loop之间的关系图:

Looper.png

下面我们来看下
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#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));
    }

private Looper(boolean quitAllowed) {
        //创建Looper的同时创建了MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

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

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介绍:

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

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() 方法是如何实现阻塞的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容