文章目录
- 一、Message 消息
- 二、Handler 消息处理者
- 三、MessageQueue 消息队列
- 四、Looper 循环者
- 五、关于 Looper 线程本地变量的说明
一、Message 消息
模仿 Android 中的 Message 基本功能 , 提供 what 与 obj 变量 , 提供一个回收方法 ;
此外 , 还要指明下一个消息 , 以及是哪个 Handler 发送的该消息 ;
代码语言:javascript复制package kim.hsl.handler;
public class Message {
/**
* 消息识别码
*/
int what;
/**
* 消息对象
*/
Object obj;
/**
* 指向下一个消息
*/
Message next;
/**
* 该 Message 使用哪个 Handler 进行发送的
*/
Handler target;
/**
* 回收方法
*/
public void recyle(){
obj = null;
next = null;
target = null;
}
}
二、Handler 消息处理者
Handler 有两个功能 :
功能一 : 发送消息到 Looper 中的 消息队列 MessageQueue 中 ;
代码语言:javascript复制 /**
* 发送消息
* @param msg
*/
public void sendMessage(Message msg){
// 为消息设置发送的 Handler
msg.target = this;
// 向消息队列中放入要执行的消息
mQueue.enqueueMessage(msg);
}
功能二 : 接收 Looper 中的 loop 方法传来的 Message , 并 执行该 Message 代表的任务 ;
Handler 执行 Message 任务 , 具体的执行逻辑需要 用户实现 ; 用户创建 Handler 时 , 需要覆盖 handleMessage 方法 , 在重写的方法中处理不同的 Message 任务 ;
代码语言:javascript复制 /**
* 执行消息对应的任务
* @param next
*/
public void handleMessage(Message next) {
}
Handler 初始化 :
Handler 的功能一 发送消息 , 就是向 消息队列 MessageQueue 中发送消息 , 并将消息放到 MessageQueue 中的 Message 链表队列的最后一个 ;
这就需要 Handler 持有 消息队列 MessageQueue 的引用 ,
消息队列封装在 Looper 中 , 因此需要先拿到 线程本地变量 Looper , 然后从 Looper 中获取对应的消息队列 ;
这里就需要特别注意 , 在初始化 Handler 时 , 需要用到 Looper , 如果 Looper 为空 , Handler 初始化就会失败 ;
因此在 创建 Handler 之前 , 必须先调用 Looper 的 prepare 方法 , 先将 Looper 进行初始化操作 ;
代码语言:javascript复制 /**
* 消息队列
* 该消息队列封装在 Looper 中
* Looper 封装在线程本地变量中
*/
MessageQueue mQueue;
public Handler(){
/*
在 Handler 中需要拿到 Looper
进而拿到 Looper 中的 MessageQueue 消息队列
Handler 的操作就是将 Message 放入 MessageQueue
因此在 Handler 中需要持有 MessageQueue 消息队列的引用
获取 Looper 时 , Looper 必须已经初始化完毕,
也就是已经调用过 prepare 创建了 Looper 并将其放入了线程本地变量
*/
// 获取当前线程中的 线程本地变量 Looper
Looper looper = Looper.looper();
// 获取封装在 Looper 中的 消息队列 MessageQueue
mQueue = looper.mQueue;
}
完整 Handler 代码 :
代码语言:javascript复制package kim.hsl.handler;
public class Handler {
/**
* 消息队列
* 该消息队列封装在 Looper 中
* Looper 封装在线程本地变量中
*/
MessageQueue mQueue;
public Handler(){
/*
在 Handler 中需要拿到 Looper
进而拿到 Looper 中的 MessageQueue 消息队列
Handler 的操作就是将 Message 放入 MessageQueue
因此在 Handler 中需要持有 MessageQueue 消息队列的引用
获取 Looper 时 , Looper 必须已经初始化完毕,
也就是已经调用过 prepare 创建了 Looper 并将其放入了线程本地变量
*/
// 获取当前线程中的 线程本地变量 Looper
Looper looper = Looper.looper();
// 获取封装在 Looper 中的 消息队列 MessageQueue
mQueue = looper.mQueue;
}
/**
* 发送消息
* @param msg
*/
public void sendMessage(Message msg){
// 为消息设置发送的 Handler
msg.target = this;
// 向消息队列中放入要执行的消息
mQueue.enqueueMessage(msg);
}
/**
* 执行消息对应的任务
* @param next
*/
public void handleMessage(Message next) {
}
}
三、MessageQueue 消息队列
Message 链表 : 消息队列 MessageQueue , 内部维护了一个 Message 链表 , 存储的时候只存储第一个 Message 即可 ;
链表插入元素 : 当 Handler 在其它线程调用 sendMessage 方法 , 将 消息 Message 放入 Looper 中的 MessageQueue 时 , 针对该链表的操作就是 , 循环获取链表的下一个元素 , 最终 获取到最后一个元素 , 最后一个元素的 next 为空 ; 将 最后一个元素的 next 设置为本次要插入的 Message , 即可完成消息存储到消息队列的操作 ;
链表元素同步 : 链表为空时 , 取出链表的操作会阻塞 , 调用的是 wait 方法 , 此时有消息加入链表后 , 需要 调用 notify 唤醒阻塞 ;
消息入队的部分代码 :
代码语言:javascript复制 /**
* 该队列是一个链表 , 因此这里只给出第一个 Message 即可
*/
Message mMessage;
/**
* 将 Message 消息加入到 Message 链表中
* @param msg
*/
public void enqueueMessage( Message msg ){
// 因为 该消息队列 可能会有多个线程 通过 Handler 向消息队列中添加消息
// 因此 需要使用同步代码块包裹以下逻辑
synchronized (this){
if( mMessage == null ){
mMessage = msg;
}else{
/*
如果链表不为空
这里需要循环查找消息队列的最后一个消息
将本次传入的 Message msg 参数加入到链表尾部
*/
Message pointer = mMessage;
Message previous = pointer;
for(;;){
// 记录上一条消息, 每次遍历都将本次遍历的记录下来
previous = pointer;
// 将 pointer 指向下一条消息
pointer = pointer.next;
// 此时如果某个 Message 的 下一个元素为空
// 说明该 Message 是消息队列最后一个元素
if(pointer == null){
break;
}
}
// 将本次参数传入的 Message 放到链表最后
previous.next = msg;
}
notify();
}
}
Looper 调用 loop 方法后 , 会一直循环 , 不断地从 消息队列 MessageQueue 中取出 Message 消息 , 然后 将 Message 消息发送给对应的 Handler 执行对应的操作 ;
从 消息队列 MessageQueue 中取出消息 , 也是 取出链表表头 的操作 , 取出该链表的表头 , 然后 将表头设置成链表的第二个元素 ;
消息同步 : 如果当前链表为空 , 此时会 调用 wait 方法阻塞 , 直到消息入队时 , 链表中有了元素 , 会调用 notify 解除该阻塞 ;
代码语言:javascript复制 /**
* 从消息队列中获取消息
* @return
*/
public Message next(){
synchronized (this){
// 本次要获取的消息, 最后要返回到 Looper 中 loop 方法中
Message result;
for (;;){
// 尝试和获取 消息队列 链表中的第一个元素
result = mMessage;
if(result == null){
// 如果当前的 Message 队列为空 , 阻塞等待 , 直到新的消息到来
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// 如果不为空 , 说明已经获取到最终的消息 , 退出循环即可
break;
}
}
// 处理链表逻辑 , 将表头指向下一个 Message
mMessage = mMessage.next;
return result;
}
}
消息队列完整代码 :
代码语言:javascript复制package kim.hsl.handler;
public class MessageQueue {
/**
* 该队列是一个链表 , 因此这里只给出第一个 Message 即可
*/
Message mMessage;
/**
* 将 Message 消息加入到 Message 链表中
* @param msg
*/
public void enqueueMessage( Message msg ){
// 因为 该消息队列 可能会有多个线程 通过 Handler 向消息队列中添加消息
// 因此 需要使用同步代码块包裹以下逻辑
synchronized (this){
if( mMessage == null ){
mMessage = msg;
}else{
/*
如果链表不为空
这里需要循环查找消息队列的最后一个消息
将本次传入的 Message msg 参数加入到链表尾部
*/
Message pointer = mMessage;
Message previous = pointer;
for(;;){
// 记录上一条消息, 每次遍历都将本次遍历的记录下来
previous = pointer;
// 将 pointer 指向下一条消息
pointer = pointer.next;
// 此时如果某个 Message 的 下一个元素为空
// 说明该 Message 是消息队列最后一个元素
if(pointer == null){
break;
}
}
// 将本次参数传入的 Message 放到链表最后
previous.next = msg;
}
notify();
}
}
/**
* 从消息队列中获取消息
* @return
*/
public Message next(){
synchronized (this){
// 本次要获取的消息, 最后要返回到 Looper 中 loop 方法中
Message result;
for (;;){
// 尝试和获取 消息队列 链表中的第一个元素
result = mMessage;
if(result == null){
// 如果当前的 Message 队列为空 , 阻塞等待 , 直到新的消息到来
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// 如果不为空 , 说明已经获取到最终的消息 , 退出循环即可
break;
}
}
// 处理链表逻辑 , 将表头指向下一个 Message
mMessage = mMessage.next;
return result;
}
}
}
四、Looper 循环者
Looper 是 线程本地变量 , 在每个线程中 , 可以通过线程调用 ThreadLocal 变量的 get 方法获取该线程对应的对象副本 , 调用 ThreadLocal 变量的 set 方法 , 设置该线程对应类型的对象副本 ;
Looper 调用 prepare 方法进行初始化 , 在该方法中处理 线程本地变量的先关初始化与设置 ,
如果之前已经初始化过 , 本次调用 prepare 方法是第二次调用 , 则会 抛出异常 ,
如果之前没有初始化过 , 那么创建一个 Looper , 然后调用线程本地变量 ThreadLocal 的 set 方法 , 将该 Looper 对象设置成线程本地变量 ;
代码语言:javascript复制 /**
* 一个线程只能有一个 Looper
* 使用 ThreadLocal 来保存该 Looper
* 是线程内部存储类 , 只能本线程才可以得到存储的数据 ;
*/
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
/**
* 准备 Looper 方法
*/
public static void prepare(){
System.out.println("prepare 创建 Looper ");
// 先进行判断 , 如果当前线程已经有了 Looper , 那就抛出异常
if(sThreadLocal.get() != null){
throw new RuntimeException("当前线程已存在 Looper");
}
// 如果不存在 Looper , 就创建一个 Looper
sThreadLocal.set(new Looper());
}
在 Looper 线程中 , 最后一句代码肯定是 Looper.loop() , 执行该方法后 , 就开启了一个无限循环 ,
不断从 消息队列 MessageQueue 中获取消息 , 然后发送给该 消息 Message 对应的 Handler ,
哪个 Handler 发送的消息 , 就将消息在送回给哪个 Handler ;
消息同步 : 当 消息队列 MessageQueue 为空时 , 无法从消息队列中获取数据 , 此时线程会 阻塞 , 直到有新的消息到来后 , 解除阻塞 ;
Looper 循环遍历消息队列部分代码 :
代码语言:javascript复制 /**
* 不断从 消息队列 MessageQueue 中取出 Message 消息执行
*/
public static void loop(){
System.out.println("开始无限循环获取 Message");
// 获取当前线程的 Looper
Looper looper = Looper.looper();
// 从当前线程的 Looper 获取 消息队列 MessageQueue
MessageQueue messageQueue = looper.mQueue;
// 不断从 消息队列中获取 消息 , 分发到发送消息的 Handler 中执行
for(;;){
// 获取消息队列中的第一个消息
Message next = messageQueue.next();
// 分发到发送该消息的 Handler 中执行
next.target.handleMessage(next);
}
}
完整 Looper 代码 :
代码语言:javascript复制package kim.hsl.handler;
public class Looper {
/**
* 一个线程只能有一个 Looper
* 使用 ThreadLocal 来保存该 Looper
* 是线程内部存储类 , 只能本线程才可以得到存储的数据 ;
*/
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
/**
* 消息队列
*/
public MessageQueue mQueue;
/**
* Looper 构造函数
*/
private Looper(){
mQueue = new MessageQueue();
}
/**
* 获取当前线程对应的 Looper
* @return
*/
public static Looper looper(){
return sThreadLocal.get();
}
/**
* 准备 Looper 方法
*/
public static void prepare(){
System.out.println("prepare 创建 Looper ");
// 先进行判断 , 如果当前线程已经有了 Looper , 那就抛出异常
if(sThreadLocal.get() != null){
throw new RuntimeException("当前线程已存在 Looper");
}
// 如果不存在 Looper , 就创建一个 Looper
sThreadLocal.set(new Looper());
}
/**
* 不断从 消息队列 MessageQueue 中取出 Message 消息执行
*/
public static void loop(){
System.out.println("开始无限循环获取 Message");
// 获取当前线程的 Looper
Looper looper = Looper.looper();
// 从当前线程的 Looper 获取 消息队列 MessageQueue
MessageQueue messageQueue = looper.mQueue;
// 不断从 消息队列中获取 消息 , 分发到发送消息的 Handler 中执行
for(;;){
// 获取消息队列中的第一个消息
Message next = messageQueue.next();
// 分发到发送该消息的 Handler 中执行
next.target.handleMessage(next);
}
}
}
五、关于 Looper 线程本地变量的说明
ThreadLocal 作用是 保存线程私有变量 ;
使用 ThreadLocal 维护一个变量时 , 每个使用该 ThreadLocal 线程本地变量 的线程 , 都会 被分配一个独立的变量副本 ,
每个线程 只 可以 改变本线程内的 变量副本 , 即 ThreadLocal 线程本地变量 ;
1 . ThreadLocal 定义 :
代码语言:javascript复制 /**
* 一个线程只能有一个 Looper
* 使用 ThreadLocal 来保存该 Looper
* 该变量是线程内部存储类 , 只能本线程才可以得到存储的数据 ;
*/
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
2 . ThreadLocal 变量获取 : 调用 ThreadLocal 变量的 get() 方法 , 可以获取该 ThreadLocal 线程本地变量 ;
在 ThreadLocalMap map = getMap(t) 中 , 获取的 ThreadLocalMap 与 Java 中的 Map 集合没有任何关联 , 该类就是为了保存 线程本地变量而在 ThreadLocal 中设置的内部类 ; 在该 ThreadLocalMap 内部类中 , 通过 key 键 , 获取对应 value 值 ;
代码语言:javascript复制public class ThreadLocal<T> {
/**
* 返回 该线程本地变量的 当前线程的变量副本.
* 如果 该线程中对应的 变量没有值, 应该首先初始化该变量值
*
* @return 返回当前线程的线程本地变量值
*/
public T get() {
// 首先通过 Thread 拿到当前的线程
Thread t = Thread.currentThread();
// 通过当前线程 , 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 通过 key 获取指定的 value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
}
3 . ThreadLocal 变量设置 : 调用 ThreadLocal 的 put() 方法 , 可以设置 线程本地变量 ;
4 . Looper 中关于 线程本地变量 的设置 : 在 Looper 中涉及到了 线程本地变量 的设置 ,
Looper 要求每个线程只能保持一个 , 并且各个线程之间的 Looper 相互独立 , 没有任何关联 ;
这就需要 将 Looper 定义成线程本地变量 ;
代码语言:javascript复制public class Looper {
/**
* 一个线程只能有一个 Looper
* 使用 ThreadLocal 来保存该 Looper
* 是线程内部存储类 , 只能本线程才可以得到存储的数据 ;
*/
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
/**
* 准备 Looper 方法
* 其中
* 使用了 sThreadLocal.get() 获取线程本地变量
* 使用了 sThreadLocal.set(new Looper()) 设置线程本地变量
*/
public static void prepare(){
// 先进行判断 , 如果当前线程已经有了 Looper , 那就抛出异常
if(sThreadLocal.get() != null){
throw new RuntimeException("当前线程已存在 Looper");
}
// 如果不存在 Looper , 就创建一个 Looper
sThreadLocal.set(new Looper());
}