学不会Handler?那是因为你还没有看过这篇文章

对Android开发者来suo,Handler机制无疑是最重要的知识之一,大家肯定也已经看过诸多有关Handler的教学文章了,为什么你会看到这篇文章?显然是你还没学会,或者忘记了,或者想深究一下。好消息是,这篇文章,绝对会与众不同,一定会让你对Handler机制有更深入的理解。不信?你就读读看

本文将立志成为全网最通俗易懂的Handler机制教学文章!!!

你将会看到与众不同的关于ThreadLocal的讲解!

你将会看到与众不同的Handler机制源码剖析!

流程图贼多的文章,喜欢看图的朋友一定不能错过!

一. 机制介绍

以终为始,还是要问一问为什么,即我们为什么要学习Handler消息机制,Handler消息机制有什么作用?

首先,你要知道,Handler消息机制和Handler不一样哦,Handler只是指Handler这个具体类,而Handler消息机制是包括Handler在内的一系列相关类组成的一个机制。

Handler消息机制有四大作用

  1. 是整个App运行的一个基础
  2. 进行线程间的通信
  3. 发送延时任务
  4. 进行性能优化

下面,分别介绍一下这四个作用

1. 为什么说是整个App运行的一个基础?

我们看下面这个代码

学不会Handler?那是因为你还没有看过这篇文章_第1张图片

ActivityThread的main方法,是App的程序入口。在入口里面,调用了Looper.loop方法,开启了死循环,使得App得以正常运行,而Lopper又是Handler消息机制里面一个很重要的角色,所以说它是整个App运行的基础

2. 关于线程间通信

线程间通信,可谓是Handler的看家本领。它最核心的功能,就是线程间通信,比如子线程向主线程进行通信,以解决子线程无法更新UI的问题。或者主线程向子线程进行通信,以执行一些耗时任务。

3. 关于发送延时任务

Handler可以通过一系列xxxDelay方法,发送延时任务。

4. 关于进行性能优化

可以通过IdleHandler,或者通过重启消息循环以解除App崩溃等方式,进行App的性能优化

二. 角色介绍

Handler消息机制里面,有几个非常关键的角色,需要逐个捋清。下面,咱们挨个说说

1. Message

它是具体的消息类。看源码,对Message类的定义

总结一下,这个源码定义

(1)Message是可以被发送给Handler来进行处理的一个类

(2)这个类里面包含了一些描述信息(其实就是它的what属性),和一些数据对象(其实就是它的data属性)

(3)获取Message,建议使用Message.obtain()或者Handler.obtainMessage()方法来获取,以实现Message的复用效果

除了这些,还有一些值得注意的点

(1)target属性,存储Message对应的Handler

(2)when属性,确定Message的执行时间

(3)setAsynchronous方法,设置Message是否异步

总之,关于Message,可以这样描述

Message是一个消息类,可以被Handler放到消息队列,也可以被取出,交给Handler来处理。它的what、when、data、target、setAsynchronous等一些属性和方法比较常用,同时它还实现了缓存复用,可以通过obtain来复用Message

2. Handler

老样子,还是看源码的定义

学不会Handler?那是因为你还没有看过这篇文章_第2张图片

这个源码其实告诉了我非常多的东西。建议仔细研读一遍。我试图做一个归纳

(1)Handler唯一绑定一个线程,同时也绑定其MessageQueue、Looper,属于多对一的关系,即一个线程可以对应多个Handler

(2)主要有两个作用,一是调度Message/Runnable,二是进行线程间通信

针对前者,列举了一些调度Message/Runnable的具体方法,还提了一下延时任务

针对后者,说到主线程会专门维护一个MessageQueue,子线程可以通过Handler和主线程通信

总之,关于Handler,可以这样描述

Handler有两个作用,一是调度Message/Runnable(包括分发和处理),二是进行线程间通信。它和线程、MessageQueue、Looper是多对一的关系

3. MessageQueue

从源码中,我们可以了解到

(1)它持有一个会被Looper分发的Message的列表

(2)Message由Handler添加,而非MessageQueue直接添加

(3)可以通过Looper.myQueue()来获取当前线程的MessageQueue

总之,关于MessageQueue,可以这样描述

MessgaeQueue是一个单链表构成的优先级队列,元素为Message,根据Message的when属性来确定优先级。由Handler来添加Message,由Looper来取出Message

4. Looper

