从源码角度简析 Android 消息机制
2017-03-16 17:52
288 查看
MessageQueue 源码解析
enqueueMessage()
next()
Looper 源码解析
prepare()
loop()
quit()、quitSafely()
Handler 源码解析
构造方法
send() 系列
post() 系列
dispatchMessage()
流程一览
Handler 最佳写法
补充
问答
直译过来大概就是:
这里先暂时放在这,我们再来看一看
关键点在第565~576行代码间,我们可以看到我们将新传进来的
所以我们前面所提到的
解决完入队方法,我们再来看看出队方法,
我们看到318行代码处,for 循环是一个死循环,如果消息队列中没有消息,那么循环将会一直阻塞在这里,而如果有消息的话,如代码344~352行代码处所示,该消息对象将会被移出单链表并且被返回。
到此
直译如下:
简单来说,需要通过
根据源码我们可以看到,先从
原来
解决完
我们定位到135行代码可以发现又是一个 for 的死循环,
这里的注释就不详细翻译了,我们查看230~231行的注释可以了解到,调用了
查看424~427行的代码我们可以看到,如果
如猜想假设的一样,第736~738行代码告诉了我们,如果当前消息对象是延迟消息的话,那么我们就调用
至此,
目前笔者接触的比较多的是前四种构造函数,至于后三种(携带有 boolean 参数的构造函数),由于笔者能力有限,就在此处不扩展了,前四种构造函数方法,最终调用的都是如下的两种当中其一的构造函数 ——
代码还是非常好理解的,如果传入一个
73~79行的注释说到 —— 可以使用通过实现
而我们如果通过实现
当然,这两个
其中第七种方法在使用中并不常见,限于笔者能力有限,对第七种方法就不进行扩展了,而上面的六种方法最终也会走入
我们可以看到,在
同样的,日常开发中更多使用的是前四种
我们惊奇地发现,
我们在这里就看到了关键的地方,原来
逻辑还是相当简单的,第一个分支判断,
很简单,也就是调用传入的
而如果我们是通过
这个代码其实就是为了解答我们前面在构造方法中提出的那个问题,如果搞懂了上面的
原因就在走 else 分支的时候它会判断当前的
大致意思就是告诉我们
所以实际上
我们在静态内部类中持有外部类的一个弱引用,需要使用的时候判断一下该弱引用是否为空,如果为不为空的话就执行 Acitivty 中的
在
原博文后续如下,实际有误,感谢 @jingjc 指出。
关于
其实也是一样的,一个线程有且仅有一个
答:以执行时间排序的单链表
问:当 MQ 消息队列中为空时会发生什么情况
答:如果当前取出的消息为空,那么会调用一个 native 方法(nativePollOnce),让当前线程无限制休眠下去。如果此时插入一个一分钟后执行的消息,那么首先会调用一个 native 方法(nativeWake)唤醒主线程,并在 MQ 中插入该消息,下一步就是 MQ 取出消息之后发现该消息是一分钟后才执行的消息,继续休眠一分钟。
enqueueMessage()
next()
Looper 源码解析
prepare()
loop()
quit()、quitSafely()
Handler 源码解析
构造方法
send() 系列
post() 系列
dispatchMessage()
流程一览
Handler 最佳写法
补充
问答
MessageQueue 源码解析
MessageQueue直译过来就是消息队列的意思,但是实际上
MessageQueue的底层实现不是 java 中的不是用的队列,而是使用一个单链表,多说无益,我们从代码的角度来一探究竟。打开
MessageQueue源码,首先是类的介绍 ——
直译过来大概就是:
一个持有通过 Looper 来发送消息队列的低级类,Message 对象不会立即添加进 MessageQueue,而是通过与 Looper 关联的 Handler 对象来添加。 你可以通过 Looper 的 myQueue() 方法找到当前线程的 MessageQueue 对象
这里先暂时放在这,我们再来看一看
MessageQueue的进出队列方法,毕竟
MessageQueue在消息机制中扮演消息队列的角色,其最重要的方法当然就是进出队列方法了,首先看进入队列的方法 ——
enqueueMessage()
关键点在第565~576行代码间,我们可以看到我们将新传进来的
Message对象是根据
when来放入的,最终这个
Message对象所处的位置的前一个
Message对象的
when值比它小,同时我们也可以看到,对于一个
Message对象来说,它内部是使用
next变量持有下一个
Message对象的引用,这里我们可以打开
Message的源码查看到,源码如下:
所以我们前面所提到的
MessageQueue对象底层实现其实就是一个单链表原因就在此。
解决完入队方法,我们再来看看出队方法,
MessageQueue中的出队方法源码如下:
next()
我们看到318行代码处,for 循环是一个死循环,如果消息队列中没有消息,那么循环将会一直阻塞在这里,而如果有消息的话,如代码344~352行代码处所示,该消息对象将会被移出单链表并且被返回。
到此
MessageQueue源码解析就暂时告一段落了,接下来看一下
Looper源码的解析。
Looper 源码解析
打开Looper类源码,类的注释如下图——
直译如下:
一个使线程内消息循环的类,默认情况下线程并没有启动小心循环;创建了一个新的 Looper 对象,需要调用它的 prepare() 方法来开始使得消息循环,然后 loop() 方法就会一直处理消息,直到循环停止。 大部分的消息循环交互是通过 Handler 类实现的。 这里有一个经典的实现 Looper 的线程的例子,使用 prepare() 和 loop() 两个方法让 Looper 能够和普通的 Handler 交互。 class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }
简单来说,需要通过
Looper.prepare()方法才能给当前线程创建
Looper对象,并且需要调用
Looper.loop()方法才能让当前线程的
MessageQueue中的消息循环被处理。这里可能有小伙伴有疑惑,为什么需要使用
Looper.prepare()来创建一个
Looper而不是
new Looper()呢?这个时候我们不妨打开
Looper的
prepare()方法,源码如下 ——
prepare()
根据源码我们可以看到,先从
ThreadLocal中
get()一下,如果当前
get()的值为 null,那么我们就给
ThreadLocal对象
set一个
new Looper()对象,我们再定位到
Looper的构造方法看一下 ——
原来
Looper的构造方法是私有的,不对外暴露的,所以我们不能够使用
new Looper()来创建一个
Looper对象,所以我们还需要注意一个知识点就是 —— 将构造方法私有化的目的就是对于一个线程来说它只能有一个
Looper,那么如何实现只有一个
Looper呢?答案就是通过使用
ThreadLocal类来实现,如果不了解
ThreadLocal类的话,可以参考我的另一篇博客从实例和源码角度简析 ThreadLocal。这里我们看到,
Looper()对象在创建的时候,同时会初始化
MessageQueue并且保存对当前线程的引用。
解决完
prepare()方法后,我们再来看看
loop()方法,源码如下:
loop()
我们定位到135行代码可以发现又是一个 for 的死循环,
Looper不停地从消息队列中拿出消息并处理,而想要退出死循环的唯一方法就是第137~140行代码所以,除非
Message对象为空,才能退出消息循环,而
Message对象非空的情况下都会走到第154行代码 ——
msg.target.dispatchMessage(msg),也就是调用
Handler对象的
dispatchMessage()方法(关于
Message对象的
target字段可参考send() 系列)。那么如何才能让
Message对象为空,或者说怎样才能退出消息循环呢?我们可以看到
loop()方法上方的注释说到 —— 请确认调用了
quit()方法来退出循环,所以关键点就是在
quit()方法了,事实上不止
quit()方法,
Looper同时还提供了
quitSafely()方法,所以如果使得消息不再循环的关键点就在这两个方法上了,我们看看这两个方法的源码,源码如下:
quit()、quitSafely()
这里的注释就不详细翻译了,我们查看230~231行的注释可以了解到,调用了
quit()方法之后
loop将会立即停止,无论消息队列中是否还有消息;而250~253行代码告诉我们,调用
quitSafely()方法,会将消息队列中所有的非延迟消息(没有使用
sendMessageDelayed()或
postDelayed()的方法)处理完再退出循环。那么具体是怎么做的呢?我们可以看到是调用了
MessageQueue对象的
quit(boolean)方法,一个是 false,一个是 true。那么我们就再来看看
MessageQueue的
quit(boolean)方法,源码如下:
查看424~427行的代码我们可以看到,如果
safe值为 true 则走
removeAllFutureMessagesLocked()方法,否则就走
removeAllMessagesLocked()方法(其实从方法名我们就可以看出来,一个是移走将来的消息,一个是移走所有的消息),也就是说
Looper的
quit()方法会走下面的
removeAllMessagesLocked()方法,而
quitSafely()方法会走上面的
removeAllFutureMessagesLocked()方法,其实这里我们可以联想到,
removeAllFutureMessagesLocked()方法终将会调用
removeAllMessagesLocked()方法的,因为当当前消息队列中存在延迟消息的时候,那么这个时候无论
safe参数的值是否为 true,它们的处理方式应该如出一辙,猜想到这,我们查看一下两个方法的源码:
如猜想假设的一样,第736~738行代码告诉了我们,如果当前消息对象是延迟消息的话,那么我们就调用
removeAllMessagesLocked方法移除所有的消息(根据前面的
MessageQueue源码解析我们可以发现
MessageQueue队列底层的实现是一个有序的单链表,而这个单链表的排序就是按照
Message对象的
when值来递增排序的,所以如果当前
Message对象的
when值大于当前系统时间,那么其后的所有
Message对象的
when值必定大于当前系统时间),而如果当前消息并非延迟消息的话,那么我们就往后找,找到延迟消息并回收它以及它往后的所有消息对象。
至此,
Looper源码解析完了。
Handler 源码解析
打开Handler源码,
Handler源码注释太长了,这里就不翻译和截图了,我们直接对它里面中的方法进行剖析,首先先从
Handler的构造函数入手,
Handler一共提供了如下几种构造方法 ——
构造方法
目前笔者接触的比较多的是前四种构造函数,至于后三种(携带有 boolean 参数的构造函数),由于笔者能力有限,就在此处不扩展了,前四种构造函数方法,最终调用的都是如下的两种当中其一的构造函数 ——
代码还是非常好理解的,如果传入一个
Looper对象那么就就在构造函数中保存这个
Looper对象的引用,而如果没有传入
Looper对象的话,那么就使用
Looper.myLooper()获取当前线程的
Looper对象(这个在前面的
Looper源码中已经分析过了),然后就是对传入的
Callback对象保存引用,当然,这个
Callback对象是可以为 null 的。那么这个
Callback对象又是什么呢?
73~79行的注释说到 —— 可以使用通过实现
Callback接口来实例化一个
Handler对象而不用去自己去实现
Hanlder的子类。这就非常好理解了,我们日常中创建
Handler对象的方式常是构造一个匿名内部类重写
Hanlder中的
handleMessage(Message)方法,代码类似如下:
private static Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // do something } };
而我们如果通过实现
Callback接口的话就可以使用另一种方法了 ——
private static Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { // do something } return true; } });
当然,这两个
handleMessage还有一个区别就是重写父类的话该方法是无返回参数的,而如果是实现
Callback接口的话是返回一个 boolean 值的,看第78行源码说到 —— 如果没有想进一步处理的期望的话那么就返回 true。这是个什么意思呢?这个在 dispatchMessage() 中我们会详细谈到,对
Callback的说明在这里先暂告一段落。
send() 系列
下面我们来看Hanlder中的另一个重点 —— 发送消息机制 ——
send()方法系列。实际项目开发中发送消息时我们可能会使用到这种调用 api 的形式 ——
mHandler.sendEmptyMessage(0x123);。
Hanlder给我们提供了丰富的 api 供我们调用,共有七种方法,如下:
其中第七种方法在使用中并不常见,限于笔者能力有限,对第七种方法就不进行扩展了,而上面的六种方法最终也会走入
sendMessageAtTime()方法,而事实上
sendMessageAtTime()方法先是调用
enqueueMessage()方法再是会调用
MessageQueue的 enqueueMessage() 方法将消息压入队列,源码如下:
我们可以看到,在
Hanlder类的
enqueueMessage()方法中,当前
Handler对象赋值给了
Message对象的
target字段,也就是
Message对象会持有当前
Handler的引用。
post() 系列
Handler除了提供大量的
send()系列方法供我们使用同时还提供了大量的
post()方法可供我们使用 ——
同样的,日常开发中更多使用的是前四种
post()方法,那么什么是
post()方法呢?我们查看一下
post()方法的源码:
我们惊奇地发现,
post()方法内部实际上竟然调用的是
send()系列方法(
post()系列其他方法也是大同小异,此处就不再放源码了)!关键点就在于
getPostMessage()方法将一个
Runnable对象转换成了
Message对象,这是怎么一回事呢?我们不妨打开
getPostMessage()源码 ——
我们在这里就看到了关键的地方,原来
Message类中有一个
callback字段,其类型就是
Runnable的,所以
post()方法相比于
send()方法,无非是给
Message对象的
callback字段赋了一个值罢了。
dispatchMessage()
send()系列和
post()系列都是发送消息,而
Handler的处理消息方法就是
dispatchMessage(),源码如下:
逻辑还是相当简单的,第一个分支判断,
Message对象的
callback字段是否为空,那么
Message对象的
callback字段是什么?通过 post() 系列源码分析我们知道了
Message对象的
callback字段实际上就是通过
Handler调用
post(Runnable)系列方法传入的
Runnable接口,也就是说如果我们使用了
post(Runnable)系列方法发送消息的话,该
callback字段就不会为空,此时就会调用
handleCallback()方法,那么我们来看一下
handleCallback(Message)的实现 ——
很简单,也就是调用传入的
Runnable接口的
run()方法。
而如果我们是通过
send()系列方法来发送消息的话,那么这个
callback字段就是为空的。那么就会经过下面的分支,首先会判断
mCallback是否为空,那么
mCallback又是一个什么参数呢?
mCallback就是我们之前构造函数中提到的那个仅含有一个
boolean handleMessage()的那个接口,如果这个接口不为空,且返回 true,那么就结束整个分发的流程,如果这个
mCallback为空或者或
Callback接口的
handleMessage方法返回 false,那么就会执行
handleMessage()方法 —— 也就是我们最常使用的匿名内部类当中的那个
handleMessage()方法。下面来看一个例子 ——
public class MainActivity extends AppCompatActivity { private static final String TAG = "TAG"; private static Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case 0x123: Log.e(TAG, "Handler.Callback.handleMessage()"); break; default: break; } return false; } }) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0x123: Log.e(TAG, "handleMessage()"); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button getButton = (Button) findViewById(R.id.btn_get); getButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() { mHandler.sendEmptyMessage(0x123); } }.start(); } }); } }
这个代码其实就是为了解答我们前面在构造方法中提出的那个问题,如果搞懂了上面的
dispatchMessage流程的话,这里就应该知道,答案输出如下 ——
原因就在走 else 分支的时候它会判断当前的
mCallback是否为空,如果不为空那么就执行它的
handleMessage方法,并且如果这个方法如果返回为 true 的话那么就直接 return 结束当前的事件处理,而如果是 false 的话那么就会继续执行下面的程序,也就是
Hanlder自身的
handleMessage方法。
流程一览
Handler创建时需要当前线程的
Looper已经创建好是因为
Handler的构造方法当中会调用
Looper.myLooper()方法来获取当前线程的
Looper,我们需要使用
Looper.prepare()来给当前线程创建一个
Looper,
Looper创建的时候同时也会创建一个
MessageQueue并且保存当前线程对象的引用,
MessageQueue是一个消息队列,但其实质就是一个单链表,它有进队的
enqueueMessage()方法(该方法有序)放入
Message对象,也有出队的
next()方法。然后应该调用
Looper.loop()方法(一个死循环方法,除非消息队列中没有消息对象了,否则一直调用消息队列的
next()方法使得消息出队被处理),这样就可以开始对消息队列进行循环取出了。当我们调用
send()系列的方法或
post(Runnable)方法(实际上
post()方法后面也是调用
send()方法,它唯一多做了一个地方就是将传入的
Runnable对象赋给了
Message对象的 callback 字段)发送一个消息对象的时候先是通过
enqueueMessage()方法将
Handler对象值赋给
Message对象的 target 再通过
MessageQueue的
enqueueMessage()方法将
Message对象塞入了
MessageQueue,然后
Looper就通过
loop()就开始不停地调用
MessageQueue对象的
next()方法取出
Message对象,并使用
Message的 target(实质是一个
Handler) 对象的
dispatchMessage方法,而该方法先分为两个部分,首先判断该
Message对象的 callback(实质就是
Runnable) 对象是否为空,如果不是为空的话,我们就调用该
Runnable接口所实现的
run()方法,如果为空的话,我们就会判断一下另一个
Callback对象是否为空,这个
Callback对象实质是就是一个含有唯一一个方法,该方法名为
boolean handleMessage()的接口,如果这个
Callback对象不为空,就调用该对象的
boolean handleMessage()方法,并根据返回值判断是否调用
Handler本身的
handleMessage()方法。
Handler 最佳写法
我们可能在日常开发中有以下的陋习 ——private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0x123: Log.e(TAG, "handleMessage()"); break; default: break; } } };
大致意思就是告诉我们
Handler应该被声明为
static的,否则可能会造成内存泄露。事实上这句话可能大家已经在网上看到过很多遍了,但是并没有什么人去解释为什么,下面听我一一道来 ——
Handler如果作为一个 Activity 中的一个匿名内部类,它是对 Activity 一直都会保持一个引用的(匿名内部类会持有对外部类的引用),而
Hanlder的生命周期一般情况下比声明的
Activity要长,所以当
Activity需要被销毁的时候,
Handler还存在对它的引用,此时就会造成内存泄露,那么为什么
Handler的生命周期一般要长于声明它的
Activity的生命周期呢?我们知道
Handler内部是通过
Looper.myLooper()获取到了
Looper对象的值的,而
Looper对象我们又是通过
ThreadLocal获得的 ——
所以实际上
Looper的生命周期是和当前线程的生命周期一样长的(
ThreadLocal的作用域即是以线程为单位的)。所以实际上
Handler的生命周期与声明它的那个线程的生命周期是一致的。所以假如我们在 Activity 的主线程中声明了一个
Handler,那么
Handler的生命周期将会和主线程的生命周期相吻合,所以当我们的 Activity 销毁时,
Handler并不会被销毁,就会导致内存泄露。那么有什么方法来解决呢?既然匿名内部类不行,是因为匿名内部类持有了外部类的引用,那么我们就不妨使用静态内部类,因为静态内部类并没有包含对外部类的引用 ——
public class MainActivity extends AppCompatActivity { private Handler mHandler = new InnerHandler(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } private void handleMessage() { // do something } private static class InnerHandler extends Handler { private WeakReference<MainActivity> mActivity; private InnerHandler(MainActivity activity) { mActivity = new WeakReference<MainActivity>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = mActivity.get(); if (activity != null) { switch (msg.what) { case 0x123: activity.handleMessage(); break; default: break; } } } } }
我们在静态内部类中持有外部类的一个弱引用,需要使用的时候判断一下该弱引用是否为空,如果为不为空的话就执行 Acitivty 中的
handleMessage()方法,这样就可以避免内存泄露了。
补充
至于为什么在主线程中不需要使用Looper.prepare()和
Looper.loop()方法,事实上绝大数的技术文章都已经做过了说明,也就是在
ActivityThread主线程的入口方法
mian()方法已经调用了
Looper.prepareMainLooper()和
Looper.loop()方法创建和开启了主线程的消息循环。
在
Looper源码中我们可以看到,关于
Looper的获取有两种方式,一种是
Looper.getMainLooper(),也就是获取主线程的
Looper,另一种是
Looper.myLooper(),也就是通过
ThreadLocal获取当前线程的
Looper,所以一个线程有且仅有一个
Looper(如果不清楚可以查看我的另一篇博客从实例和源码角度简析 ThreadLocal);
MessageQueue的创建实际上已经在
Looper的构造方法中完成了,并且
MessageQueue的构造方法只是对包内开放,所以我们也无法 new 一个
MessageQueue,所以对于一个线程有且仅有一个
MessageQueue;
原博文后续如下,实际有误,感谢 @jingjc 指出。
关于
Handler的话 ——
其实也是一样的,一个线程有且仅有一个
Handler,不过我们也可以根据某个线程的
Looper获取到该线程的
Handler。
问答
问:MessageQueue(后续简称 MQ) 队列是一个什么样的数据结构答:以执行时间排序的单链表
问:当 MQ 消息队列中为空时会发生什么情况
答:如果当前取出的消息为空,那么会调用一个 native 方法(nativePollOnce),让当前线程无限制休眠下去。如果此时插入一个一分钟后执行的消息,那么首先会调用一个 native 方法(nativeWake)唤醒主线程,并在 MQ 中插入该消息,下一步就是 MQ 取出消息之后发现该消息是一分钟后才执行的消息,继续休眠一分钟。
相关文章推荐
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- 从源码的角度分析Android消息处理机制
- Android异步消息处理机制(二):从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- 从源码角度解析Android消息机制
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解(Handler+Message处理机制)
- 【 Android】handler异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解