Android消息机制源码分析

96
木木00
2017.04.08 08:37* 字数 5302

作者简介  原创微信公众号郭霖 WeChat ID: guolin_blog

本篇是小楠的第二篇投稿,从源码的角度分析了Handler机制,因为涉及源码,内容还是挺多的,需要花费一些时间来阅读理解了。最后,希望对大家有所帮助。

小楠的博客地址:

http://www.jianshu.com/u/70c12759d4fe

前言

很多读者,尤其是初学者特别抗拒去看源码,这里我说明一下为什么要进行源码分析。其中包括下面一些好处:

学习Android源码有助于我们学习其中的设计模式、思想、架构。

熟悉整个源码的架构,有助于我们更加正确地调用Android提供的SDK,写出高效正确的代码。

学习源码有助于我们面试,因为大公司都喜欢问这些。

学习源码有助于我们学习一些黑科技,比如学习插件化的从时候我们需要学习Hook机制,但是学习Hook机制的时候我们需要掌握Activity的启动流程、消息机制等等机制。

我个人觉得,只懂得去调用API,跟掌握API底层的实现,这是一个码农跟高级工程师的区别。只会用API每天只能做很多重复性的工作,但是学习了源码以后,我们能够做到很多原生API做不到的事情,这就是我们所说的黑科技,这样能够让我们的知识面更加广泛,因为,即使一个人天资再好也罢,如果他的见识面不够广泛,很多东西(比如说热更新、插件化、NDK)没有接触过的话,永远只能停留在他所到达的瓶颈上面。

对于像本人一样在做系统APP、系统Framework层开发和定制来说源码可能比较重要,但是这并不意味着做第三方APP的时候就不重要。当然,学习源码需要有一定的耐心,也可能需要你在分析的过程中去画一些图(图片更加直观)、花额外的时间去学习源码用到的设计模式等等,学习源码是一个比较痛苦的事情,因为你会发现掌握了源码并不意味者你就能够一步登天。但是随着亲们慢慢地掌握了整个Android的系统架构的时候,相信你不会后悔当初自己的付出。因为我一直都相信,付出必定会有所收获。

这里扯个题外话,刚刚提到NDK,我觉得NDK也是一块比较重要的模块,它能够利用C/C++来实现Java实现不了,或者用Java去实现的时候效率很低的事情,比如说QQ的变声功能、全民K歌的音频混合、视频处理、直播等等,所以有时间的话我将会写一些关于NDK的文章。

应用程序的入口分析

应用程序的入口是 在ActivityThread 的 main 方法中的(当应用程序启动的时候,会通过底层的C/C++去调用main方法),这个方法在 ActivityThread类 的最后一个函数里面,核心代码如下:

在分析源码的时候,你可能会发现一些 if(false){} 之类的语句,这种写法是方便调试的,通过一个标志就可以控制某些代码是否执行,比如说是否输出一些系统的Log。

在 main 方法里面,首先初始化了我们的 Environment对象,然后创建了 Looper,然后开启消息循环。根据我们的常识知道,如果程序没有死循环的话,执行完 main函数(比如构建视图等等代码)以后就会立马退出了。之所以我们的APP能够一直运行着,就是因为 Looper.loop() 里面是一个死循环:

publicstaticvoidloop() {

for(;;) {    }}

这里有一个小小的知识,就是之所以用 for (;;) 而不是用 while(true) 是因为防止一些人通过黑科技去修改这个循环的标志(比如通过反射的方式)

在非主线程里面我们也可以搞一个 Handler,但是需要我们主动去为当前的子线程绑定一个 Looper,并且启动消息循环。

Looper 主要有两个核心的方法,一是 prepare,而是开始 loop 循环。通过 Looper、Handler、Message、MessageQueue 等组成了 Android 的消息处理机制,也叫事件、反馈机制。

为什么需要这样一个消息机制

我们知道每一个应用程序都有一个主线程,主线程一直循环的话,那么我们的自己的代码就无法执行了。而系统在主线程绑定一个 Looper 循环器以及消息队列,Looper 就像是一个水泵一样不断把消息发送到主线程。如果没有消息机制,我们的代码需要直接与主线程进行访问,操作,切换,访问主线程的变量等等,这样做会带来不安全的问题,另外APP的开发的难度也会提高,同时也不利于整个 Android 系统的运作。有了消息机制,我们可以简单地通过发送消息,然后 Looper 把消息发送到主线程,然后就可以执行了。