学不会Handler?那是因为你还没有看过这篇文章_第3张图片

源码依然告诉了我非常非常多的信息,让我试图归纳一下

(1)此类是用来开启,一个线程中,Message的循环的

(2)线程默认是没有消息循环的,需要手动创建,可以通过调用prepareloop,来开启循环

(3)与Looper交互最多的是Handler

(4)给了一个典型的实现了消息循环的线程案例

总之,关于Looper,可以这样描述

可以理解为一个消息循环器,从MessageQueue中取出消息,交给Handler执行。创建方式是prepare,开启循环的方式是调用loop方法

5. ThreadLocal

学不会Handler?那是因为你还没有看过这篇文章_第4张图片

这里的源码,略显难懂,但我也试图归纳一下

(1)ThreadLocal是与线程相关的,可以提供线程局部变量

(2)举了一个例子,说明ThreadLocal是如何提供线程局部变量的

只看这些,基本很难懂。因地制宜,对于理解ThreadLocal,我们或许可以不看源码的定义,转而看他的使用。也就是它的get方法,和set方法。

我们只看get,因为看懂了get方法,也就能看懂set方法了

ThreadLocal的get方法

学不会Handler?那是因为你还没有看过这篇文章_第5张图片

get方法就很清晰,比如我们调用ThreadLocal.get()方法,它会这样执行:

首先拿到调用这个方法的线程,然后从这个线程中取出ThreadLocalMap对象,然后将ThreadLocal作为key,找到它对应的value,从而作为返回值返回。

那么这里,问题来了,ThreadLocalMap是什么呢?先不看源码,只看名字,大体能猜到,这是一个Map集合,那么既然是Map集合,key和value又分别是什么呢?我们进去看一下

学不会Handler?那是因为你还没有看过这篇文章_第6张图片

可以看到,key就是ThreadLocal本身!

所以,我总结一下。

在每次调用ThreadLocal的get方法时,会首先拿到当前执行的线程,这个线程里面有一个变量,是ThreadLocalMap类型的,key是ThreadLocal,value就是我们想要得到的值

画图来看,就是这样

学不会Handler?那是因为你还没有看过这篇文章_第7张图片

剖析ThreadLocal原理

又有一个问题,同一个ThreadLocal,在不同的线程,调用get方法,得到的value值是不一样的吗?

答案,是的。因为这就是ThreadLocal机制的作用呀,它的作用就是保存线程本地值,在不同的线程,需要映射为不同的值。它的原理,就是因为不同的线程,有不同的ThreadLocalMap,即虽然key一样,但是Map不一样,所以value可以不同

学不会Handler?那是因为你还没有看过这篇文章_第8张图片

类比我们生活中的例子,就像是我们同一个人(同一个ThreadLocal),在不同的环境(不同的Thread),承担着不同的角色(映射出不同的value)。比如我们在家这个环境中,承担着孩子的角色,为人父母的角色,在职场这个环境中,我们就承担着职工,同事的角色。真可谓是“艺术来源于生活,高于生活”呀

所以,我说,不同的线程之间Looper可以不同,是通过ThreadLocal来保证的,这句话,相信你也能很快理解了。就是因为不同的线程,ThreadLocalMap不同而已。

ThreadLocal总结

综上,关于ThreadLocal,可以这样描述

能够映射线程本地变量,映射的原理,就是不同的线程,ThreadLocalMap不同

总结

ok,以上就是针对Handler机制几大角色的非常详细又通俗易懂的解释。这些都只是一个开胃菜,更详细,更有含金量的,还在后面。

接下来,让我们来介绍一下,关于Handler机制的运行原理。打起精神来!各位!!!

三. Handler消息机制的运行原理

在此部分,我会先捋源码,然后给出一个非常详细的图加深理解,以此来讲清楚Handler消息机制的运行原理

一图以蔽之

学不会Handler?那是因为你还没有看过这篇文章_第9张图片

这张图,可以说非常详细、生动地展现了Handler机制整体的运行原理。再结合上文,对各个角色的解释,相信你很容易就能看懂了。那么,对自己要求高的人,肯定还是不满于此,是不是还想扒开源码看看?OK,我来满足你的需求。

Handler消息机制源码

看源码,有个问题,即我们看的线索,怎么找,我们从哪一截开始探入,会比较好理解,不会迷乱在纷乱的源码世界中?这个,不同的人有不同的理解,我认为,可以从Handler放消息,即我们调用sendXXX、postXXX方法开始看起,从Looper交给Handler进行消息处理作为结束,正好按照时间顺序,同时也形成闭环。

