清明小长假期间,深圳湾公园被共享单车攻陷,4月3日整个下午人车爆满,公园方估计有超万辆单车进入公园。工作人员全员出动维持秩序但效果不佳。当晚在政府的牵头下,5家共享单车企业负责人一致表示,将连夜把所有共享自行车搬出该区域,各企业将采取后台短信、微信提醒和现场劝导等方式,及时提醒用户注意。
本篇是 小楠 的第二篇投稿,从源码的角度分析了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() 里面是一个死循环:
public static void loop() {
for (;;) {
}
}
这里有一个小小的知识,就是之所以用 for (;;) 而不是用 while(true) 是因为防止一些人通过黑科技去修改这个循环的标志(比如通过反射的方式)
在非主线程里面我们也可以搞一个 Handler,但是需要我们主动去为当前的子线程绑定一个 Looper,并且启动消息循环。
Looper 主要有两个核心的方法,一是 prepare,而是开始 loop 循环。通过 Looper、Handler、Message、MessageQueue 等组成了 Android 的消息处理机制,也叫事件、反馈机制。
我们知道每一个应用程序都有一个主线程,主线程一直循环的话,那么我们的自己的代码就无法执行了。而系统在主线程绑定一个 Looper 循环器以及消息队列,Looper 就像是一个水泵一样不断把消息发送到主线程。如果没有消息机制,我们的代码需要直接与主线程进行访问,操作,切换,访问主线程的变量等等,这样做会带来不安全的问题,另外APP的开发的难度也会提高,同时也不利于整个 Android 系统的运作。有了消息机制,我们可以简单地通过发送消息,然后 Looper 把消息发送到主线程,然后就可以执行了。
消息其中包括:
我们的思路是先分析系统的 Handler,然后再去深入理解消息机制里面各个部件。
主线程与Looper的关系
举个例子,广播:AMS 发送消息到 MessageQueue,然后 Looper 循环,系统的 Handler 取出来以后才处理。(AMS 是处理四大组件的生命周期的一个比较重要的类,在以后我们分析IPC机制以及Activity启动流程的时候会提到)
在 ActivityThread 的成员变量里面有一个这样的 大H(继承Handler),这个就是系统的Handler:
final H mH = new H();
回顾一下 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消息可以自杀,这样来退出应用程序。
case SUICIDE:
Process.killProcess(Process.myPid());
break;
应用程序的退出过程
实际上我们要退出应用程序的话,就是让主线程结束,换句话说就是要让 Looper 的循环结束。这里是直接结束 Looper 循环,因此我们四大组件的生命周期方法可能就不会执行了,因为四大组件的生命周期方法就是通过 Handler 去处理的,Looper 循环都没有了,四大组件还玩毛线!因此我们平常写程序的时候就要注意了,onDestroy 方法是不一定能够回调的。
这里实际上是调用了 MessageQueue 的 quit,清空所有 Message。
public void quit() {
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方法:
public void quit() {
mQueue.quit(false);
}
而消息队列的 quit方法 实际上就是执行了消息的清空操作,然后在 Looper 循环里面如果取出消息为空的时候,程序就退出了:
removeAllFutureMessagesLocked 方法如下:
4、msg.target.dispatchMessage(msg)就是处理消息,这里就会调用Handler的dispatchMessage方法:
在这个方法里面会先去判断 Message 的 callback 是否为空,这个 callback 是在 Message类 里面定义的:
Runnable callback;
这是一个 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 方法,这个方法的默认实现是空处理:
public void handleMessage(Message msg) {}
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是跟主线程绑定在一起的。
主线程一直在循环,为什么没有卡死,还能响应我们的点击之类的呢?
虽然主线程一直在执行,但是我们可以通过外部条件、注入的方法来执行自己的代码,而不是一直死循环。
如图所示,在主线程ActivityThread中的main方法入口中,先是创建了系统的Handler(H),创建主线程的Looper,将Looper与主线程绑定,调用了Looper的loop方法之后开启整个应用程序的主循环。Looper里面有一个消息队列,通过Handler发送消息到消息队列里面,然后通过Looper不断去循环取出消息,交给Handler去处理。通过系统的Handler,或者说Android的消息处理机制就确保了整个Android系统有条不紊地运作,这是Android系统里面的一个比较重要的机制。
我们的APP也可以创建自己的Handler,可以是在主线程里面创建,也可以在子线程里面创建,但是需要手动创建子线程的Looper并且手动启动消息循环。
花了一天的时间,整个Android消息机制源码分析就到这里结束了,今天的天气真不错,但是我选择了在自己的房间学习Android的消息机制,我永远相信,付出总会有所收获的!
每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。
如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。
欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: