Android Framework:探索Handler的设计思想

Posted River_ly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 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是一个无限循环");
    }

运行结果:

会发现一个问题,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();
//            handleMessage(next); 无法在多个Handler中找到
        }
    }
}

上述代码,通过Looper类来管理消息队列MessageQueue,通过prepare方法向当前线程存储Looper对象,通过myLooper方法获取当前线程存储的Looper对象,通过loop方法进行消息队列的循环获取消息。

但是:无法在Looper中调用THandler的handleMessage方法,思考一下如果在一个线程中创建了多个THandler实例,难道要想Looper传递一个THandler实例的列表吗?这样是不现实的,那么如何解决呢?在THandler中有Message消息的引用,而MessageQueue存储了Message引用,所以我们向Message添加一个THander target的引用。

public class Message {
    public String obj;

    public MHandler target;

    public Message() {
    }

    public Message(String obj) {
        this.obj = obj;
    }
}

而在THandler中的sendMessage中通过message的实例,设置THandler的实例,然后通过Looper.myLooper()获取到当前线程的Looper实例,就可以获取到消息队列的MessageQueue的实例,调用enqueueMessage(message)方法添加到消息队列中。

    Looper mLooper;

    public MHandler() {
        mLooper = Looper.myLooper();
    }

    //发送消息
    public void sendMessage(Message message){
        enqueueMessage(message);
    }

    private void enqueueMessage(Message message){
        message.target = this;//保存当前的Handler
        //将消息添加到消息队列中去
        mLooper.messageQueue.enqueueMessage(message);
    }

那么就可以在Looper中的loop方法中拿到THandler的实例对象了,直接调用handleMessage就可以了

    public static void loop(){
        //获取当前线程的Looper
        final Looper looper = myLooper();
        MessageQueue messageQueue = looper.messageQueue;
        for (;;){
            Message next = messageQueue.next();
//            handleMessage(next); 无法在多个Handler中找到
            next.target.handleMessage(next);
        }
    }

通过Looper类就实现了,一个线程只有一个消息队列

测试代码如下:

private MHandler handler;
    private MHandler handler2;

    public void method() {
        //主线程向子线程发送消息
        //子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//创建Looper保证一个线程一个消息队列
                //那么如何在子线程切换到主线程呢?
                handler = new MHandler() {
                    @Override
                    public void handleMessage(Message message) {
                        try {
                            Thread.sleep(1000);//处理消息阻塞
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" message = " + message.obj);
                    }
                };
                //支持多Handler实例
                handler2 = new MHandler() {
                    @Override
                    public void handleMessage(Message message) {
                        try {
                            Thread.sleep(1000);//处理消息阻塞
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" message2 = " + message.obj);
                    }
                };
                //looper会阻塞子线程,导致后面的代码无法运行,所以需要Looper机制,一个线程只能有一个队列
                Looper.loop();//无限循环,在子线程调用了handleMessage,所以handleMessage是在子线程执行的
                System.out.println("无法执行");
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程:"+Thread.currentThread().getName());
        //主线程,向子线程发送消息 发送大量消息 通过阻塞队列,将消息放入队列,不会因为长时间处理消息而阻塞主线程
        handler.sendMessage(new Message("hello"));
        System.out.println("显示1");
        handler.sendMessage(new Message("hello"));
        handler2.sendMessage(new Message("hello"));
        handler2.sendMessage(new Message("hello"));
    }

结果如下:消息都得到了处理。

那么整体的线程间通信框架的封装思路,如下图所示:

上述对THandler的封装处理,其实就是android源码中的Handler的大致思路。其实同THandler还可以看出Handler为什么会造成内存泄漏的问题:

内存泄漏的本质:长生命周期持有短生命周期对象,也就是Message持有了Handler的引用而造成的。

从THandler的持有链来看:

**线程 -> Looper -> MessageQueue -> Message -> THandler -> 其他/Activity **如此长的持有链,Looper和MessageQueue的生命周期和线程的生命周期一样长,这样长的生命周期持有了THandler和其他/Acitivity的短生命周期的引用,是很有可能会造成内存泄漏的。

