Handler机制原理

一、google为什么设计这套机制

主要是为了解决在非UI线程中更新UI组件比较麻烦的问题。

二、google如何实现这套机制

UI线程中有一个线程专属的Looper对象,它负责安排所有准备在UI线程上执行的代码。这里有两点技术:实现UI线程专属的Looper对象用到了java的ThreadLocal技术,想深究请直接看ThreadLocal源码,不难,真心不难,当然不看也行,只要知道有这么回事也够,需要时再看。Looper对象通过消息机制接受系统或者应用的其他线程提交的准备在UI线程上执行的代码。提交方式是:以该Looper对象为参数创建一个Handler对象,也可以在UI线程中无参数构建一个Handler对象,此时的Handler对象直接就与UI线程的Looper对象绑定。Handler对象负责向Looper对象提交代码。最直接的提交方法是调用Handler对象的post方法,该方法的参数是一个Runnable对象,代表了要在UI线程上执行的代码。这样的方法简单但是没法传递数据给要执行的代码,因为构造Runnable对象是没有参数的。为此,google提供了另一种提交代码的方法,就是让Handler对象发送一个消息给Looper对象,这个消息中可以包含一定的数据(消息的what域和obj域就是用来包含数据的),要执行的代码就是Handler中的handleMessage方法,该方法会收到发送给Looper的消息,进而可以从中取出数据再执行代码。

以上为原理。

三、一些可以帮助理解原理的细节

因为Looper是给UI线程安排代码的,所以一个UI线程只能有一个Looper对象,否则多个Looper对象都要在UI线程上安排代码,解决冲突就是个大难题。因为Looper对象是线程专属的,所以一个Looper也只能对应一个UI线程。二者是一对一的关系。但是一个Looper对象可以有多个Handler对象向它提交代码,这并不会引起代码冲突。因为Looper对象会线性安排在UI线程上待执行的代码,它通过一个队列管理各个Handler对象提交的代码。Looper对象安排执行代码靠的是它的loop方法。

四、具体使用方法

将一个与UI线程上Looper对象关联的Handler对象传给其他线程,其他线程通过这个对象向UI线程上提交代码。

五、该机制的一般性扩展

实际上,可以为任意一个线程创建一个唯一的Looper对象,这是通过Looper类的静态方法prepare实现的,然后可以用这个Looper对象创建一个或多个Handler对象,然后就可以用这些Handler对象向该线程提交执行代码了,google提供了一个HandlerThread类,就是一个已经实现好了Looper对象的Thread,方便你的使用。

1、我们先说下什么是Android消息处理机制?

消息处理机制本质:一个线程开启循环模式持续监听并依次处理其他线程给它发的消息。

简单的说:一个线程开启一个无限循环模式,不断遍历自己的消息列表,如果有消息就挨个拿出来做处理,如果列表没消息,自己就堵塞(相当于wait,让出cpu资源给其他线程),其他线程如果想让该线程做什么事,就往该线程的消息队列插入消息,该线程会不断从队列里拿出消息做处理。

2、Android消息处理机制的工作原理?

打个比方:公司类比App

PM 的主要工作是设计产品,写需求文档,改需求,中途改需求,提测前改需求...

UI 主要工作是UI设计,交互等。

RD 工作我就不说了

CEO 不解释。

公司开创之后(App启动),那么CEO开始干活了(主线程【UI线程】启动),这时候CEO开启了无限循环工作狂模式,自己的公司没办法啊(相当于UI主线程转成Looper线程【源码里面有】)CEO招了一名RD(new Handler 实例)并把告诉PM和UI,如果你们有什么任务和需求就让RD(Handler实例)转告给我(CEO)。RD会把PM和UI的需求(Message)一条条记到CEO的备忘录里(MessageQueue)。CEO 无限循环的工作就是不断查看备忘录,看有什么任务要做,有任务就从备忘录一条一条拿出任务来,然后交给这一名RD(Handler 实例)去处理(毕竟CEO 不会写代码 囧...)。当然如果备忘录都做完了,这时候CEO就会去睡觉(线程堵塞【简单理解成线程wait】,让出CPU资源,让其他线程去执行)。但是这个备忘录有个特殊的功能就是没有任务的时候突然插入第一条任务(从无到有)就会有闹钟功能叫醒CEO起床继续处理备忘录。 整个消息处理机制的工作原理基本就是这样的。后面会有源码分析,你再来结合这个场景,会更好理解一些。

这里先给一张Android消息处理机制流程图和具体执行动画,如果看不懂没事,接着往下看(后面会结合Android UI主线程来讲解),然后结合着图和动画一块看更能理解整个机制的实现原理。

