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");

那么要如何优化呢?

解决点:

  1. 处理大量消息问题
  2. 处理消息不要阻塞主线程,在当前的线程处理消息

对于大量消息的问题,可以使用消息队列的方式来解决,将发送的消息放到消息队列中,然后无限循环获取消息。 这时候框架的架构就变成了如下图所示:

如下示例代码:

/**
 * 使用消息队列解决大量消息的问题
 */
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");
    }

显然只执行了tHandler3handler没有执行handleMessage,这是因为handler.looptHandler3.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面试集合

思摩尔:中国电子烟行业领导企业致力于探索电子烟减害

深入探索Android中的Handler

怒肝Framework通信篇合集;Handler+Binder+LiveData事件机制

Handler+Binder;看完这一篇就理解了