消息其中包括:

我们自己的操作消息(客户端的 Handler)

系统的操作消息(系统 Handler):比如启动 Activity 等四大组件(例如突然来电话的时候跳转到电话界面)

我们的思路是先分析系统的 Handler,然后再去深入理解消息机制里面各个部件。

主线程与Looper的关系

举个例子,广播:AMS 发送消息到 MessageQueue,然后 Looper 循环,系统的 Handler 取出来以后才处理。(AMS 是处理四大组件的生命周期的一个比较重要的类,在以后我们分析IPC机制以及Activity启动流程的时候会提到)

系统的Handler在哪里

在 ActivityThread 的成员变量里面有一个这样的 大H(继承Handler),这个就是系统的Handler:

finalHmH=newH();

回顾一下 ActivityThread 的 main 方法可以知道,在 new ActivityThread 的时候,系统的 Handler 就就初始化了,这是一种饿加载的方法,也就是在类被new的时候就初始化成员变量了。另外还有一种懒加载,就是在需要的时候才去初始化,这两种方式在单例设计模式里面比较常见。

下面看系统 Handler 的定义(下方高能,看的时候可以跳过一些case,粗略地看即可):

从系统的 Handler 中,在 handleMessage 我们可以看到很多关于四大组件的生命周期操作,比如创建、销毁、切换、跨进程通信,也包括了整个Application进程的销毁等等。

比如说我们有一个 应用程序A 通过 Binder 去跨进程启动另外一个 应用程序B 的 Service(或者同一个应用程序中不同进程的Service),如图:

跨进程启动Service

最后是 AMS 接收到消息以后,发送消息到 MessageQueue 里面,最后由系统的 Handler 处理启动 Service 的操作:

在 handleCreateService 里通过反射的方式去 newInstance(),并且回调了 Service 的 onCreate方法:

又例如我们可以通过发SUICIDE消息可以自杀,这样来退出应用程序。

caseSUICIDE:Process.killProcess(Process.myPid());

break;

应用程序的退出过程

实际上我们要退出应用程序的话,就是让主线程结束,换句话说就是要让 Looper 的循环结束。这里是直接结束 Looper 循环,因此我们四大组件的生命周期方法可能就不会执行了,因为四大组件的生命周期方法就是通过 Handler 去处理的,Looper 循环都没有了,四大组件还玩毛线!因此我们平常写程序的时候就要注意了,onDestroy 方法是不一定能够回调的。

这里实际上是调用了 MessageQueue 的 quit,清空所有 Message。

publicvoidquit() {    mQueue.quit(false);}

tips:看源码一定不要慌,也不要一行一行看,要抓住核心的思路去看即可。

消息机制的分析

消息对象Message的分析

提到消息机制,在 MessageQueue 里面存在的就是我们的 Message对象:

首先我们可以看到 Message 对象是实现了 Parcelable 接口的,因为 Message 消息可能需要跨进程通信,这时候就需要进程序列化以及反序列化操作了。

Message 里面有一些我们常见的参数,arg1 arg2 obj callback when 等等。这里要提一下的就是这个 target 对象,这个对象就是发送这个消息的 Handler对象,最终这条消息也是通过这个 Handler 去处理掉的。

Message Pool消息池的概念——重复利用Message

Message里面中一个非常重要的概念,就是消息池Pool:

我们通过obtain方法取出一条消息的时候,如果发现当前的消息池不为空,那就直接重复利用Message(已经被创建过和handle过的);如果为空就重新 new 一个消息。这就是一种享元设计模式的概念。例如在游戏里面,发子弹,如果一个子弹是一个对象,一按下按键就发很多个子弹,那么这时候就需要利用享元模式去循环利用了。

这个消息池是通过链表的实现的,通过上面的代码可以知道,sPool永远指向这个消息池的头,取消息的时候,先拿到当前的头sPool,然后使得sPool指向下一个结点,最后返回刚刚取出来的结点,如下图所示:

上面我们知道了消息可以直接创建,也可以通过obtain方法循环利用。所以我们平常编程的时候就要养成好的习惯,循环利用。

消息的回收机制

有消息的创建,必然有回收利用,下面两个是Message的回收相关的核心方法:

