Android消息机制Handler

love.GIF

  在android开发中,我们经常会在子线程中通过handler发送数据给主线程,通知主线程做相应的操作。子线程、handler、主线程构成了线程模型中的经典问题:生产者-消费者模型:生产者和消费者在同一时间段内共用同一存储空间,生产者往存储空间中添加数据,消费者从存储空间中取走数据。

handler生产者-消费者模型.png
  • 保证数据生产消费的顺序(通过MessageQuene,先进先出)
  • 生产者和消费者之前没有耦合(子线程和主线程都只依赖于handler)

源码分析:

Handler机制相关类
Handler机制相关类 说明
Handler 发送和接受消息
Looper 用于轮询消息队列,一个线程只能有一个Looper
Message 消息实体
MessageQueue 消息队列,用于存储和管理消息

  当我们调用handler.sendMessage()发送一个Message时,实际上是将这个Message发送到MessageQueue中,然后与当前线程绑定的Looper会不断地从MessageQueue中取出新的Message,调用msg.target.dispatchMessage(msg)将消息分发到handlerMessage()中。

1.实例化Handler
final Looper mLooper;
final MessageQueue mQueue;
public Handler(Callback callback, boolean async) {
    //...
    //检查当前的线程是否有Looper,不存在就抛异常(主线程在ActivityThread已为我们创建好了Looper)
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
    }
    //Looper持有一个MessageQueueu
    mQueue = mLooper.mQueue;
    //初始化了回调接口
    mCallback = callback;
    mAsynchronous = async;
}

一个完整的Handler使用例子应该这样创建:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            //问题1:handler.handleMessage(msg)是在什么时候回调的呢?
            public void handleMessage(Message msg) {
                // 处理接收到的消息
            }
        };

        Looper.loop();
    }
}
2.Looper.prepare()

Looper提供了Looper.prepare()来创建Looper,借助ThreadLocal来实现与当前线程的绑定

static final ThreadLocal sThreadLocal = new ThreadLocal();

private static void prepare(boolean quitAllowed) {
    //Android规定一个线程只能够拥有一个与自己关联的Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //在当前线程绑定一个Looper
    sThreadLocal.set(new Looper(quitAllowed));
}

在Looper中,维持着一个Thread对象和MessageQueue对象

private Looper(boolean quitAllowed) {
    //创建一个新的MessageQueue quitAllowed参数表示这个Queue是否能够被退出
    mQueue = new MessageQueue(quitAllowed);
    //将线程对象指向了创建Looper的线程
    mThread = Thread.currentThread();
}
3.Looper.loop()

Looper.loop()会不断地从MessageQueue(MessageQueue.next())中获取Message,并调用msg.target.dispatchMessage(msg)回到Handler来分发消息。

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 (;;) {
        //不断地从MessageQueue获取消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // ...
        try {
            //问题2:Looper.loop()死循环中的msg.target是什么时候被赋值的呢?
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            //...
        }
        //...
        //回收message
        msg.recycleUnchecked();
    }
}

msg.target.dispatchMessage(msg)中的msg.target就是发送该消息的Handler,这样就回调到了Handler那边去了

//Handler
public void dispatchMessage(Message msg) {
    //msg.callback是Runable
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        //可以利用 Callback 这个拦截机制来拦截 Handler 的消息。
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //问题1:回调到Handler的handlerMessage方法
        handleMessage(msg);
    }
}
4.创建Message

可以直接new Message但有更好的方式Message.obtain。复用Message避免过多的创建、销毁Message对象达到优化内存的目的。

public static Message obtain(Handler h) {
    Message m = obtain();//调用重载的obtain()方法
    m.target = h;//绑定创建Message对象的handler
    return m;
}

public static Message obtain() {
            //sPoolSync是一个Object对象,用来保证线程安全。
        synchronized (sPoolSync) {
        //sPool是handler dispatchMessage后通过msg.recycleUnchecked()回收用来复用的Message
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
5.handler.sendMessage()

无论是handler.sendMessage(msg)还是handler.sendEmptyMessage(what),最终都追溯到以下方法

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
   //引用Handler中的MessageQueue
    //这个MessageQueue就是创建Looper时被创建的MessageQueue
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    //将新来的Message加入到MessageQueue中
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //问题2:msg.target被赋值(即Message和Handler的绑定)的时机:在handler.sendMessage(msg)后,执行enqueueMessage的时候
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);//把消息设置为异步消息
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
其它问题:
1.Handler如何保证自身的线程安全?

  消息入库(enqueueMessage) 和消息出库(next)加synchronized (this)

  synchronized (this)这个锁,说明对所有调用同一个MessageQueue对象的线程来说,它们都是互斥的。在Handler里,一个线程对应一个唯一的Looper,而Looper中又只有唯一一个MessageQueue,所以主线程只有一个MessageQueue对象。所有子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其它消息需要等待,这样消息队列就不会出现混乱。同时也保证了enqueueMessage函数和next函数能够实现互斥,多线程访问的时候MessageQueue有序的进行。

2.消息机制之同步屏障

  线程的消息都是放在同一个MessageQueue里面,取消息的时候是互斥取消息,而且只能从头部取消息,而添加消息时按照消息的执行的先后顺序进行的排序。如果一个消息需要立刻执行怎么办?所以需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。所谓同步屏障就是阻碍同步消息,让异步消息先执行。

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

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        //1)没有个msg.target赋值,因此msg.target为null !!!!!
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        //2)将消息按照时间顺序插入到消息队列(单链表)中合适的位置
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
      //3)返回一个序号,通过这个序号可以撤销屏障
        return token;
    }
}

