全面理解Handler第一步:理解消息队列,手写消息队列

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全面理解Handler第一步:理解消息队列,手写消息队列相关的知识,希望对你有一定的参考价值。

前言

Handler机制这个话题,算是烂大街的内容。但是为什么偏偏重拿出来“炒一波冷饭”呢?因为自己发现这“冷饭”好像吃的不是很明白。最近在思考几个问题,发现以之前对Handler机制的了解是在过于浅显。什么问题?

  • Handler机制存在的意义是什么?能否用其他方式替换?
  • Looper.loop();是一个死循环,为什么没有阻塞主线程?用什么样的方式解决死循环的问题?

如果透彻的了解Handler,以及线程的知识。是肯定不会有这些疑问的,因为以上问题本身就存在问题。

就这俩个小问题,就发现自己在学习道路上的不扎实,所以这段时间重新理解了一下Handler。先预告一小下下,关于Handler的内容将是一个系列文章,今天这一篇内容重点在于Handler的理解,以及对消息队列的思考。

正文

1、Handler机制为了什么?

我们都知道,在android开发中,无法在子线程中更新UI。

我们先思考一个问题?为什么不能在子线程更新UI。如果看过View绘制的源码,我们都知道不能在子线程更新UI的原因是:ViewRootImpl中有这么一个方法:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

很明显这是人为限制的一个操作。那我们在思考,为什么谷歌开发Android系统时要这么限制?

其实不难推测出来。对于线程来说,我们都知道线程与线程之间是内存共享的。所以如果某一时刻多个子线程同时去更新UI,那么对于绘制UI来说便成为了一个不安全的操作。为了保证UI绘制的正确性,此时势必要增加锁,以同步的方式去控制这个问题。

然而加锁的方式显然是一种牺牲性能的方式。

那么还有没有其他方案呢?很显然,最终谷歌选择了只能在主线程更新UI,应运而生的Handler机制被创造出来了。但是它也不是什么新概念,说白了就是消息队列。实现原理也很简单:只允许一个线程(主线程)去更新UI,子线程将消息放到消息队列中,由主线程去轮询消息队列,拿出消息并执行。

这也就是我们的Handler机制。

2、消息队列

这种单线程 + 消息队列的模型其实应用很广。比如在Web前端之中,对于javascript来说,被设计时就决定了单线程模型。假设如果 Javascript 被设计为多线程的程序,那么操作 DOM 必然会涉及到资源的竞争。此时只能加锁,那么在 Client 端中跑这么一门语言的程序,资源消耗和性能都将是不乐观的。但是如果设计成单线程,并辅以完善的异步队列来实现,那么运行成本就会比多线程的设计要小很多了。

所以我们可以看到,Handler机制的思路可以说是一个颇为常见的设计。

既然本质是消息队列,是不是我们自己也可以写一套消息队列来感受一下Handler的设计思路呢?没错,接下来让我们一起实现一套简单的消息队列:

3、手写消息队列

我们先来捋一捋思路:

Looper中创建了MessageQueue,Handler之中又通过ThreadLocal拿到主线程new出来的Looper,因此Handler就持有了MessageQueue,又因此线程间是内存共享的,所以子线程可以通过Handler去往MessageQueue之中发送Message。

Looper.loop()死循环轮询MessageQueue,拿到Message就回调其对应的方法。

这样整个Handler机制就运转起来了。接下来我们就依靠这个思路,实现自己的消息队列,为了代码更简洁,以及和Handler机制产生区别,我这里省略一些操作比如ThreadLocal之类的。

3.1、代码实现

代码结束后有解释


public class MainMQ {
private MyMessageQueue mMQ;
public static void main(String[] args) {
    new MainMQ().fun();
}

public void fun() {
    mMQ = new MyMessageQueue();
    System.out.println("当前线程id:" + Thread.currentThread().getId());
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 省略try-catch
            Thread.sleep(3000);
            mMQ.post(new MyMessage(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行此条消息的线程id:" + Thread.currentThread().getId());
                }
            }));
        }
    }).start();

    loop();
    System.out.println("死循环了,我永远也不被执行~");
}

public void loop() {
    while (true) {
        // 省略try-catch
        MyMessage next = mMQ.next();
        if (next == null) {
            continue;
        }
        Runnable runnable = next.getRunnable();
        if (runnable != null) {
            runnable.run();

    }
}

}

这里没有使用Looper这种思想,因为Looper本质就是使用ThreadLocal创建一个和主线程唯一关联的Looper实例,并以此保证MessageQueue的唯一性。

知道这个原理之后,这个demo。直接在主线程中new MessageQueue(),是同样的道理,然后调用loop()方法死循环轮询MessageQueue中的Message,不为null则执行。

main()方法中start了一个子线程,然后sleep3秒后,往MessageQueue中post Message。效果很简单,我猜很多小伙伴已经猜到了:

![](http://i2.51cto.com/images/blog/201809/30/d9f45c6c1e12522dd0ad17e54bcb86d7.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

> 贴一下MessageQueue和Message
```java
public class MyMessageQueue {
    private final Queue<MyMessage> mQueue = new ArrayDeque<>();

    public void post(MyMessage message) {
        synchronized (this) {
            notify();
            mQueue.add(message);
        }
    }

    public MyMessage next() {
        while (true) {
            synchronized (this) {
                // 省略try-catch
                if (!mQueue.isEmpty()) {
                    return mQueue.poll();
                }
                wait(); 
            }
        }
    }
}
public class MyMessage {
    private Runnable mRunnable;

    public MyMessage(Runnable runnable) {
        mRunnable = runnable;
    }

    public Runnable getRunnable() {
        return mRunnable;
    }
}

3.2、思考存在的问题

细心的小伙伴,可能有留意到loop()方法执行后有这么一行代码,然后效果图中并没有被打印:

System.out.println("死循环了,我永远也不被执行~");

当然这是必然的,毕竟我们的loop()是一个死循环,后边的代码是不可能被执行的。其实我们ActivityThread中调用了Looper.loop()之后,也没有任何代码了。

这里可能有小伙伴有疑问了。loop()死循环了,那么我们在主线程中的生命周期回调怎么办?岂不也不被执行了?其实不然,通过上述的消息队列,我们就能看出:我们在手写的这个demo中,loop启动前start了一个子线程,由子线程发送Message交由loop去执行。保证了消息的流畅性。

那是不是我们Android中的loop也是这种思路?没错,main中的loop启动前,的确会起一个子线程......

不要着急,关于这个问题,让我们下篇文章再展开~

结尾

今天这篇文章是全面理解Handler机制的第一篇,内容大多并没有直切到Handler机制本身,而是从外部去思考Handler的设计。而接下来的内容则是对Handler内部源码进行剖析了。

希望可以对小伙伴们有所帮助,如果感觉有收获,欢迎点赞,收藏,关注呦~

以上是关于全面理解Handler第一步:理解消息队列,手写消息队列的主要内容,如果未能解决你的问题,请参考以下文章

从Message生产到消费的过程理解Handler消息机制

Android的消息机制Handler详解

Android Handler机制剖析

Android中的Handler机制

RabbitMQ实战:理解消息通信

消息队列技术之基本概念(转)