您的位置:首页 > 移动开发 > Android开发

从源码角度简析 Android 消息机制

2017-03-16 17:52 288 查看
MessageQueue 源码解析

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 源码
相关文章推荐