recycleUnchecked 中拿到消息池,清空当前的消息,next 指向当前的头指针,头指针指向当前的 Message对象,也就是在消息池头部插入当前的消息。

关于消息的回收还有一点需要注意的就是,我们平时写 Handler 的时候不需要我们手动回收,因为谷歌的工程师已经有考虑到这方面的问题了。消息是在 Handler 分发处理之后就会被自动回收的,我们回到 Looper 的 loop方法 里面:

msg.target.dispatchMessage(msg) 就是处理消息,紧接着在 loop方法 的最后调用了msg.recycleUnchecked() 这就是回收了 Message。

消息的循环过程分析

下面我们继续分析这个死循环:

1、首先拿到 Looper 对象(me),如果当前的线程没有 Looper,那么就会抛出异常,这就是为什么在子线程里面创建Handler如果不手动创建和启动 Looper 会报错的原因。

2、然后拿到 Looper 的成员变量 MessageQueue,在 MessageQueue 里面不断地去取消息,关于 MessageQueue 的 next方法 如下:

这里可以看到消息的取出用到了一些native方法,这样做是为了获得更高的效率,消息的去取出并不是直接就从队列的头部取出的,而是根据了消息的when时间参数有关的,因为我们可以发送延时消息、也可以发送一个指定时间点的消息。因此这个函数有点复杂,我们点到为止即可。

3、继续分析 loop方法:如果已经没有消息了,那么就可以退出循环,那么整个应用程序就退出了。什么情况下会发生呢?还记得我们分析应用退出吗?

在 系统Handler 收到 EXIT_APPLICATION 消息的时候,就会调用 Looper 的 quit方法:

Looper 的 quit方法 如下,实际上就是调用了消息队列的 quit方法:

publicvoidquit() {    mQueue.quit(false);}

而消息队列的 quit方法 实际上就是执行了消息的清空操作,然后在 Looper 循环里面如果取出消息为空的时候,程序就退出了:

removeAllFutureMessagesLocked 方法如下:

4、msg.target.dispatchMessage(msg)就是处理消息,这里就会调用Handler的dispatchMessage方法:

在这个方法里面会先去判断 Message 的 callback 是否为空,这个 callback 是在 Message类 里面定义的:

Runnablecallback;

这是一个 Runnable对象,handleCallback方法 里面做的事情就是拿到这个 Runnable 对象,然后在 Handler 所创建的线程(例如主线程)执行run方法:

Handler(Looper)在哪个线程创建的,就在哪个线程回调,没毛病,哈哈!

这就是我们平常使用post系列的方法:post、postAtFrontOfQueue、postAtTime、postDelayed。其实最终也是通过Message包装一个Runnable实现的,我们看其中一个即可:

通过 post 一个Runnable的方式我们可以很简单地做一个循环,比如无限轮播的广告条Banner:

当然,我们的 Handler 自己也可以有一个 mCallback 对象:

如果自身的 Callback 不为空的话,就会回调 Callback 的方法。例如我们创建 Handler 的时候可以带上 Callback:

如果自身的 Callback 执行之后没有返回 true(没有拦截),那么最后才会回调我们经常需要复写的 handleMessage 方法,这个方法的默认实现是空处理:

publicvoidhandleMessage(Messagemsg) {}

5、最后是回收消息:msg.recycleUnchecked()。所以说:我们平时在处理完handleMessage之后并不需要我们程序员手动去进行回收哈!系统已经帮我们做了这一步操作了。

6、通过上面就完成了一次消息的循环。

消息的发送

分析完消息的分发与处理,最后我们来看看消息的发送:

消息的发送有这一系列方法,甚至我们的一系列post方法(封装了带Runnable的Message),最终都是调用sendMessageAtTime方法,把消息放到消息队列里面:

MessageQueue的进入队列的方法如下,核心思想就是时间比较小的(越是需要马上执行的消息)就越防到越靠近头指针的位置:

消息并不是一直在队列的尾部添加的,而是可以指定时间,如果是立马需要执行的消息,就会插到队列的头部,就会立马处理,如此类推。

关于这一点这里我们可以从MessageQueue的next方法知道,next是考虑消息的时间when变量的,下面回顾一下MessageQueue的next方法里面的一些核心代码:next方法并不是直接从头部取出来的,而是会去遍历所有消息,根据时间戳参数等信息来取消息的。