3、Looper、Handler、MessageQueue,Message作用和存在的意义?

Looper

我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.prepare()...Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop()方法里面有一段死循环的代码,所以主线程会进入while(true){...}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while(true){...}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。

MessageQueue

MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。

Handler

简单说Handler用于同一个进程的线程间通信。Looper让主线程无限循环地从自己的MessageQueue拿出消息处理,既然这样我们就知道处理消息肯定是在主线程中处理的,那么怎样在其他的线程往主线程的队列里放入消息呢?其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到MessageQueue 的实例,就可以往主线程的MessageQueue放入消息,主线程在轮询的时候就会在主线程处理这个消息。那么怎么拿到主线程 MessageQueue的实例,是可以拿到的(在主线程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

Message

Message 很简单了,你想让主线程做什么事,总要告诉它吧,总要传递点数据给它吧,Message就是这个载体。

-------------------------------

刚好画了UML图,核心的几个类如下:

Handler本质是用于跨线程通信的,如果是“子线程执行耗时操作,完毕后通知主线程”,就是我们常说的异步过程;当然也可以是两个对等子线程,例如线程A与线程B,要实现线程A拥有消息循环,线程B执行执行某项操作后通知线程A。


Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

这个过程可以这样描述:

1、线程A中new一个Looper对象(同时也在其中new了MessageQueue对象),并封装到线程的成员变量ThreadLocal.ThreadLocalMap中;

2、线程A中new一个Handler对象(new的过程中获得当前线程的Looper对象和其中的MessageQueue对象),覆写其中的dispatchMessage(Message msg)函数;

3、线程A中获得Looper对象,并获得其中的MessageQueue对象,然后进入死循环,不断遍历MessageQueue;

4、线程B实例化Message,并将target设置为线程A中的Handler,通过线程A的Handler对象,将Message放到Handler的MessageQueue对象中,此时线程A的死循环能获得这个Message,然后执行了这个Message的回调,也就是Handler的dispatchMessage(Message msg)函数。

使用AWS构建可靠且可扩展的网站和Web应用程序

在传统数据中心开发和部署可伸缩、全球可用的Web应用需要执行大量的手动操作,这会耗费大量人力物力,负载高峰和流量波动会导致资源配置不足或过度配置。如今在AWS上构建网站免费,在云中构建、部署和管理网站和Web应用程序所需的免费产品和服务易如反掌。查看详情

Android程序启动后会起一个进程,所有的组件都在这个进程里面运行。开始这个进程只包含一个线程,叫做UI主线程,负责处理UI界面的显示更新。对于一些费时的操作(超过5S会卡顿)需要单独启动一个子线程去处理。子线程处理完毕将结果通知给UI主线程,主线程得到结果后更新UI界面。子线程与UI主线程的通信在android中使用了消息机制来完成,那么是怎么完成的呢?这就和handler 机制的原理,简而言之言而总之,就是需要两样样古老的东西,消息队列、轮询。也就是说,主线程起来以后有一个消息队列,同时和该队列配对的有一个轮询,而子线程有这个消息队列的引用,那这样,子线程处理完以后就会向主线程的消息队列发消息,主线程轮询自己的队列,发现有未处理的消息就进行处理。这就是handler的机制,对于handler具体怎么设计的,不解释,去查查String 的字符串池

Handler 是一个消息分发对象。而消息分发,有赖于消息循环,也就是 Looper。在一个线程中,Looper 阻塞线程,等待消息构成循环,有了消息,分配到对应的 Handler,让他进一步分发处理,如是。


# 1. 什么是消息机制

说到消息机制,作为一名 Android 开发者一定先想到的是 Handler。Handler 就是 Android 消息机制的上层接口,我们可用通过 Handler 轻松的在不同的线程中切换任务,但 Handler 的实现还有两个很重要的概念MessageQueue和Looper。

MessageQueue 的翻译是消息队列,它的内部采用了单链表的结构存储 Handler 对象发送的消息。

Looper 的作用是不断地查询 MessageQueue 中是否有消息,如果 Looper 发现 MessageQueue 中存入了新的消息,它就会去处理这条消息,如果没有新消息,Looper 就会以无限循环的方式去查询 MessageQueue 中是否有新消息。

# 2. 为什么要有 Handler

## 2.1)官方文档中 Handler 的主要作用

(1)安排将来某个时间点执行的Message和Runnables;

(2)在不同于当前的线程上执行的操作;

## 2.2)Handler 被用来做的最多的一件事就是更新主线程的 UI。

在 Android 开发中,默认子线程是不可以更新 UI 的,这一点可以从 View 的最高层级 ViewRootImpl 类中找到答案

void checkThread() {

    if (mThread != Thread.currentThread()) {

        throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");

    }

}

ViewRootImpl 类中的checkThread方法会在更新 UI 前被执行,如果当前线程不是主线程,就会抛出Only the original thread that created a view hierarchy can touch its views.的异常

## 2.3)那么 Android 为什么要设计为只能在主线程中更新 UI 呢?

Android 在子线程中更新 UI 是不安全的,如果多个子线程同时修改一个控件的数据,后果是不可控的

如果给 UI 更新机制加锁,会降低 UI 的访问效率,并且可能阻塞某些线程的执行

# 3. Handler 的用法

## 3.1)在主线程中创建 Handler

通常,我们在主线程中创建 Handler 的写法如下:

private Handler handler = new Handler(){

    @Override

    public void handleMessage(Message msg) {

        super.handleMessage(msg);

    }

};

但这样写,系统会这样提示:

This Handler class should be static or leaks might occur (anonymous android.os.Handler)

这个Handler类应该是静态的,否则可能会发生泄漏

出现这个警告但原因是,Handler 在 Activity 中作为一个匿名内部类来定义,它的内部持有来 Activity 的实例。当 Activity 被用户关闭时,因为 Handler 持有了 Activity 的引用,就造成了 Activity 无法被回收,从而导致了内存泄漏。

因此,在这里推荐一种更加安全的写法:

private static class MyHandler extends Handler{

    private WeakReference<Activity> weakReference;

    public MyHandler(Activity activity){

        weakReference = new WeakReference<>(activity);

    }

    @Override

    public void handleMessage(Message msg) {

        super.handleMessage(msg);

        switch (msg.what){

                case 0:                   

                  Toast.makeText(weakReference.get(),Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();

                  break;

            }

    }

}

private MyHandler handler = new MyHandler(this);

通过静态内部类的方式实现一个 Handler,此时内部类并不持有外部类对象的应用,需要在内部类的构造方法内增加一个外部类(Activity)的弱应用。这样,即使 Activity 被关闭,Activity 也能顺利被回收。

onCreate() 中的代码如下:

btn_0 = findViewById(R.id.btn_0);

btn_0.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

        new Thread(){

            @Override

            public void run() {

                super.run();

                Message message = Message.obtain();

                message.what = 0;

                handler.sendMessage(message);

            }

        }.start();

    }

});