所以,先从这里看起

Handler怎么放的消息

也就是图中红色区域

学不会Handler?那是因为你还没有看过这篇文章_第10张图片

在前文,Handler源码的定义中,有介绍到Handler发送消息的几种方式,我再贴一下

一般来说呢,我们使用post、sendMessage这两种方法会多一些,看方法名我们也可以猜出来,其他几种方法,基本都是这两种方式的变形体,所以呢,我们只关注post方法,和sendMessage方法。

sendMessage方法的执行过程

由简到繁,我们先看sendMessage方法的执行过程

学不会Handler?那是因为你还没有看过这篇文章_第11张图片

这里调用了sendMessageDelayed()方法,将msg传进去了,然后有一个delayMills为0,这个delayMills的意思就是延时任务的延时时间,前面也介绍了,Handler有一个功能是可以执行延时任务。那么延时的时间,就是sendMessageDelayeddelayMills参数。

那进去sendMessageDelayed方法

学不会Handler?那是因为你还没有看过这篇文章_第12张图片

这个方法又调用了一个sendMessageAtTime

学不会Handler?那是因为你还没有看过这篇文章_第13张图片

传入了一个uptimeMills参数,这个参数,是之前的delayMills,加上启动后的时间。所以前面的delayMills是一个相对时间,然后再加上SystemClock.uptimeMillis(),组成了一个绝对时间uptimeMills。(其实这在后面会被赋值为Message的when属性)

然后调用了Handler的enqueueMessage方法

学不会Handler?那是因为你还没有看过这篇文章_第14张图片

最后,调用到了MessageQueue的enqueueMessage方法。自此,就把Message放入消息队列了。

一图以蔽之

学不会Handler?那是因为你还没有看过这篇文章_第15张图片

让我来总结一下,调用sendMessage,需要传入一个Message对象,然后通过层层传递,最终调用到了MessageQueue的enqueueMessage方法,将Message加入消息队列。

post方法的执行过程

看完sendMessage,我们再看post方法。

一般来说呢,我们是这样调用post方法的

学不会Handler?那是因为你还没有看过这篇文章_第16张图片

我们进去post方法,看一看

what???它居然又调用了sendMessageDelayed方法!我们需要看下这个getPostMessage做了啥

学不会Handler?那是因为你还没有看过这篇文章_第17张图片

哦~,原来它把Runnable封装为了Message,并且将自身作为了Message的callback属性!

后续流程都是一样的了,就不继续跟了。

一图以蔽之

学不会Handler?那是因为你还没有看过这篇文章_第18张图片

好,以上我们就把Handler将Message传递给MessageQueue的过程,捋清楚了。

MessageQueue怎么调度的消息

接下来,我们看MessageQueue是怎么把Message加进去的,怎么把Message取出来的,即图中的这一部分

学不会Handler?那是因为你还没有看过这篇文章_第19张图片

MessageQueue的放Message的方法,是enqueueMessage,看名字就知道,它的意思是将Message放入队列,里面一定包含了一些策略。MessageQueue的取出Message方法,是next。下面,我们还是按照时间顺序,先enqueueMessage方法,然后再看next方法。

enqueueMessage方法的执行过程

enqueueMessage方法,有点大,截图截不开,我就直接贴代码了。别担心,我会在关键地方,留下注释

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        // 不允许发送target为null的消息,也就是说这个方法放不了同步屏障消息
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        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; // 这里将when赋值给了message的when属性
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 把传入的message,作为新的头节点
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; // 如果之前是阻塞状态,则唤醒
        } else {
            // 下面的英文注释说的非常详细,我可以再试图总结一下
            // 只有队列的头节点为同步屏障消息,并且当前message,是最早加进来的异步消息时,才有可能需要唤醒
            // 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;
                }
            }
            // 把传进来的message插入了队列中
            msg.next = p; 
            prev.next = msg;
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

建议你仔细看几遍以上代码,然后,看下面的流程图

一图以蔽之

学不会Handler?那是因为你还没有看过这篇文章_第20张图片

此方法执行完后,如果一切正常的话,就可以将Message入队了。

next方法的执行过程

下面,我们来看,从MessageQueue中取出Message的部分,也就是next方法的执行过程

