前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android 异步操作】手写 Handler ( 总结 | Message | MessageQueue | Looper | Handler ) ★

【Android 异步操作】手写 Handler ( 总结 | Message | MessageQueue | Looper | Handler ) ★

作者头像
韩曙亮
发布2023-03-28 18:48:57
2840
发布2023-03-28 18:48:57
举报

文章目录

一、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());
    }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-11-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 一、Message 消息
  • 二、Handler 消息处理者
  • 三、MessageQueue 消息队列
  • 四、Looper 循环者
  • 五、关于 Looper 线程本地变量的说明
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档