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;看完这一篇就理解了