这时候点击按钮的运行效果如下:

## 3.2)在子线程中创建 Handler

在官方文档中 Handler 的主要作用是在不同于当前线程的线程中执行操作,那么如何用 Handler 解决两个子线程之间的通信呢?

请看代码:

btn_1 = findViewById(R.id.btn_1);

btn_1.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

        new Thread(){

            @Override

            public void run() {

                super.run();

                Looper.prepare();

                handler = new MyHandler(MainActivity.this);

                try {

                    sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                Looper.loop();

            }

        }.start();

        new Thread(){

            @Override

            public void run() {

                super.run();

                Message message = Message.obtain();

                message.what = 0;

                handler.sendMessage(message);

            }

        }.start();

    }

});

此时点击按钮:

可见当前的处理线程已经变成了子线程。

# 4. Handler 工作原理

如果细心的观察代码,可以看到在子线程中创建 Handler 的时候调用了Looper.prepare()和Looper.loop()两个方法。这两句代码有什么用呢?

我们暂时可以把 Looper 理解为消息的管理者,它负责从 MessageQueue 中提取出消息,传递给 Handler 进行处理,每一个 Handler 都必须要有一个 Looper,在 Handler 创建的时候,它会自动使用当前线程的 Looper,而Looper.prepare()的作用就是为当前线程准备一个 Looper,Looper.loop()的作用是开始查找当前 MessageQueue 中是否有了新的消息。

这就是 Handler 工作的第一步 :

## 4.1)采用当前线程的 Looper 创建 Handler

因为这里主要讲 Handler 的工作流程,创建 Looper 的具体过程放到文章的下面讲解。我们只要知道

Looper.prepare()为当前的线程创建了一个 Looper 对象即可。

但是,在主线程中创建 Handler 的时候,我们并没有看到Looper.prepare()的执行,这是因为在 UI 线程,即 ActivityThread 的创建过程中,Looper 已经被创建好了。

我们可以在 ActivityThread 的 main() 方法中看到这样一句代码:

Looper.prepareMainLooper();