Message对象初始化的时候没有给msg.target赋值,也就是一条msg.target==null的消息进入了消息队列。那么开启同步屏障后,所有的异步消息是如何被处理的呢?

Message next() {
        //...
            int pendingIdleHandlerCount = -1;
            int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
                        //1)如果有消息插入到消息队列或延时时间到,就会被唤醒,否则阻塞在这。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
              //2)如果target==null,那么它就是屏障,需要遍历,一直往后找到第一个异步消息
                if (msg != null && msg.target == null) {
                   //3)遍历找异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                  //如果有消息需要处理,先判断时间有没有到,没到的话设置一下阻塞时间
                  //场景如postDelay
                    if (now < msg.when) {
                      //计算出离执行时间还有多久赋值给nextPollTimeoutMillis
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        //链表操作,获取msg并删除该节点
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //返回拿到的消息
                        return msg;
                    }
                } else {
                    //没有消息就一直休眠,等待被唤醒
                    nextPollTimeoutMillis = -1;
                }
                //...
        }
    }

从上面可以看到,当消息队列开启同步屏障的时候,即msg.target==null,消息机制在处理消息的时候会优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

同步屏障.png

如上图所示:在队列中有同步消息、异步消息、屏障消息。有了同步屏障的存在,msg3这个异步消息可以被优先处理,后msg2这个同步消息不会被处理。我们只有调用removeSyncBarrier移除这个同步屏障,msg2这个同步消息才能被处理。

  同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的 setAsynchronous(true) 时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

问题总结:

  • 简述Handler原理
    当我们调⽤handler.sendMessage(msg)发送⼀个Message时,实际上这个Message是发送到与当前线程绑定的的⼀个MessageQueue中,然后与当前线程绑定的Looper将会不断的从MessageQueue中取出Message,调⽤msg.target.dispatchMessage(msg)⽅法将消息分发到与Message绑定的handler.handleMessage()⽅法中。

  • 一个线程可以有几个Handler?几个Looper?
    一个线程可以有多个Handler,只能有一个Looper。调用Looper.perpare会创建一个Looper保存在ThreadLocal中,每个线程只有一个ThreadLocalMap,会将Looper保存在对应线程中的LocalThreadLocalMap中,ThreadLocal为key,Looper为value

  • Handler内存泄漏问题及解决方案
    非静态内部内持有外部类的引用,如果延时期间关闭了Activity,Message持有了Handler的引用,而Handler持有了Activity的引用,导致Activity无法被回收,导致内存泄漏。将Handler定义为静态内部类,在内部类中持有Activity的弱引⽤,并在onDestory前移除所有消息。

  • 可以在子线程中new Handler 吗?要怎么做?
    可以通过子线程创建Handler,需要调用Looper.perpare和Looper.loop方法。

  • 子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
    子线程中创建了Looper,当没有消息的时候子线程会被阻塞,无法被回收,所以我们需要手动调用quit方法将消息删除并唤醒looper,然后在next方法中返回null退出loop。

  • 既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它 内部是如何确保线程安全的?取消息呢?
    在添加数据和取出数据的时候都加了this锁。一个线程对应一个唯一的Looper,而Looper中又只有唯一一个MessageQueue,所以主线程只有一个MessageQueue对象。所有子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其它消息需要等待,这样消息队列就不会出现混乱。同时也保证了enqueueMessage函数和next函数能够实现互斥,多线程访问的时候MessageQueue有序的进行。

  • 我们使用 Message 时应该如何创建它?
    通过Message.obtain来创建Message。也就是复用之前的Message的内存,不会频繁的创建对象导致内存抖动。(享元设计模式就是重复利用内存空间,减少对象的创建)

  • Looper死循环为什么不会导致应用卡死
    卡死是ANR,指应用程序在一定时间内没有得到响应。loop循环是用于取出消息分发出去,没有消息会阻塞在next里的nativePollOnce方法里,并释放CPU资源进入休眠。Android中大多数操作都是通过Handler机制来完成的,包括ANR消息,如果没有消息,则不需要程序去响应,更不会有ANR。

  • 为什么系统不建议在子线程中更新UI
    UI对象没有加锁,子线程中更新UI,会出现多线程安全问题。

  • Handler是如何完成子线程和主线程通信的?
    主线程创建Handler,在子线程中发送消息实际上是将消息放入到MessageQueue中,通过loop循环取出消息进行执行handleMessage,由于Looper是在主线程中初始化的,初始化的时候会创建消息队列,所以消息是在主线程被执行的。

  • 点击页面上的按钮后更新TextView的内容,谈谈你的理解?
    点击按钮的时候会发送消息到Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个target为null的消息,这样在使用消息队列的next获取消息的时候,如果发现消息的target为null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将target为null的消息移除。(同步屏障)

你可能感兴趣的:(Android消息机制Handler)