还可以看出:Handler存在的意义在于:主线程的通信,也是App内部的通信都必须使用Handler,一个线程只有一个Looper,一个MessageQueue(消息队列),有多个Handler的实例。Android所有的主线程通信都是在Handler上运行的,因为Looper.loop()开启了一个死循环,在loop()后面的代码都无法执行,使得主线程一直在运行,而Android中的App内部通信必须使用Handler进行通信,包括启动服务、四大组件、UI绘制和更新都是在Looper.loop()的线程上进行执行的。

Message 设计

在Handler中的Message设计非常巧妙,为什么要设计Message呢?因为在App的内部都是通过Handler进行通信的,然而除了应用层的代码还有底层的其他服务事件等等都需要通过Handler传递Message进行通信,会创建大量的Message,会造成Message不停的创建和销毁,而造成内存抖动,下面来看看如何设计Message的回收与复用机制,以及通过什么的方式创建Message。

在上述自己写了一个线程的通信框架THandler在设计,设计的Message特别简单:通过Object存储数据

public class Message {
    public Object obj;

    public MHandler target;

    public Message() {
    }

    public Message(Object obj) {
        this.obj = obj;
    }
}

我们指定一个线程只有一个Looper和MessageQueue,当使用THandler大量的发送消息时,会频繁的创建Message对象,使Message对象频繁的创建和销毁,从而造成内存抖动

内存抖动:短时间大量的创建和销毁对象会造成内存抖动内存抖动解决方式就是复用。而享元设计模式就是解决主要用于减少创建对象的数量,以减少内存占用和提高性能。

享元模式的设计思路如下:

假设要大量的生成一个类的对象:

public class Circle implements Shape {
    private String color;
    private int x;
    private int y;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public void draw() {
        System.out.println("Circle:" + color + " x:" + x + " y:" + y);
    }
}

通过HashMap记录创建过的对象,当下次在有相同的key则直接复用对象,注意HashMap必须是static.

public class ShapeFactory {
    private static final HashMap<String,Shape> cirMap= new HashMap();

    public static Shape getCir(String color){
        Circle circle = (Circle) cirMap.get(color);
        if (circle == null){
            //如果为null则创建对象
            circle = new Circle();
            circle.setColor(color);
            cirMap.put(color,circle);
        }
        //如果不为空则复用对象
        return circle;
    }
}

其实享元模式非常简单,只需要用一个在内存中一直存在的static对象,根据唯一标识码来存储到内存中即可,用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。其实上述代码还存在问题虽然减少了创建对象的数量,但是没有回收对象的机制,也会存在导致对象越来越多占用内存越来越大。

那么Handler中的Message是如何设计的呢?下面来进行源码的探索。

Message.obtain() 复用Message,Message采用单向链表实现了复用,可以检查是否有可以复用的Message,用过复用避免过多的创建、销毁Message对象达到优化内存和性能。

其实Message中的复用和回收机制是享元模式的一种实现,不一定要使用HashMap来存储对象。Message采用了更简单的单链表的形式对对象进行存储。

那么什么是单链表呢?其实就是在当前对象加上一个next变量,next指向下一个对象。如下代码:

Message具备了享元模式的特性,必须通过static来存储复用对象,以单链表的形式代替唯一标识码进行存储复用对象链表,因为Message对象没有什么特性,不需要唯一标识码。这样Message就具备了对象的复用机制。

	Message next;//典型的单链表,指向下一个对象    
	private static Message sPool;//在内存中保存复用的对象
    private static int sPoolSize = 0;//链表的大小
    private static final int MAX_POOL_SIZE = 50;//链表的最大大小,如果超过了最大的大小则通过new创建

Message实现复用的机制,通过obtain方法:

    public static Message obtain() {
        synchronized (sPoolSync) {//
            if (sPool != null) {//首先判断 如果在内存中的static为空则直接new创建
                Message m = sPool;//m 表示是当前要复用的对象
                sPool = m.next;//将sPool 指到下一个复用对象,供下一个Message使用
                m.next = null;//一定要断开当前的链表,因为外部要使用这个m对象
                m.flags = 0; // clear in-use flag
                sPoolSize--;//链表的大小减一
                return m;
            }
        }
        return new Message();
    }