线程与Looper的绑定

线程里面默认情况下是没有 Looper 循环器的,因此我们需要调用 prepare方法 来关联线程和 Looper:

此处调用了 ThreadLocal 的 set方法,并且 new 了一个 Looper 放进去。

可以看到 Looper 与线程的关联是通过 ThreadLocal 来进行的,如下图所示:

ThreadLocal 是JDK提供的一个解决线程不安全的类,线程不安全问题归根结底主要涉及到变量的多线程访问问题,例如变量的临界问题、值错误、并发问题等。这里利用ThreadLocal 绑定了 Looper 以及线程,就可以避免其他线程去访问当前线程的 Looper 了。

ThreadLocal 通过 get 以及 set方法 就可以绑定线程和 Looper 了,这里只需要传入 Value 即可,因为线是可以通过 Thread.currentThread() 去拿到的:

为什么可以绑定线程了呢?

map.set(this, value) 通过把自身(ThreadLocal以及值(Looper)放到了一个Map里面,如果再放一个的话,就会覆盖,因为map不允许键值对中的键是重复的)

因此ThreadLocal绑定了线程以及Looper。

因为这里实际上把变量(这里是指Looper)放到了Thread一个成员变量Map里面,关键的代码如下:

ThreadLocal的getMap方法实际上是拿到线程的MAP,底层是通过数组(实际上数据结构是一种散列列表)实现的,具体的实现就点到为止了。

如果android系统主线程Looper可以随随便便被其他线程访问到的话就会很麻烦了,啊哈哈,你懂的。

Handler、Looper是怎么关联起来的呢?

我们知道,Looper是与线程相关联的(通过ThreadLocal),而我们平常使用的Handler是这样的:

其实 Handler 在构造的时候,有多个重载方法,根据调用关系链,所以最终会调用下面这个构造方法:

这里只给出了核心的代码,可以看到我们在构造Handler的时候,是通过Looper的静态方法myLooper()去拿到一个Looper对象的:

看,我们的又出现了ThreadLocal,这里就是通过ThreadLocal的get方法去拿到当前线程的Looper,因此Handler就跟线程绑定在一起了,在一起,在一起,啊哈哈。

一般们是在Activity里面使用Handler的,而Activity的生命周期是在主线程回调的,因此我们一般使用的Handler是跟主线程绑定在一起的。

主线程一直在循环,为什么没有卡死,还能响应我们的点击之类的呢?

通过子线程去访问主线程的代码,有代码注入、回调机制嘛。

切入到消息队列里面的消息去访问主线程,例如传消息,然后回调四大组件的生命周期等等。

IPC跨进程的方式也可以实现。

虽然主线程一直在执行,但是我们可以通过外部条件、注入的方法来执行自己的代码,而不是一直死循环。

总结

如图所示,在主线程ActivityThread中的main方法入口中,先是创建了系统的Handler(H),创建主线程的Looper,将Looper与主线程绑定,调用了Looper的loop方法之后开启整个应用程序的主循环。Looper里面有一个消息队列,通过Handler发送消息到消息队列里面,然后通过Looper不断去循环取出消息,交给Handler去处理。通过系统的Handler,或者说Android的消息处理机制就确保了整个Android系统有条不紊地运作,这是Android系统里面的一个比较重要的机制。

我们的APP也可以创建自己的Handler,可以是在主线程里面创建,也可以在子线程里面创建,但是需要手动创建子线程的Looper并且手动启动消息循环。

花了一天的时间,整个Android消息机制源码分析就到这里结束了,今天的天气真不错,但是我选择了在自己的房间学习Android的消息机制,我永远相信,付出总会有所收获的!


文章原创作者GuoLin 书籍推荐

郭林大神原创android 书籍:《第一行代码 android》

淘宝链接:

https://s.click.taobao.com/t?e=m%3D2%26s%3DgKUfuKdAZKocQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67p2n%2BQBNMyE6Rku8%2Bpj6eJall3bs%2B3NRhNHnsKI%2BqxhyM0iVZhTFBom4YIorMPnmg8G0g2OJi%2FzmXHfenomYtn5EW9vzeG8LzfPUwktUBEmkxg5p7bh%2BFbQ%3D&pvid=10_106.6.161.154_3367_1490163222155

guolin_blog