这个方法内部也调用了Looper.prepare()为 UI 线程创建了一个 Looper。

## 4.2)通过 Handler 的 `sendMessageAtTime()` 方法发送 Message

为什么是sendMessageAtTime?不是还有sendMessage(),sendEmptyMessage(),sendEmptyMessageDelayed(),sendEmptyMessageAtTime(),sendMessageDelayed()这么多方法吗?

通过阅读这些方法的源码可以发现,这些方法最终调用的都是sendMessageAtTime()。

其次还有post(),postAtTime(),postDelayed()方法最终调用的也都是sendMessageAtTime()方法,只是多了一步调用getPostMessage(Runnable r, Object token)将 Runnable 封装为一个 Message 对象的 callback 里。

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;

}

那么sendMessageAtTime()里的具体操作是什么呢?我们去源码里一探究竟

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

    // 先获取当前 Handler 中的 MessageQueue,mQueue 在 Looper 的构造方法中进行初始化。

    MessageQueue queue = mQueue;

    if (queue == null) {

        RuntimeException e = new RuntimeException(

                this + " sendMessageAtTime() called with no mQueue");

        Log.w("Looper", e.getMessage(), e);

        return false;

    }

    // queue 不为空,则执行 Handler.java 里的另一个 enqueueMessage() 方法

    return enqueueMessage(queue, msg, uptimeMillis);

}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

    // 指定 msg 的 Target 对象为当前的 Handler

    msg.target = this;

    if (mAsynchronous) {

      msg.setAsynchronous(true);

    }

    return queue.enqueueMessage(msg, uptimeMillis);

}

Handler 中的 enqueueMessage() ,最终会调用 MessageQueue.java 中的 enqueueMessage() 方法。

之后,Message 对象最终传递到 MessageQueue 即消息队列里中,在消息队列里的具体处理逻辑在文章的MessageQueue 工作原理部分会具体解释。

## 4.3)Looper 处理消息后调用 Handler 的 dispatchMessage() 方法

在第二步将消息插入消息队列后,Looper 就开始遍历消息队列,找到新的消息,再通知 Handler 去执行这条消息,调用的就是 Handler 的dispatchMessage()方法。

public void dispatchMessage(Message msg) {

  // msg 的 callback 对象就是一个 Runnable

  if (msg.callback != null) {

        handleCallback(msg);

    } else {

        // 检查 mCallback 是否为空,不为空就执行它内部定义的 handleMessage() 方法

        if (mCallback != null) {

            if (mCallback.handleMessage(msg)) {

                return;

            }

        }

        // 如果 mCallback 为空,就执行在实例化 Handler 过程中我们自己定义的 handleMessage() 方法中的内容

        handleMessage(msg);

    }

}

dispatchMessage()方法首先会检查 Message 的 Callback 对象是否为空,callback 就是通过 post() 方法传递的 Runnable 对象,如果 callback 不为空,就去执行 handleCallback() 方法。

handleCallback() 方法的实现也很简单,它在内部执行了 Runnable 的run()方法

private static void handleCallback(Message message) {

    message.callback.run();

}

如果 callback 对象为空,就检查 mCallback 是否为空,不为空就执行它的定义的  handleMessage() 方法,若没有 mCallback,最终将直接执行我们在继承 Handler 时自己定义的 handleMessage() 方法中的代码。

Callback是 Handler 中定义的的一个接口,它的代码如下:

/**

* Callback interface you can use when instantiating a Handler to avoid

* having to implement your own subclass of Handler.

*/

public interface Callback {

    /**

    * @param msg A {@link android.os.Message Message} object

    * @return True if no further handling is desired

    */

    public boolean handleMessage(Message msg);

}

如果使用 Callback 接口的话,我们可以直接实例化一个 Handler 而不用去实现一个 Handler 的子类,

private Handler mHandler = new Handler(new Handler.Callback() {

    @Override

    public boolean handleMessage(Message msg) {

        return false;

    }

});

# 5. MessageQueue 工作原理

我们从上一部分的 MessageQueue.java 中的 enqueueMessage() 方法开始入手。

## 5.1)enqueueMessage()

代码量有点多,要耐心看哦!