上述代码设计的非常巧妙,只看代码可能发现不了,直接上图:下图是当前某个时刻的链表结构

当调用obtain()方法时:将链表的头部对象返回,sPool指向下一个复用对象。

看到这里可能存在疑问?sPool没有看到初始化的地方啊?它从哪里初始化的呢?而且对象如何复用的呢?

Message对象是在使用完毕后,存放到单链表复用池的,那么什么地方使用了Message对象呢?就是handleMessage,而调用handleMessage的地方就是Looper.loop()方法

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
         for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
             .......
          	msg.target.dispatchMessage(msg);//调用了Handler.handleMessage
         	msg.recycleUnchecked();//调用了销毁对象的方法
        }
    }

从上述代码可以看到使用完Message后调用了recycleUnchecked方法,其实就是回收对象,将对象复用存储到复用池中。

    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

嗖嘎,没错就在这里,sPool 没有初始化,是因为sPool第一次指向了回收的Message对象,复用了使用完的Message对象,同时还判断了链表复用池的大小,如果超过了MAX_POOL_SIZE,就不会往复用池中添加了,防止无限添加Message对象,导致内存占用越来越大。

sPool 其实不用初始化,如下图就是第一次产生Message对象。

当突然一起并发来了三个obtain(),这时候复用池只有一个Message对象,其中复用池的一个对象被拿去使用,剩下的两个通过new创建了两个Message对象,返回被拿去使用,这时候复用池是空的

当第一个消息处理完毕:

第二个消息处理完毕:

第三个消息处理完毕:

可以看出,形成了一个单向循环链表,最后一个被回收的消息对象最先被复用。

可以得出结论:Handler中的Message,推荐使用Message.obtain()方法复用Message对象,Message类采用享元模式,通过static来使复用池存储在内存中,使用单向循环链表作为复用池

那我们自己写的THandler中的Message就可以优化成如下:

public class Message {
    public String obj;

    public MHandler target;

    Message next;
    private static Message sPool;
    private static final int MAX_POOL = 50;
    private static int sPoolSize = 0;

    public Message() {
    }

    public Message(String obj) {
        this.obj = obj;
    }

    public static Message obtain(){
        if (sPool != null){
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            sPoolSize--;
            return m;
        }
        return new Message();
    }

    public static Message obtain(String msg){
        Message obtain = obtain();
        obtain.obj = msg;
        return obtain;
    }

