Android的Handler线程间通信作为面试必问,重要性不言而喻。作为开发者如何理解和利用进程间通信就变得尤为关键。本文将分三个部分剖析:使用方式、原理分析,如何利用。
使用方式
Handler的使用方式很简单
- 处理消息
//处理接受到的message
var handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
}
}
//或
var handler1 = Handler(Looper.getMainLooper(), object : Handler.Callback {
override fun handleMessage(msg: Message): Boolean {
return true
}
})
代码中可以看到主要有两种方式,一种是重写handleMessage()方法,一种是实现Handler.Callback接口。这两种方式在优先级上有所不同,当实现Handler.callback接口时将优先执行接口的方法,当返回flase时才会执行重写的handleMessage()方法。两种方式中共有的参数,是表明使用的时哪个线程的Looper,最后就会在哪个线程执行方法。
- 发送消息
val obtainMessage = handler.obtainMessage()
// 设置传入的数据
obtainMessage.what = 1
handler.sendMessage(obtainMessage)
//或
handler.post(object : Runnable {
override fun run() {
}
})
发送消息同样有两种方式,一种是获取message,传入数据然后发送,还有一种是直接post传入一个Runnable对象。就这两种方式而言,本质是一样的,第二种方式依然会转换成一个Message对象。但区别在于,如果handle重写了handleMessage()方法或者实现了Handler.Callback接口,那么将不会执行,只会执行post的run()方法。
原理分析
组成部分
从使用的代码来看,线程间通信功能的实现至少有:
-
Handler
用于处理消息的 -
Looper
循环,用于分发消息 -
Message
消息的载体
但是只有以上的组件,并不能完成这个功能。从逻辑上来看:1、线程2的Looper分发线程1Message到handler中去处理。2、在handler发送线程1的Message。在这两条逻辑上似乎缺少东西联系在一起。Looper从哪里获取到Message,handler把Message发送到哪里去。这时就需要第4个组件:
-
MessageQueue
存储Message,由Looper获取
所以整个的通信流程如图所示
![](https://img.haomeiwen.com/i4819282/c047c7d36ecbda35.png)
具体实现
以主线程为例,在我们的App启动时最先执行的时启动Looper
//android.app.ActivityThread#main
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
上面就是启动Loop的主要方法,同时会创建ActivityThread对象,在这个对象中就有Handler对象。
代码中的prepareMainLooper()是一个关键代码。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
这段代码会先判断是否已有主线程的Loop,没有则会通过myLooper()创建所在线程的Looper,然年后再把Loop赋值给静态变量sMainLooper ,通过这个静态变量就可以跨线程获取主线程的Looper。而myLooper()只能获取当前线程的方法。原因就在于prepare(),和ThreadLocal对象。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 把当前线程创建的Looper放入ThreadLocal中
// 这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程
//(通过其get或set方法)都有自己独立初始化的变量副本
sThreadLocal.set(new Looper(quitAllowed));
}
Handle中主要的方法为handleMessage()、dispatchMessage()以及各种发送message方法。
public void handleMessage(@NonNull Message msg) {
}
//表明 了消息处理的顺序
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
MessageQueue中主要有5类方法
- 创建MessageQueue
nativeInit() Native方法 - 添加Message进入队列
enqueueMessage() - Message出队列
next() - 删除队列中的Message
removeMessage() - 销毁整个队列
__nativeDestroy()__Native方法
这些方法表面MessageQueue是一个容器用来存放Message。
而Message对象,就是用来存放传递数据的容器。
以上是4个组成部分主要的实现方式
组件间关系
知道了组件间的实现,那么组件间的是怎么调用的?
依然从使用来看,首先handler要求传入Looper对象,handler会获取Message对象。所以我们会认为,handler持有Message、Looper对象。但当我MessageQueue对象却无法直接看到。从前面的分析中可以知道,MessageQueue是容器,Looper分发Message,所以Handler持有MessageQueue对象才更为合理,而Looper同样也应该持有MessageQueue对象,才能从其中获取、分发Message,Message同样应持有Handler对象,这个Looper才能知道该把Message发给哪里的Handler。
在查看源码后确实如此(为了方便就不放出源码)
![](https://img.haomeiwen.com/i4819282/85ed34c623849cbc.png)
如何利用
首先,线程间通信的主要功能就是跨线程。其次,在实现上来说,会利用到队列。针对这两个特点,我们可以加以利用。例如,高频发送的命令式异步任务,这个任务需,高频的发送命令,同时命令会有延时操作,而且新的命令会覆盖到已经进入队列但暂为执行的任务。这样的任务就适合直接利用Handler的结构进行解决,而不用自己维护一个队列。
网友评论