boolean enqueueMessage(Message msg, long when) {

    // 检查当前 msg 的 target 是否为空

    if (msg.target == null) {

        throw new IllegalArgumentException("Message must have a target.");

    }

    // msg 如果正在被执行,就抛出异常

    if (msg.isInUse()) {

        throw new IllegalStateException(msg + " This message is already in use.");

    }

    synchronized (this) {

        // 在 quit() 方法中,mQuitting 会被设置为 true

        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 正在执行

        msg.markInUse();

        // 设置 msg 的 when 为传进来的 when 参数,when 是 Message 想要被执行的时间

        msg.when = when;

        // 得到当前消息队列的头部消息

        Message p = mMessages;

        boolean needWake;

        // 当前消息队列为空,新消息的触发时间为 0,或者新消息的触发时间早于消息中第一条消息的触发时间

        // 则将新消息插入到队列的头部,作为当前消息队列的第一条消息

        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 {

            // 将新消息插入到当前消息队列当中,(不是头部)

            // 通常我们不必唤醒事件队列,

            // 除非队列头部有消息障碍,并且消息是队列中最早的异步消息。

            needWake = mBlocked && p.target == null && msg.isAsynchronous();

            Message prev;

            // 开始循环便利消息队列,比较新消息和队列中消息的 when(触发事件)的值,将新消息插入到适当位置

            for (;;) {

                // 循环第一次遍历时,将当前队列中的头部消息赋值给 prev

                prev = p;

                // p 指向队列中的第二个消息

                p = p.next;

                // 如果下一个消息为空,或者新消息的触发时间早于下一个消息,找到了要插入的位置,退出循环

                if (p == null || when < p.when) {

                    break;

                }

                // needWake 为 true,并且 下一条消息是异步的,则不需要唤醒。

                if (needWake && p.isAsynchronous()) {

                    needWake = false;

                }

            }

            // 将新消息插入到 p 之前,头消息之后。

            msg.next = p; // invariant: p == prev.next

            prev.next = msg;

        }

        // 如果需要唤醒,调用 nativeWake 方法去唤醒

        if (needWake) {

            nativeWake(mPtr);

        }

    }

    return true;

}

执行完 enqueueMassage 方法,我们新发送的 Message 就成功的插入了消息队列当中。

但是除了插入新消息,我们还需要从消息队列中读取消息,这又要怎么做呢?

## 5.2)next()

Message next() {

    // 如果消息循环已退出,并且被丢弃,则返回空。

    // 这个将在应用重启一个 looper 时发生

    final long ptr = mPtr;

    if (ptr == 0) {

        return null;

    }

    // 记录空闲时处理的 IdlerHandler 数量,只在第一次迭代时为 -1

    // IdleHandler 只在队列为空 或者 是头部消息时执行

    int pendingIdleHandlerCount = -1;

    //  native 层使用的变量,设置的阻塞超时时长,0 为不阻塞,-1 为阻塞

    int nextPollTimeoutMillis = 0;

    for (;;) {

        if (nextPollTimeoutMillis != 0) {

            Binder.flushPendingCommands();

        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        // 尝试检索下一条消息。 如果找到则返回。

        synchronized (this) {

            // 获取系统从开机到现在到时间

            final long now = SystemClock.uptimeMillis();

            Message prevMsg = null;

            // 将队列中到头部消息赋值给 msg

            Message msg = mMessages;

            if (msg != null && msg.target == null) {

                // msg 不为空,但是这个 msg 没有 handler,则这个 msg 为栅栏

                // 开始遍历,指到获取第一个异步消息

                do {

                    prevMsg = msg;

                    msg = msg.next;

                } while (msg != null && !msg.isAsynchronous());

            }

            if (msg != null) {

                // 如果当前时间不到 msg 的触发时间,则计算时间差,设置阻塞超时时长

                if (now < msg.when) {

                    // Next message is not ready.  Set a timeout to wake up when it is ready.

                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                } else {

                    // 当前时间到了 msg 的触发时间,则获取消息并返回

                    mBlocked = false;

                    // 如果当前的 msg 不是头部消息,则上一条消息的 next 指向 msg 的 next

                    if (prevMsg != null) {

                        prevMsg.next = msg.next;

                    } else {

                        // 当前 msg 为头部消息,则将下一个 msg 设置为头部消息

                        mMessages = msg.next;

                    }

                    // msg 的下一个 Message 对象置空,表示从消息队列中取出来了这条 msg

                    msg.next = null;

                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);

                    // 标记 msg 正在使用

                    msg.markInUse();

                    return msg;

                }

            } else {

                // 如果没有消息,则设置阻塞时长为 -1,直到被唤醒

                nextPollTimeoutMillis = -1;

            }

            // 所有的消息都被处理后,判断是否退出,并返回 null。

            if (mQuitting) {

                dispose();

                return null;

            }

            // If first time idle, then get the number of idlers to run.

            // Idle handles only run if the queue is empty or if the first message

            // in the queue (possibly a barrier) is due to be handled in the future.

            // 第一次循环时,消息队列为空,或 当前时间未到消息的触发时间,获取 IdleHandler 的数量

            if (pendingIdleHandlerCount < 0

                    && (mMessages == null || now < mMessages.when)) {

                pendingIdleHandlerCount = mIdleHandlers.size();

            }

            // pendingIdleHandlerCount 的数量为 0 时,线程会继续堵塞

            if (pendingIdleHandlerCount <= 0) {

                // No idle handlers to run.  Loop and wait some more.

                mBlocked = true;

                continue;

            }

            // 判断当前空闲时处理任务的handler是否是为空,如果为空,就实例化出新的对象

            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];

            // 释放 IdleHandler 的引用

            mPendingIdleHandlers[i] = null;

            boolean keep = false;

            try {

                // 执行 IdleHandler 的方法

                keep = idler.queueIdle();

            } catch (Throwable t) {

                Log.wtf(TAG, "IdleHandler threw exception", t);

            }

            if (!keep) {

                synchronized (this) {

                    mIdleHandlers.remove(idler);

                }

            }

        }

        // 重置 IdleHandler 的数量为 0,确保不会重复运行它们

        pendingIdleHandlerCount = 0;

        // 在执行 IdleHandler 时,一个新的消息可能插入或消息队列中的消息到了触发时间

        // 所以将 nextPollTimeoutMillis 设为 0,表示不需要阻塞,重新检查消息队列。

        nextPollTimeoutMillis = 0;

    }

}