    public void recycle(){
        if (sPoolSize < MAX_POOL){
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

//Looper
    public static void loop(){
        //获取当前线程的Looper
        final Looper looper = myLooper();
        MessageQueue messageQueue = looper.messageQueue;
        for (;;){
            Message next = messageQueue.next();
//            handleMessage(next); 无法在多个Handler中找到
            next.target.handleMessage(next);
            next.recycle();//回收Message对象
        }
    }

MessageQueue 设计

在上述自己写的THandler中的,MessageQueue消息队列,实现的非常简单,只是解决了大量消息的问题,对于按照时间发送消息和阻塞唤醒都没有实现,那么在Android 中Handler的MessageQueue是如何做到按时间排序发送消息以及阻塞和唤醒的呢?如何重新设计一下自己的MessageQueue呢?

首先,来看MessageQueue如何实现按时间排序消息队列的,在上述Message设计中,Message是一个单向循环链表,而MessageQueue正是基于单向链表进行时间排序的。MessageQueue中有一个Message mMessage的变量而这个变量就是消息队列。

在Handler中通过sendMessageDelayed,按照时间进行发送消息。在(当前时间+ delayMillis)之前的所有挂起消息之后将消息放入消息队列。

threadHandler.sendMessageDelayed(threadHandler.obtainMessage(),1000);

 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
       // SystemClock.uptimeMillis() + delayMillis 转换从绝对的时间
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

将消息按时间放入消息队列正是调用了MessageQueue的enqueueMessage方法

其实实现很简单,当when==0或者小于链表头部的when那么就会将msg插入链表头部。

如果when > 0 ,就会遍历链表当遍历到when < p.when,那么msg就会插入到p的前面。其实就是链表的插入操作。

 boolean enqueueMessage(Message msg, long when){
     //msg 要放入消息队列的消息   when 什么时间执行这个 消息
    .......
     		msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
     ........
 }

如下图所示:

  1. 当when==0或者when<p.when msg插入链表头部

  1. 当when > p.when 时就会遍历链表

那么总结一下:一个线程对应着一个Looper,一个Looper对应着一个MessageQueue,而一个MessageQueue对应一个Message单向链表作为消息队列和时间排序。这样整体的设计清晰明了了。

Message 基于单链表的实现,正是方便了对时间的排序,MessageQueue消息队列对时间排序实现很简单,那么MessageQueue是如何当时间没有到如何实现消息阻塞和唤醒呢?单向链表无法实现阻塞

其实阻塞和唤醒通过底层内核层的代码epoll 机制来实现的。

消息阻塞的Java层实现,其实就是通过JNI调用底层的nativePollOnce方法使其进入阻塞状态。

//出队阻塞的逻辑
Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //下一次进入 阻塞调用Native的方法
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                //.....
                if (msg != null) {
                    if (now < msg.when) {
                        // 阻塞的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ///
                        return msg;
                    }
                } else {
                    // No more messages. 如果没有消息-1表示一直阻塞
                    nextPollTimeoutMillis = -1;
                }
               .....

                if (pendingIdleHandlerCount <= 0) {
                    // 如果没有返回msg则说明要进入阻塞状态,将mBlocked设置为true,当有新的消息来时就会
                    //根据mBlocked==true 唤醒
                    mBlocked = true;
                    continue;
                }
            }
            .....
        }
    }

唤醒机制:通过JNI调用nativeWake进入唤醒状态

//入队唤醒的逻辑
boolean enqueueMessage(Message msg, long when) {
        ....
        synchronized (this) {
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;//mBlocked 决定了是否要唤醒消息
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            // 唤醒next阻塞,执行消息 唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

Loop 消息循环机制

在上述探索线程的通信机制中,发现在Looper类中的loop()方法是一个死循环,那么按照正常逻辑会阻塞主线程,如下代码:假设我们在主线程中开启了loop使用自己写的THandler他是不会执行loop后面的代码的,在Android中主线程也就是UI线程,为什么不会阻塞UI线程呢

    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是一个无限循环");
    }

在Android的App中通过ActivityThread中的main函数中的代码:

    public static void main(String[] args) {
        ......

        Looper.prepareMainLooper();

       	......
        Looper.loop();//死循环 主线程一直会存在
		//如果继续执行抛出异常 主线程都没了 那么整个app就会杀死
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

loop必须要一直执行,保证主线程不会杀死。如果主线程没了那么App肯定也会被杀死,所以在App运行阶段必须要保证主线程一直处于运行的状态,那么Android在主线程是如何更新UI呢?

在Android中的所有的主线程操作都是通过Handler来执行这些操作的,以事件为驱动的操作系统

Looper通过阻塞+任务执行来实现更新UI,当没有任务时进入阻塞状态,当任务来时就会添加到消息队列,loop()死循环查到有新的消息就会唤醒执行任务,阻塞可以让出CPU资源,阻塞和唤醒机制通过了MessageQueue的next方法进行了实现,不会让主线程导致卡死。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    
//在ActivityThread中就是调用了这个方法
    public static void prepareMainLooper() {
        prepare(false);//消息队列不可以quit
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }	
	//创建MessageQueue   获取当前的线程与线程绑定   
	private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
	public static void prepare() {
        //消息队列可以quit	
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            //每个线程只能创建一个Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //将Looper加入到当前的线程中
        sThreadLocal.set(new Looper(quitAllowed));
    }

在上述代码中prepare有两个重载方法,quitAllowed表示的MessageQueue的消息队列是否可以销毁。

MessageQueue的构造方法如下:mQuitAllowed决定队列是否可以销毁,主线程的队列不可以被销毁需要传入false,在MessageQueue的quit方法

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

ThreadLocal 干什么的?

线程上下文的存储变量,线程隔离的工具类,

    public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);//当前线程存储信息
        else
            createMap(t, value);
    }

