Framework | 探索Handler的设计思想
Posted 涂程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Framework | 探索Handler的设计思想相关的知识,希望对你有一定的参考价值。
好文推荐:
作者:JakePrim
Handler 存在的意义?
Handler 实现了App的内部通信,Binder实现了App间的进程通信,这是Framework的核心的技术。
Handler 是做线程间的通信的,那么线程通信一定要用Handler吗?(不一定) Handler 更重要的是实现了线程的切换,MessageQueue 消息队列解决了线程切换中的通信问题。 通过最简单的方式来一步步演进Handler的实现。
如下代码,实现线程间的通信和切换:
下面的代码实现非常简单,线程1和线程2进行通信,只需要一个全局的message变量,线程1无限循环通过state判断线程2是否发送了消息,然后执行相关的方法,这个方法是在线程1中执行的。想·
/**
* 实现最原始的线程切换
*/
public class ThreadChange {
public static void main(String[] args) {
ThreadChange change = new ThreadChange();
change.method();
}
String message;
boolean state;
public void method(){
//线程1
new Thread(new Runnable() {
@Override
public void run() {
//线程1 给线程2发送消息
message = "hello";
//线程2收到消息后,线程1 执行方法
for (;;){//无限循环 监听消息
if (state){
execute();//执行
}
}
}
}).start();
//线程2
new Thread(new Runnable() {
@Override
public void run() {
String data = message;//收到了消息
if (data.equals("hello")){
state = true;
}
}
}).start();
}
public void execute(){
System.out.println(Thread.currentThread());
}
}
那么Handler是如何显示线程的切换和通信的呢?其实Handler就是对线程进行封装处理。
假设要封装一个线程的通信框架,你会如何设计?最简单就是有两个方法一个是发送消息,一个处理消息
如下示例代码:
public class THandler {
public void sendMessage(Message message){
handleMessage(message);
}
public void handleMessage(Message message){
}
}
然后写一个ActivityThread来模拟app的入口:从下面的代码看起来没有任何问题,子线程可以收到主线程发送过来的消息。
public class ActivityThread {
public static void main(String[] args) {
//假设这是在主线程
ActivityThread activityThread = new ActivityThread();
activityThread.method();
}
private THandler handler;
public void method(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
handler = new THandler(){
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
//是在主线程处理的消息
System.out.println("message = " + message.obj);
}
};
}
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发送消息
handler.sendMessage(new Message("hello"));
handler.sendMessage(new Message("hello1"));
handler.sendMessage(new Message("hello2"));
}
}
但是假设消息处理时间很长,由于sendMessage直接调用了handleMessage,如果主线程通过sendMessage发送消息,而handleMessage处理消息是在主线程中处理消息。 假设如下的示例代码:处理消息需要5000ms
handler = new THandler(){
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());
}
};
而实际想要的结果是,消息发送完立即去执行其他的代码,如下示例代码,实际是想要发送完消息就显示页面1、页面2、页面3,由于handleMessage处理消息时间长导致了页面无法显示。
handler.sendMessage(new Message("hello"));
System.out.println("显示页面1");
handler.sendMessage(new Message("hello1"));
System.out.println("显示页面2");
handler.sendMessage(new Message("hello2"));
System.out.println("显示页面3");
那么要如何优化呢?
解决点:
- 处理大量消息问题
- 处理消息不要阻塞主线程,在当前的线程处理消息
对于大量消息的问题,可以使用消息队列的方式来解决,将发送的消息放到消息队列中,然后无限循环获取消息。 这时候框架的架构就变成了如下图所示:
如下示例代码:
/**
* 使用消息队列解决大量消息的问题
*/
public class MessageQueue {
//BlockingQueue 阻塞队列 后续了解,以及线程的了解问题
private BlockingQueue<Message> queue = new ArrayBlockingQueue<>(100);
/**
* 将消息存入消息队列
*/
public void enqueueMessage(Message message){
try {
queue.put(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 从消息队列取消息
*/
public Message next(){
Message message = null;
try {
message = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return message;
}
}
THandler 中的代码变成了如下:
在子线程中调用loop,使handleMessage在子线程中处理消息,不会阻塞主线程。
public class THandler {
private MessageQueue queue = new MessageQueue();
public void loop(){
for (;;){
Message next = queue.next();
handleMessage(next);
}
}
public void sendMessage(Message message) {
queue.enqueueMessage(message);
}
public void handleMessage(Message message) {
}
}
通过loop
来循环获取消息,然后调用handleMessage
处理消息
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
handler = new THandler(){
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());
}
};
handler.loop();
}
});
thread.start();
那么结果就保证了主线程中界面的正常显示,子线程处理消息。
那么子线程向主线程发送消息呢?将代码倒过来就可以了,如下代码:
private THandler tHandler2;
public void method2(){
tHandler2 = new THandler(){
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
System.out.println("主线程处理消息 message = " + message.obj);
}
};
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
tHandler2.sendMessage(new Message("我是子线程发送的消息"));
}
});
thread.start();
tHandler2.loop();//开启消息循环机制
System.out.println("loop后面的代码无法执行,因为loop是一个无限循环");
}
运行结果:
:::tips 会发现一个问题,loop()方法后面的代码无法执行了,因为loop是一个死循环,这个问题后面详细讲解,loop()如何解决主线程阻塞的问题。 ::: 上面的代码看似实现了线程间的通信,没什么问题,但是如果在子线程中创建多个THandler时就存在问题了。如下示例代码
其实我们已经注意到了,loop()方法是一个无限循环,loop()后面的代码都无法执行
private THandler handler;
private THandler tHandler3;
public void method3(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
handler = new THandler(){
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());
}
};
tHandler3 = new THandler(){
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("message = " + message.obj+" thread:"+Thread.currentThread().getName());
}
};
tHandler3.loop();//loop后面的代码都无法执行
handler.loop();
}
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发送消息
handler.sendMessage(new Message("hello"));
System.out.println("显示页面1");
tHandler3.sendMessage(new Message("hello1"));
System.out.println("显示页面2");
tHandler3.sendMessage(new Message("hello2"));
System.out.println("显示页面3");
}
显然只执行了tHandler3
而handler
没有执行handleMessage
,这是因为handler.loop
在tHandler3.loop
后面了,在for(;;)
后面肯定是无法执行的。
难道我们只能在一个线程中定义一个THandler?,这显然是不行的,思考一下导致无限循环的是消息队列,如果想要在一个线程中创建多个THandler,那么消息队列就不能在THandler中创建,而是要和线程绑定,一个线程拥有一个消息队列。
那么问题来了,如何在一个线程中保存一个对象呢?Java正好提供了一个类ThreadLocal
,ThreadLocal提供了两个方法get和set,分别是从当前线程获取某个值,以及从当前线程中存储某个值。
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//存储到当前线程的map集合中 key:ThreadLocal对象 value:存储的任何值
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//根据ThreadLocal对象 获取存储的值。
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
OK,找到了解决方案,下面就开始写代码了,首先要梳理一下,通过创建Looper
类来管理当前线程和消息队列也就是MessageQueue
,我们中需要暴露两个静态方法就可以了一个是prepare
通过ThreadLocal来向当前的线程存储当前类的对象,另一个方法就是loop
开启消息队列循环机制,还可以在加一个方法myLooper
就是通过ThreadLocal
来返回当前的Looper
对象。 Looper类的结构图如下:
代码实现如下:
/**
* Looper和线程绑定一个线程只能有一个Looper,管理队列
*/
public class Looper {
public MessageQueue messageQueue;
//ThreadLocal
private static ThreadLocal<Looper> threadLocal = new ThreadLocal<>();
//保证Looper唯一
public Looper() {
messageQueue = new MessageQueue();
}
/**
* Looper的生命周期是当前线程的生命周期长度
* 通过ThreadLocal保证一个线程只有一个Looper,ThreadLocal,
* 将Looper存储到当前线程的ThreadLocalMap,key是ThreadLocal对象
*/
public static void prepare(){
if (threadLocal.get()!=null){
throw new RuntimeException("Only one Looper my be create per thread");
}
//当前线程 存储Looper
threadLocal.set(new Looper());
}
/**
* 获取当前线程的Looper
* @return
*/
public static Looper myLooper(){
return threadLocal.get();
}
public static void loop(){
//获取当前线程的Looper
final Looper looper = myLooper();
MessageQueue messageQueue = looper.messageQueue;
for (;;){
Message next = messageQueue.next()Android Framework:探索Handler的设计思想
Framework源码面试六部曲:3.Handler面试集合