至此,MessageQueue 的两个最重要的方法已经分析完了,下面来看 Looper 如何循环地从消息队列中取出消息。

# 6. Looper 工作原理

在讲 Looper 之前,需要先理解 ThreadLocal 的工作原理

## 6.1)ThreadLocal 的工作原理

ThreadLocal 是一个线程内存储数据的类,当不同的线程去访问同一个 ThreadLocal 对象时,获得的值都是不一样的,下面用一段代码来证明

private ThreadLocal<String> mThreadLocal = new ThreadLocal<>();

btn_1 = findViewById(R.id.btn_1);

btn_1.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

        new Thread(){

            @Override

            public void run() {

                super.run();

                mThreadLocal.set("Thread_A");

                Log.d("ThreadLocalValue",mThreadLocal.get());

            }

        }.start();

        new Thread(){

            @Override

            public void run() {

                super.run();

                mThreadLocal.set("Thread_B");

                Log.d("ThreadLocalValue",mThreadLocal.get());

            }

        }.start();       

    }

);

我在两个线程中分别存入在 mThreadLocal 中存入了不同的值,然后在控制台输出它们的内容

可见不同线程访问同一个 ThreadLocal 对象得到的值也是不一样的。

ThreadLocal 实现这种特性的原因也很简单,下面来看它内部的 set 方法:

public void set(T value) {

    // 获取当前线程 t

    Thread t = Thread.currentThread();

    // 根据当前线程 t,获取当前线程的 ThreadLocalMap 对象

    ThreadLocalMap map = getMap(t);

    if (map != null)

        // map 不为空,调用 ThreadLocalMap 的 set() 方法。

        map.set(this, value);

    else

        // map 为空,则为当前线程创建一个新的 ThreadLocalMap 对象

        createMap(t, value);

}

在 set 方法中,先获取当前线程,然后获取当前线程的 ThreadLocalMap 对象。getMap() 的 和 createMap() 的实现如下:

ThreadLocalMap getMap(Thread t) {

    return t.threadLocals;

}

void createMap(Thread t, T firstValue) {

    t.threadLocals = new ThreadLocalMap(this, firstValue);

}

那么 ThreadLocalMap 又是什么呢,这里是它的一部分源码:

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {

        /** The value associated with this ThreadLocal. */

        Object value;

        Entry(ThreadLocal<?> k, Object v) {

            super(k);

            value = v;

        }

    }

    // 初始的 table 容量

    private static final int INITIAL_CAPACITY = 16;

    // Entry 数组用于存储数据

    private Entry[] table;

    // table 的大小

    private int size = 0;

    // 负载因子,用于扩容

    private int threshold; // Default to 0

    // 设置负载因子为当然容量大小的 2 / 3

    private void setThreshold(int len) {

        threshold = len * 2 / 3;

    }

    // 初始化 Entry 数组

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

        table = new Entry[INITIAL_CAPACITY];

        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

        table[i] = new Entry(firstKey, firstValue);

        size = 1;

        setThreshold(INITIAL_CAPACITY);

    }

}