一个线程只有一个Looper,因为一个Thread线程只有一个ThreadLocalMap<this,value>, this存储的是唯一的ThreadLocal,所以value也是唯一的。

一个线程只有一个唯一的Looper.Looper.ThreadLocal在整个APP的是唯一的,因为他是static final.

在Looper中,一个Looper只有一个MQ.

final MessageQueue mQueue;

那么Handler的MessageQueue来自哪里呢?如下代码来自Looper的mQueue,所以Looper的mQueue是共享在Handler的

public Handler(Callback callback, boolean async) {
        ......

        mLooper = Looper.myLooper();//调用了sThreadLocal.get()获得刚才创建的Looper对象
    	//如果Looper为空则抛出异常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  • 一个线程有几个Handler? 可以有多个Handler,Looper在主线程就只有一个

  • 一个线程有一个Looper,通过ThreadLocal

  • Handler内存泄露的原因:匿名内部类持有了外部类对象

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;//Message会持久Handler而Handler持有了Activity.
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 如果要在子线程new Handler要做什么工作? 主线程持有Looper,而子线程没有Looper 必须调用Looper.praper()在子线程创建Looper.
//MessageQueue    
void quit(boolean safe) {
		.....
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
//Looper
    public void quit() {
        mQueue.quit(false);
    }
  • 消息队列无效是怎么处理呢?

MessageQueue 可以无限制的存放Message,因为系统需要调用如果入队阻塞那么系统功能就无法使用了。出队如果MessageQueue为空就会一直阻塞。线程会阻塞,CPU可以降低CPU的调用该线程,提高CPU的性能。

这也是为甚Looper死循环不会导致应用卡死,因为Looper死循环,当从消息队列中获取next没有消息的时候就会一直阻塞,当有消息入队的时候就会唤醒。

卡死是发生了 ANR,而ANR 是一定的时间内,消息没有处理完,又用Handler发送一个ANR提醒。ANR和阻塞是没有关系的。

而Looper死循环,没有消息处理时就会block(阻塞) 不过是线程没事做了。而ANR 其实也是通过Handler发送了一个消息提醒。

如下代码:

//出队阻塞的逻辑
Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
      //阻塞调用Native的方法
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                //.....
                if (msg != null) {
                    if (now < msg.when) {
                        // 阻塞的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ///
                    }
                } else {
                    // No more messages. 如果没有消息-1表示一直阻塞
                    nextPollTimeoutMillis = -1;
                }
               .....
            }
            .....
        }
    }
//入队唤醒的逻辑
boolean enqueueMessage(Message msg, long when) {
        ....
        synchronized (this) {
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            // 唤醒next阻塞,执行消息 唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
  • 为什么子线程无消息一定要调用quit方法

因为子线程不会向主线程一直运行,当子线程销毁时,一定要将Looper的循环结束掉,下面我们看看是如何处理的:

//Looper
    public void quit() {
        mQueue.quit(false);
    }

//MessageQueue 将mQuitting设置ture并且唤醒阻塞执行next
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			//阻塞调用Native的方法
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                //.....
                if (msg != null) {
                    if (now < msg.when) {
                        // 阻塞的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ///
                    }
                } else {
                    // No more messages. 如果没有消息-1表示一直阻塞
                    nextPollTimeoutMillis = -1;
                }
               // Process the quit message now that all pending messages have been handled.
               //如果mQuitting=true返回null,这个null是关键
                if (mQuitting) {
                    dispose();
                    return null;
                }
               .....
            }
            .....
        }
    }

上述代码在执行Looper.quit()方法后,最终返回了null,那么在loop()方法中的处理,如下代码直接return并且结束掉了无限循环。

    public static void loop() {
       ....

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        }
    }

线程同步

Handler是用于线程间通信,整个APP都是用它来进行线程间的协调。

主要有几个关键点:消息入库(enqueueMessage)2. 消息出库(next)3. quit销毁消息队列

  • MQ入队和出队以及调用quit() 都会加上synchronized锁,另外主线程不能quit(),会抛出异常