一样的,我还是直接贴代码,不截图了,因为一屏截不下

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    // 如果应用程序试图在退出后重新启动looper(这是不支持的),就会发生这种情况。
    final long ptr = mPtr; // mPtr是native代码使用的
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0; // 阻塞时间
    for (;;) { // 注意,这里是一个死循环
        if (nextPollTimeoutMillis != 0) {
            // 将当前线程中挂起的Binder命令刷新到内核驱动程序。
            // 在执行可能会阻塞很长时间的操作之前调用此方法非常有用,
            // 以确保所有挂起的对象引用已被释放,以防止进程持有对象的时间超过所需时间。
            // 总之,这个方法在执行阻塞任务之前调用会好,又因为下面有可能会阻塞,所以在这里调用了这个方法。
            Binder.flushPendingCommands();
        }

        // 阻塞,阻塞时长为nextPollTimeoutMillis
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // 发现了一个同步屏障消息,然后遍历,在队列中找到第一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            // 此时,msg要么为null,要么为队首消息,要么为第一个异步消息
            if (msg != null) {
                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 {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // -1表示一直阻塞
                nextPollTimeoutMillis = -1;
            }

            // 当处理完所有该处理的message时,才去处理要不要quit
            if (mQuitting) {
                dispose();
                return null;
            }

            // idle handles 只能在MessageQueue为空,或者队首的Message的when不符合要求时,才会执行
            // 下面的英文其实说的更详细
            // 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.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            // 如果第一次循环,处理了mIdleHandlers里面的任务,那么第二次循环时
            // pendingIdleHandlerCount为0,所以直接continue,而不会重新处理mIdleHandlers
            // 注意:这里的循环指的是某一次next方法执行时,里面for死循环中的一次循环。
            // 而又因为next方法会执行多次,所以第二次next方法执行时,又会重新启动一次for死循环,
            // 那么这时,还是会判断是否要处理mIdleHandlers里面的任务。
            if (pendingIdleHandlerCount <= 0) {
                // No idle Handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue; // 注意一下这个continue。
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 运行IdleHandler里面的代码,只有第一次迭代的时候会执行到这里
        // Run the idle Handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the Handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // 注意这里,不管上面的mIdleHandlers是否还有元素,都重置。因为queueIdle都已经执行过一次了
        // Reset the idle Handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // 设置阻塞时长为0,进入下一次循环
        // While calling an idle Handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}
一图以蔽之

学不会Handler?那是因为你还没有看过这篇文章_第21张图片

出现了!!!你绝没见过的流程图!!! 这个就稍显复杂了,不过多看几遍代码,和我画的流程图做对照,相信你很快就能理解。

需要注意的点,是IdleHandler。在这里,可以看到,它执行的时机,是取不出合适的Message,在阻塞线程之前,执行。同时,执行完之后,会刷新阻塞时长。

所以在这里,它也有坑,

一是有可能永远都不会执行,因为有可能一直都能取出合适的Message。

二是有可能delay正常的Message。比如队首的Message,需要等待1ms就可以执行,然后在阻塞1ms之前,发现有idleHandler可以执行,那么就去执行idleHandler,如果执行idleHandler的耗时为10ms,那么就delay了队首的Message的执行时机,delay了9ms。

尽管它有坑,但还是会在性能优化的场景中用到,用于在线程空闲时执行一些优化任务。

好,以上,关于MessageQueue的拿到Message,和交付Message,就全部介绍完了。

Looper怎么调度的消息

下面介绍Looper的拿到Message,和交付Message的过程,也就是这一部分

学不会Handler?那是因为你还没有看过这篇文章_第22张图片

如何拿到消息

拿到Message,是在开启消息循环,即Looper.loop中实现的

学不会Handler?那是因为你还没有看过这篇文章_第23张图片

可以看到这个方法的注释,谷歌官方特意提示,一定要调用!所以我们在开启子线程的消息循环的时候,一定要调用Looper.loop方法。里面有一个核心逻辑,是死循环,每一次循环,会调用loopOnce方法,让我们进入到这个loopOnce方法中,看一看

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // 这里有可能阻塞
    if (msg == null) {
        // msg为null,只有两种情况,即MessageQueue正在退出或者已经退出
        return false;
    }
    ......
    msg.target.dispatchMessage(msg); // 把message交付给Handler
    ......
    msg.recycleUnchecked(); // 回收message,缓存起来,以便下次复用
    ......
    return true;
}

在每次loopOnce中,一切正常的话,就可以取出一个消息。

如何交付给Handler的

message交付给Handler是怎么交付的呢?Message的target属性就是Handler,所以在这里就是调用了Handler的dispatchMessage方法,进去看下

学不会Handler?那是因为你还没有看过这篇文章_第24张图片

这里就非常明朗了,在这里,进行最终关于message的处理,然后形成了闭环。

一图以蔽之(Looper的loop)

学不会Handler?那是因为你还没有看过这篇文章_第25张图片

细看Handler对Message的处理

其实,这里值得再强调下,我们看到,这里有三种方式来处理message,分别是

学不会Handler?那是因为你还没有看过这篇文章_第26张图片

我们一个一个地看。

处理方式一:handleCallback方法

第一个,handleCallback,是Message的callback属性不为null时,调用。在前面的内容中有过介绍,Message的callback属性,就是调用Handler.post()时,传入的Runnable。可以看下图

学不会Handler?那是因为你还没有看过这篇文章_第27张图片

所以,如果此Message,是通过Handler.post方式传进来的,那么就直接执行Runnable里面的run方法。

学不会Handler?那是因为你还没有看过这篇文章_第28张图片

这是第一种处理message的方式。

处理方式二:mCallback的handleMessage方法

第二种,是看Handler的mCallback属性是否为null,如果不为null,则调用mCallbackhandleMessage方法。这里的mCallback和前面说的Message的callback属性不同。这里的mCallback是创建Handler的时候,通过构造方法得到的,

学不会Handler?那是因为你还没有看过这篇文章_第29张图片

这个Callback和Runnable没有关系,而是一个自定义的接口,里面有一个方法,是handleMessage

学不会Handler?那是因为你还没有看过这篇文章_第30张图片

所以第二种处理方式,是调用mCallbackhandleMessage方法。如果返回了true,则处理结束,如果返回了false,那么就进入到第三种处理方式,即handleMessage

处理方式三:子类实现handleMessage方法

学不会Handler?那是因为你还没有看过这篇文章_第31张图片

这里默认实现为空,所以此方式,一般是调用的子Handler的handleMessage方法,来处理的Message。

一图以蔽之

学不会Handler?那是因为你还没有看过这篇文章_第32张图片

为什么分了三种处理方式?

我个人认为,有两个原因。

(1)保证消息最终能够进行处理

Handler可以通过多种方式进行创建:比如可以直接new,或者new一个子类,同时呢,Handler的构造函数还有好几种。每一种创建方式,对应的消息处理方式,不一定是相同的。所以,分了三种,原因之一,是要把所有的消息处理方式全部覆盖到,尽可能防止有消息处理不到的情况。

(2)有些场景,可能需要对message多次处理,有第二种和第三种处理方式,可以满足此需求。

在代码上,关于第二种和第三种处理方式,分别是这样实现的

 // 第二种处理方式,赋值给Handler的mCallback属性
val Handler1 = Handler(object : Handler.Callback {
    override fun handleMessage(msg: Message): Boolean {
        return true/false
    }
})

// 第三种处理方式,派生子类,重写handleMessage方法
val Handler2 = object : Handler(){
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
    }
}

这两种处理方式有一种关系,就是如果第二种处理方式返回了false,那么第三种处理方式还要执行。即有可能两种处理都会执行,这在某些降级处理场景,可能会被用到。

但其实呢,谷歌提示我们尽量避免创建子类,进而避免调用子类的消息处理方法,而优先用前两种处理Message的方式。原因来自这个注释:

学不会Handler?那是因为你还没有看过这篇文章_第33张图片

在Callback的类注释中,有“尽量避免不得不实现Handler的子类”这样的字眼,所以得出了上面那个结论。

所以,为什么分了三种处理方式,总结下,就是两个原因

  • 保证消息最终能够进行处理
  • 有些场景,可能需要对Message多次处理,有第二种和第三种处理方式,可以满足此需求。

以上,就把Handler机制的全貌,都捋了一遍了。

三. 加餐:Handler那些事儿

1. 为什么子线程不能访问UI,非要整一个Handler?

因为在Android中,关于UI操作,是没有加锁的,所以不是线程安全的,所以只能是单线程访问才可以。因此Android只能单线程修改UI,而且这个单线程只能是主线程。

那么,为什么不加锁呢?

我能想到的一个很重要的原因,就是流畅性,UI修改一定要是瞬时就要变化的,如果有多线程互相抢着锁,那UI的访问及修改效率都会低很多,手机就会表现的很卡。

而且像UI这种,非常基础的能力,没必要做的这么复杂,如果访问一个UI控件,修改一个UI控件都要加锁的话,那就太复杂了,完全没必要。

总结

Android只能允许单线程修改UI,然后设计了Handler机制来实现子线程修改UI的需求。

2. MessageQueue的阻塞唤醒机制是如何实现的?

其实就是:epoll、pipe机制

什么是pipe?

在类Unix-like操作系统中,一切皆文件,包括管道pipe。管道可以像文件一样在文件系统中存在,并且可以使用文件描述符来引用它们。

管道的作用之一,就是可以实现,线程间的通信。一个线程与另外一个线程之间发生的读、写操作,都可以通过管道,来实现。

什么是epoll?

epoll 是 Linux 中的高效的 I/O 多路复用机制,它会监听一个或多个文件描述符的多个事件类型,其中之一是文件描述符的写入事件。

epoll如何实现阻塞、唤醒?

epoll能够实现阻塞、唤醒的原理,就是能够监听管道这个文件描述符的读操作和写操作,实现阻塞和唤醒。

比如,一个MessageQueue中,没有了消息,那么就应该被阻塞了,那么怎么保持一直阻塞的?什么时候会被唤醒?答案就是:epoll可以监听,pipe的文件描述符的写操作。

当有新的消息写入时,epoll就可以监听到,从而通知线程,实现唤醒。没有写入时,就能保持一直阻塞。

因此,可以这么理解:在没有数据可读时,MessageQueue主要使用了 native 层的 epoll 机制来监听文件描述符(通常是pipe)的写入事件,以实现持续的阻塞和唤醒

一个更好理解的例子

举个生活中的例子,当有人给我打过来电话时,我的手机会通过亮屏,响手机铃声或者震动等方式来通知我。

OK,这里面,包含了两个方面。一是可以打电话,有电话线这条“管道”。二是可以通知我,让我知道,有人打电话过来了。

在这里,电话线就像是pipe,通知就像是epoll。在没有电话打来时,且我当前没有正在接听电话时,属于“读阻塞”的状态,此时如果有写入操作,则会进行唤醒。这里的写入,就是有新的人给我打来了电话,然后epoll通过通知我,唤醒了我,然后我就可以接听电话,即对写入的这条消息进行处理。同时呢,手机不光是在收到电话的时候可以通知,在比如收到微信视频通话的通知时,来了一条短信时,都可以发起通知,这对应epoll的可以监听多个文件描述符的事件的特点。

在MessageQueue中,哪些地方进行了阻塞

next方法中,总共有两个地方,对mBlocked进行了赋值。

  • 一是消息正常取出时,将mBlocked赋值为false,表示不阻塞。
  • 二是当没有可取出的消息,且也没有可以处理的IdleHandler时,赋值为true,表示阻塞。

学不会Handler?那是因为你还没有看过这篇文章_第34张图片

3. 同步屏障消息和异步消息?

同步屏障消息即Message的target属性为null

异步消息即Message的isAsynchronous返回true

两者之间是如何运转的?

在没有遇到同步屏障消息时,同步消息和异步消息都是正常在MessageQueue上面根据when属性排列,依次取出,执行。然而,当遇到同步屏障消息时,就会向后遍历,找到第一个异步消息,然后根据when是否满足要求,决定如何处理。

学不会Handler?那是因为你还没有看过这篇文章_第35张图片

这里呢,这种机制,一般会用在优先级比较高的,需要尽快执行的任务,比如绘制UI(draw、invalidate、requestLayout等方法)

4. 消息循环如何退出

其实这块的内容非常多,完全可以单拎出一篇文章来。我们先从入口说起。

退出,是通过调用Looper的quit或者quitSafely来实现的。

学不会Handler?那是因为你还没有看过这篇文章_第36张图片

两者都是调用了MessageQueue的quit方法,只是传递的参数不同。让我们进去MessageQueue的quit来瞧一瞧

学不会Handler?那是因为你还没有看过这篇文章_第37张图片

safe参数,顾名思义,就是描述是否安全退出。在这个方法中,首先会判断,是否允许退出,如果不允许,则抛出异常。如果允许,则看当前是否已经在退出中,如果是的话则return,不是,则继续。这里就用到传过来的参数safe了。如果是true,则代表安全退出,否则是非安全退出。让我们看下这两个方法有什么区别?

非安全退出

先看非安全退出,也就是removeAllMessagesLocked方法,它是找到Message队列的头节点,然后依次向后遍历,把所有的message全部回收。非常简单粗暴

学不会Handler?那是因为你还没有看过这篇文章_第38张图片

安全退出

如果是安全退出

学不会Handler?那是因为你还没有看过这篇文章_第39张图片

在这里,它会判断队首的messagewhen属性,和now的大小。如果now小于when,则说明队首的消息不满足被取出的条件,则调用removeAllMessagesLocked方法,将所有的消息全部回收。否则就向后遍历到第一个不符合取出要求的message,将它以及它后面的message全部回收。 也就是说,when满足要求的Message,还是会被Looper正常取出然后交给Handler进行消费。

回收Message后,做了什么?

然后,再回到MessageQueue的quit方法来,我们看到,在回收完之后,还调用了一个nativeWake方法

学不会Handler?那是因为你还没有看过这篇文章_第40张图片

这里的目的其实是唤醒所有阻塞的线程,以免引起不必要的等待,因为消息循环,都已经退出了,所有该回收的消息都回收了,这时候就应该考虑释放线程的一些资源了,此时有可能有一些阻塞的线程,所以要把阻塞的线程全部唤醒,让他们知道,“消息循环已经退出了,你别再等了”。防止这些线程,无限期地阻塞下去。

一图以蔽之

学不会Handler?那是因为你还没有看过这篇文章_第41张图片

退出之后,如果再继续放入消息,会发生什么呢?

我们知道,Handler的放入消息操作,最终会调用到MessageQueue的enqueueMessage方法

学不会Handler?那是因为你还没有看过这篇文章_第42张图片

在方法里面,会判断,mQuitting是否为true,如果为true,则直接回收这个消息,然后返回false

学不会Handler?那是因为你还没有看过这篇文章_第43张图片

所以这时候再放入消息,是不会被取出的,而是放一个回收一个。

退出之后,Looper的loop方法会发生什么呢?

我们都知道Looper的loop是进行消息循环的方法。当退出的时候,MessageQueue的next方法会返回null

学不会Handler?那是因为你还没有看过这篇文章_第44张图片

此时,loopOnce方法会返回false

学不会Handler?那是因为你还没有看过这篇文章_第45张图片

然后,在loop里面,当发现loopOnce返回false时,直接return

学不会Handler?那是因为你还没有看过这篇文章_第46张图片

从而退出消息循环

5. HandlerThread是什么

看类的定义

学不会Handler?那是因为你还没有看过这篇文章_第47张图片

我们可以发现,HandlerThread是一个线程,并且此线程具有消息循环的能力。也就是说,它是一个封装了Handler机制的线程

run方法里,有一个注意点,就是,给mLooper赋值后,调用了notifyAll方法进行唤醒。那么,是在哪里进行的阻塞呢?

学不会Handler?那是因为你还没有看过这篇文章_第48张图片

啊哈!就是这里,当调用getLooper方法,获取Looper的时候,如果发现mLooper为null,则阻塞,直到它被成功赋值,才会唤醒阻塞在这的线程。

6. IntentService是什么

直接从类注释中找答案,最简单也最准确!

学不会Handler?那是因为你还没有看过这篇文章_第49张图片

下面看下这个类的具体内容

@Deprecated
public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    public IntentService(String name) {
        super();
        mName = name;
    }

    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

我们通过类注释,和类内容,可以总结出

它是个Service,并且内部维护了一个HandlerThread。它能够在子线程处理任务,并且只有一个子线程,同时只能处理一个子任务,并且当所有任务处理完时,自动关闭。

所以,它是一个可以在子线程执行耗时任务,同时只能执行一个任务,并且在所有任务执行完后,可以自动停止的Service

7. Handler内存泄漏

什么是内存泄漏?

内存泄漏的根本原因,是这样的:一个长生命周期的对象,持有着一个短生命周期的对象的引用,同时此长生命周期的对象又被其他对象引用着,向上能够追溯到GC Root,从而导致这个短生命周期的对象无法在合适时机回收

Handler发生内存泄漏的原因?

那么在Handler这里,很常见的一种内存泄漏的场景,是这样的:

Handler(长生命周期对象)持有了Activity(短生命周期的对象)的引用,同时Handler向上能够被主线程(GC Root)引用着,导致Activity无法在合适时机进行回收。

那么这个引用链,是这样的:

主线程-ThreadLocal-Looper-MessageQueue-Message-Handler-Activity

所以导致有可能发生内存泄漏。

如何解决Handler的内存泄漏问题?

解决方案有三个:

(1)不要用非静态内部类来使用Handler,不然Handler就会持有Activity的引用。

可以用静态内部类或者独立的外部类的方式,创建Handler,这样创建的Handler就不会自带Activity的引用了

(2)如果非要持有Activity引用,那么可以用弱引用包裹Activity

因为弱引用在垃圾回收时是一定会被回收的,所以可以解决内存泄漏的问题

(3)在短生命周期对象(比如Activity)结束时,释放Handler。

这种方式虽然可以解决,但是不太推荐,因为难以维护,容易忘记。如下:

@Override
protected void onDestroy() {
  if(mHanlder != null){
      mHandler.removeCallbacksAndMessages(null)
  }
  super.onDestroy();
}

8. 为什么死循环不会导致ANR?

来了来了,非常经典的问题!!!

为什么消息循环是死循环?

死循环是为了能够保证线程能持续运行下去,而不是执行完就退出了。

为什么这个死循环没有导致ANR?

这里的死循环并不满足ANR的条件。Android的ANR机制,它会检测主线程是否在一定时间内无法响应用户输入。而在消息循环这里,用户在屏幕上操作后,都会通过立即发送一个消息或者其他方式,响应用户的输入。所以这里并不满足Android认定的符合ANR的标准。所以不会ANR。

所以,这是两个维度,死循环只是为了能够让程序一直运行下去。和ANR没有关系

所以真正触发ANR的并非Looper的死循环,而是某一次消息执行时阻塞了很久,即run方法或者handleMessage方法停滞了很久,比如在run方法或者handleMessage方法里面有一个死循环,导致主线程无法在一定时间内响应用户输入。

死循环,不会很耗费资源吗?

在这里也不会很消耗资源,因为在空闲时间会进行阻塞,在需要的时候会进行唤醒。就算有阻塞,用户做了什么操作,也是可以立刻向线程的MessageQueue发送消息,并唤醒线程的。

总结

以上,我就把关于Handler机制的核心知识全部介绍完了,此文花费了我大量精力,如果能对你有所帮助,那是我的荣幸!同时,如果有什么问题,也欢迎在评论区讨论。

如果你还没有掌握Framework,现在想要在最短的时间里吃透它,可以参考一下《Android Framework核心知识点》,里面内容包含了:Init、Zygote、SystemServer、Binder、Handler、AMS、PMS、Launcher……等知识点记录。

《Framework 核心知识点汇总手册》:https://qr18.cn/AQpN4J

Handler 机制实现原理部分:
1.宏观理论分析与Message源码分析
2.MessageQueue的源码分析
3.Looper的源码分析
4.handler的源码分析
5.总结

学不会Handler?那是因为你还没有看过这篇文章_第50张图片

Binder 原理:
1.学习Binder前必须要了解的知识点
2.ServiceManager中的Binder机制
3.系统服务的注册过程
4.ServiceManager的启动过程
5.系统服务的获取过程
6.Java Binder的初始化
7.Java Binder中系统服务的注册过程

学不会Handler?那是因为你还没有看过这篇文章_第51张图片

Zygote :

  1. Android系统的启动过程及Zygote的启动过程
  2. 应用进程的启动过程

学不会Handler?那是因为你还没有看过这篇文章_第52张图片

AMS源码分析 :

  1. Activity生命周期管理
  2. onActivityResult执行过程
  3. AMS中Activity栈管理详解

学不会Handler?那是因为你还没有看过这篇文章_第53张图片

深入PMS源码:

1.PMS的启动过程和执行流程
2.APK的安装和卸载源码分析
3.PMS中intent-filter的匹配架构

学不会Handler?那是因为你还没有看过这篇文章_第54张图片

WMS:
1.WMS的诞生
2.WMS的重要成员和Window的添加过程
3.Window的删除过程

学不会Handler?那是因为你还没有看过这篇文章_第55张图片

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

学不会Handler?那是因为你还没有看过这篇文章_第56张图片

你可能感兴趣的:(Android,移动开发,Framework,android,移动开发,framework,架构,Handler)