可以将 ThreadLocalMap 当作一个哈希表,它的内部用 Entry 存储相应的数据。

在 Thread 的属性中有ThreadLocal.ThreadLocalMap threadLocals = null;,所以每一个线程内部,都持有一个 ThreadLocalMap 对象,系统才可以通过getMap()方法获取当前线程的 ThreadLocalMap 对象。

在 ThreadLocal 中调用 set 方法,实际上会调用 ThreadLocalMap 中的 set 方法,源码如下:

// ThreadLocalMap 的 set 方法

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at

    // least as common to use set() to create new entries as

    // it is to replace existing ones, in which case, a fast

    // path would fail more often than not.

    // 首先获取当前 ThreadLocal 对象的 table 属性,table 一个 Entry 的数组

    // Entry 相当于一个 HashMap,存储了当前 ThreadLocal 对象和 Object 类型的 value 对象

    Entry[] tab = table;

    int len = tab.length;

    // 计算出存储的位置

    int i = key.threadLocalHashCode & (len-1);

    // 遍历 tab

    for (Entry e = tab[i];

        e != null;

        e = tab[i = nextIndex(i, len)]) {

        ThreadLocal<?> k = e.get();

        // 如果 tab 中已经存在了相同的 key 值,就覆盖它原有的 value

        if (k == key) {

            e.value = value;

            return;

        }

        // 如果 当前 entrt 的 key 为 null,调用 replaceStaleEntry 方法清楚所有 key 为 null 的数据

        if (k == null) {

            replaceStaleEntry(key, value, i);

            return;

        }

    }

        // 都不满足,就新建一个 Entry 对象

    tab[i] = new Entry(key, value);

    int sz = ++size;

    // ThreadLocalMap 的容量到达阀值后扩容

    if (!cleanSomeSlots(i, sz) && sz >= threshold)

        rehash();

}

ThreadLocal 中的 get() 方法和 set() 方法一样,都是对 Thread 中对 ThreadLocalMap 进行操作

public T get() {

    // 获取当前线程

    Thread t = Thread.currentThread();

    // 获取当前线程的 ThreadLocalMap 对象

    ThreadLocalMap map = getMap(t);

    if (map != null) {

        // 获取 ThreadLocalMap 中对应当前线程的 Entry 对象

        ThreadLocalMap.Entry e = map.getEntry(this);

        if (e != null) {

            @SuppressWarnings("unchecked")

            // 将 Entry 对象中的 value 取出来

            T result = (T)e.value;

            return result;

        }

    }

    return setInitialValue();

}

private Entry getEntry(ThreadLocal<?> key) {

    int i = key.threadLocalHashCode & (table.length - 1);

    Entry e = table[i];

    if (e != null && e.get() == key)

        return e;

    else

        return getEntryAfterMiss(key, i, e);

}

## 6.2)Looper 中的 prepare() 方法

那么 ThreadLocal 和 Looper 有什么关系呢?我们知道每一个线程都有自己的 Looper,Looper 的作用域就是当前的线程,Android 系统中便通过 ThreadLocal 对象来存储不同线程中的 Looper。

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

    }

    // 将 Looper 对象保存到当前线程的 ThreadLocalMap 当中

    sThreadLocal.set(new Looper(quitAllowed));

}

这里再看一下 Looper 的构造方法

private Looper(boolean quitAllowed) {

    mQueue = new MessageQueue(quitAllowed);

    mThread = Thread.currentThread();

}

可以看到在一个 Looper 中创建了一个 MessageQueue,这里我们就可以搞清楚 Handler、Looper 和 MessageQueue 的对应关系了:

每个线程都有一个 Looper 对象,在 Looper 对象的初始化过程中,会为当前线程创建一个 MessageQueue,而一个线程中可以有多个 Handler。

## 6.3)Looper 中的 loop() 方法:

prepare() 调用后,就是调用 loop() 方法:

/**

  * Run the message queue in this thread. Be sure to call

  * {@link #quit()} to end the loop.

  */