//MessageQueue.class
boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
		//上锁,如果发现有线程正在入队列,其他线程要想入队列需要等待
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

synchronized 锁是一个内置锁,说明对所有调用同一个MessageQueue对象的线程来说,他们都是互斥的,在Handler里一个线程对应着一个唯一的Looper对象,而Looper中有只有一个唯一的MessageQueue.所以主线程只有一个MessageQueue对象,也就是说当子线程向主线程发送消息的时候,主线程一次只会处理一个消息,并发的情况下其他线程都需要等待,这样消息队列就不会出现混乱。

再看next函数:

有个疑问,入队列的时候加锁就可以了,为什么出队列还要加锁呢?next加锁主要是可以保证next和enqueueMessage方法能够实现互斥,当一个线程调用对消息队列入队操作,那么next时候主线程中的Looper调用的,必须要等到当前的线程入队完毕,那么Looper才能做出队操作。这样才能够真正的保证多线程访问MessageQueue时有序进行的

Message next() {
        //....
        for (;;) {
            //.... 上锁
            synchronized (this) {

            }

           //....
        }
    }

消息机制之同步屏障

在上述的学习中,线程的消息都是放在同一个MessageQueue里面,取消息的时候互斥取消息,而且只能从头部取消息,而添加消息是按照消息的执行先后顺序进行的排序,那么同一个时间范围内的消息,如果它是需要立刻执行的,需要怎么办?常规方法需要等到队列轮询到自己的时候才能执行,所以需要给一个紧急需要执行的消息是一个绿色通道,这个绿色通道就是同步屏障。

例如:救护车前面后20辆车,如果一辆一辆的走,肯定不行,所以救火车要优先通过立刻执行。

msg.taget = null(标记为同步屏障)-> msg1 -> msg2 -> msg3() -> msg4

同步屏障:就是阻碍同步消息,只让异步消息通过。

开启同步屏障:

	//MessageQueue.class
	public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            //从消息池获取Message
            final Message msg = Message.obtain();
            msg.markInUse();
            //就是这里 初始化Message对象的时候,并没给target赋值,因此target==null
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    //如果开启同步屏障的时间T不为0,且当前的同步消息里有时间小于T,则prev也不为null
                    prev = p;
                    p = p.next;
                }
            }
            //根据prev是不是null,将msg按照时间顺序插入到消息队列的合适位置
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

//Message.java
	//如果是异步消息 需要对消息对象设置为 true
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

从上述代码,可以看出将一条Message对象并且target=null的消息插入到消息队列中了,那么异步消息如何处理呢?从分析上述的Looper的代码中可以看出调用MessageQueue.next 处理消息,我们再来看next

Message next() {
		//......
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
    	//nextPollTimeoutMillis == -1 一直阻塞
    	//nextPollTimeoutMillis == 0 不会阻塞立即返回
    	//nextPollTimeoutMillis > 0 最长阻塞nextPollTimeoutMillis毫秒
    	//如果期间有程序唤醒会立即执行
        int nextPollTimeoutMillis = 0;
    	//next也是一个无线循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 获取系统开启到现在时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;//当前链表的头结点
                //注意这是关键,如果msg!=null 并且 msg.target ==null 他就是屏障,需要循环遍历,一直往后找到第一个异步消息
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                        //isAsynchronous() 表示是否是异步消息 如果是异步消息立即执行
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    //如果有消息需要处理,先判断时候有没有到,如果没到的话设置一下阻塞时间
                    //如有些场景常用的postDelay
                    if (now < msg.when) {
                        //计算出离执行时间还有多久赋值给nextPollTimeoutMillis
                        //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //获取消息
                        mBlocked = false;
                        //链表操作,获取msg并且删除

以上是关于Android Framework:探索Handler的设计思想的主要内容,如果未能解决你的问题,请参考以下文章

Framework | 探索Handler的设计思想

Framework | 探索Handler的设计思想

探索 Kubernetes 做为 Mesos 的一个 Framework 是否有意义

Android Framework

对于Android开发了解Android的Framework 层有多重要,Android Framework精编内核解析。

android framework具体工作是啥?