public static void loop() {

    // 通过 Thread Local 获取当前线程的 Looper

    final Looper me = myLooper();

    if (me == null) {

        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

    }

    // 获取当前 Looper 对象的 MessageQueue

    final MessageQueue queue = me.mQueue;

    // 清空远程调用端进程的身份,确保此线程的身份是本地进程的身份,并跟踪该身份令牌

    // 这里主要用于保证消息处理是发生在当前 Looper 所在的线程

    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;

        }

        // 用 logging 打印日志,默认为 null,可通过 setMessageLogging() 方法来指定

        final Printer logging = me.mLogging;

        if (logging != null) {

            logging.println(">>>>> Dispatching to " + msg.target + " " +

                    msg.callback + ": " + msg.what);

        }

        // 开始跟踪,并写入跟踪消息,用于 debug 功能

        final long traceTag = me.mTraceTag;

        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {

            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));

        }       

        ...

        ...

        try {

            // // 通过 Handler 分发消息

            msg.target.dispatchMessage(msg);

            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;

        } finally {

            if (traceTag != 0) {

                // 停止跟踪

                Trace.traceEnd(traceTag);

            }

        }

        if (logSlowDispatch) {

            showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);

        }

        if (logging != null) {

            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

        }

        //  确保在分发消息的过程中线程的身份没有改变

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

    }

}

可以看到 loop() 方法就是不停的遍历消息队列中的消息,当发现有新的消息时,便调用 Handler 的dispatchMessage()方法。

## 6.4)getMainLooper()

public static void prepareMainLooper() {

    prepare(false);

    synchronized (Looper.class) {

        if (sMainLooper != null) {

            throw new IllegalStateException("The main Looper has already been prepared.");

        }

        sMainLooper = myLooper();

    }

}

  /**

    * Returns the application's main looper, which lives in the main thread of the application.

    */

public static Looper getMainLooper() {

    synchronized (Looper.class) {

        return sMainLooper;

    }

}

getMainLooper() 方法用于返回当前 UI 线程的 Looper,UI 线程的 Looper 在 ActivityThread 的建立时通过调用

prepareMainLooper()方法创建。

## 6.5)quit() 和 quitSafely()

在子线程中,如果手动为其创建了Looper,那么在所有消息处理完成之后应该调用 quit() 方法终止消息循环,不然 Looper 就会一直处于等待状态。

public void quitSafely() {

    mQueue.quit(true);

}

public void quit() {

    mQueue.quit(false);

}

可以看到这两个方法都调用了 MessageQueue 中都 quit(boolean safe) 方法,quitSafely 的参数为 true,quit 的参数为 false。

void quit(boolean safe) {

    // 主线程不退出消息循环

    if (!mQuitAllowed) {

        throw new IllegalStateException("Main thread not allowed to quit.");

    }

    synchronized (this) {

        // 如果已经退出了,直接 return

        if (mQuitting) {

            return;

        }

        // 标记为已经退出

        mQuitting = true;

        // 如果 safe 的值为 true,执行完当前的消息后退出消息循环

        if (safe) {

            removeAllFutureMessagesLocked();

        } else {

            // 直接退出消息循环

            removeAllMessagesLocked();

        }

        // We can assume mPtr != 0 because mQuitting was previously false.

        nativeWake(mPtr);

    }

}

quitSafely()会等待当前消息执行完毕后退出消息循环,而quit()方法会直接退出消息循环。

private void removeAllMessagesLocked() {

    // 获取当前 MessageQueue 的头部消息

    Message p = mMessages;

    while (p != null) {

        // 循环遍历所有的 Message

        Message n = p.next;

        // 回收消息,并把消息放入消息池

        p.recycleUnchecked();

        p = n;

    }

    // 将头部消息置为空

    mMessages = null;

}

private void removeAllFutureMessagesLocked() {

    // 获取系统从开机到现在到时间

    final long now = SystemClock.uptimeMillis();

    // 将当前的头部消息赋值给 p

    Message p = mMessages;

    if (p != null) {

        if (p.when > now) {

            // 如果当前头部消息将要执行的时间大于系统开机到现在的时间,则执行 removeAllMessagesLocked() 方法

            // 清空 MessageQueue 队列

            removeAllMessagesLocked();

        } else {

            Message n;

            // 遍历当前的 MessageQueue,直到某个消息的执行时间小于 now 值(即这个消息正在执行)

            // 将这个消息的 next 赋值为 null

            for (;;) {

                n = p.next;

                if (n == null) {

                    return;

                }

                if (n.when > now) {

                    break;

                }

                p = n;

            }

            p.next = null;

            // 回收不会被执行的 Message

            do {

                p = n;

                n = p.next;

                p.recycleUnchecked();

            } while (n != null);

        }

    }

}

终于讲完了,希望大家能通过我的文章,彻底理解 Handler 的机制,但我的能力有限,如果存在错误的地方,还请指出。

零碎的东西很多,为了方便大家记忆,我把上面的内容做成了思维导图,需要的朋友可以保存下来,偶尔看一下,帮助自己记忆。

欢迎关注本文作者:可爱的肥脸

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