Android异步消息机制初探

作者: SheHuan | 来源:发表于2016-05-23 16:50 被阅读375次

    Android中,当我们需要在子线程中进行网络请求等耗时操作后,如果需要更新UI时,通常会考虑使用Handler来处理,一种常用的写法如下:

    private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 1){
                    // update ui
                }
            }
        };
    
    new Thread(new Runnable() {
                @Override
                public void run() {
                    Message message = Message.obtain();
                    message.what = 1;
                    mHandler.sendMessage(message);
                }
            });
    

    除了上边我们用到的Handler类外,Android消息机制要正常工作还需要LooperMessageQueue两个类的配合,下来我们分别介绍三个类,以及它们之间的工作原理:

    1、MessageQueue

    顾名思义就是消息队列,但其实MessageQueue的底层是通过单链表来实现的,MessageQueue是用来中转Handler对象发送出的消息,主要包括enqueueMessagenext两个操作,分别用来插入一条消息和取出一条消息,因为链表在插入和删除操作上有较高的效率,这可能就是使用该数据结构的原因吧。MessageQueue只负责保存消息,并不会处理消息,MessageQueue对象如何得到呢?继续往下看。

    2、Looper

    如果我们是在UI线程中使用Handler来发送异步消息,系统已经在当前UI线程通过Looper.prepareMainLooper()帮助我们创建好Looper对象,并调用Looper.loop()开启无限消息循环,不断从MessageQueue的实例中读取消息。如果在子线程中我们不手动创建Looper对象,则会抛出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()的异常,所以在子线程中创建并使用Handler对象,就必须手动调用Looper.prepare()创建Looper对象,并通过Looper.loop()开启消息循环。
    在子线程中可通过如下方式使用Android消息机制:

    new Thread(new Runnable() {
                @Override
                public void run() {
                    Looper.prepare();//创建Looper对象
                    Handler handler = new Handler();
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            //do something
                        }
                    });
                    Looper.loop();//开启消息循环
                }
            });
    

    这样子线程同样具有了异步消息循环处理的能力。
    我们先通过源码分析一下Looper.prepare()方法

    public static void prepare() {
            prepare(true);
        }
    

    很简单,只有一行代码,继续进入到prepare方法中

    private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    

    重点看一下new Looper()方法

    private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    

    可以看到在创建Looper对象时也创建了MessageQueue对象,此时Looper对象持有了MessageQueue对象的引用。并与当前线程绑定,保证一个线程只会有一个Looper对象,同时一个Looper对象也只有一个MessageQueue对象。

    3、Handler

    首先看一下Handler的构造方法

    public Handler() {
            this(null, false);
        }
    

    依然只有一行,继续跟进

    public Handler(Callback callback, boolean async) {
           ........
            省略
           ........
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    通过mLooper = Looper.myLooper(),我们Handler对象拿到了对应Looper对象的引用,产生了关联。
    可以看到如果Looper等于null的话会抛出异常,这也验证了上边第二点中的说法,创建Handler对象时必须有Looper对象存在。
    再看 mQueue = mLooper.mQueue这一行,其实就是将Handler的实例与我们Looper实例中创建的MessageQueue对象关联起来。此时MessageQueue对象已经和Looper对象以及Handler对象关联了起来,并且他们在同一线程中。

    4、工作原理

    结合文章开头的例子来分析 Handler 具体的工作原理。

    通过Handler对象发送消息时可用的方法非常多,例如常用的sendMessage系列方法,post系列方法等,通过源码可以知道,这两系列方法都最终都是调用下边的方法来实现的:

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    

    继续看下sendMessageAtTime方法

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }
    

    其核心的的就是最后一行,跟进去

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    首先看第一行msg.target = this,将Handler对象赋值给的Message对象的target属性,此时Message对象持有了Handler对象的引用。
    最后一行通过调用MessageQueue 对象的enqueueMessage方法来保存发送的消息。

    上边Looper类中还有一个Looper.loop()方法没分析,调用该方法后,则会一直检测MessageQueue对象中是否有数据,有的话则取出,否则阻塞,此时我们来看一下源码:

    public static void loop() {
            ........
            省略
            ........
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                msg.target.dispatchMessage(msg);
                ........
                省略
                ........
            }
        }
    

    只看核心的代码,首先是一个无限的for循环,循环中调用MessageQueue 的next方法不断的取出Message对象,没有消息则进入阻塞状态。否则执行msg.target.dispatchMessage(msg),这里的msg.target就是上边enqueueMessage方法中的msg.target,也就是Handler对象的引用,所以此时消息就可以调用Handler的dispatchMessage()方法进行消息处理。此时Handler对象发出的消息在MessageQueue对象中通过 Looper 完成了中转,由于Looper是在主线程创建并开启消息循环的,所以dispatchMessage()在主线程被调用:

    接下来看一下dispatchMessage方法:

    public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    首先看第一行if判断,什么时候msg.callback != null成立呢,其实就是当我们通过如下形式发送消息时

    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            // update ui
                        }
                    });
    

    继续查看post方法

    public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    

    进入getPostMessage方法中

    private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
        }
    

    记住m.callback = r这一行,此时再看dispatchMessage中第一个if条件的执行代码:

    private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    方法中执行的正是callback 中的run方法

    if (mCallback != null)什么时候成立呢?当我们通过如下方式使用Handler时成立

    private Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                return false;
            }
        });
    

    从而下边的代码得到执行

    if (mCallback.handleMessage(msg)) {
                        return;
                    }
    

    即就是我们上边new Handler.Callback()中的handleMessage方法

    最后如果我们采用文章最开始的代码执行handler操作则会执行dispatchMessage中最后一行handleMessage(msg)方法:

    public void handleMessage(Message msg) {}
    

    可以看到是一个空的方法,因为需要我们自己去实现哦!同时 Handler 也是在主线程创建的,所以 handleMessage()方法最终在主线程执行,这样就完成了从子线程到主线程的切换。

    最后说一下,创建Message对象,可以通过new方法 ,也可以使用Message.obtain()方法,但建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

    到这里你有没有发现LooperMessageQueueHandler之间的关系呢?

    1. 在Looper中创建了MessageQuene,Looper循环从MessageQuene取出消息,由于消息持有Handler的引用,最终会调用Handler的handleMessage()方法或其它方法,Looper会被保存在ThreadLocal中,一个线程对应唯一Looper。
    2. Handler发送消息时,会从ThreadLocal中取出对应线程的Looper,再从中得到对应的MessageQuene,发送的消息会持有Handler的引用,然后将消息保存到MessageQuene。
    3. 关于ThreadLocal在下一篇有讲到

    到此Android消息机制的基本原理已经分析完了,简单的概括一下:通过Handler对象的相关方法将Message对象发送出去进而插入到MessageQueue对象中,然后Looper对象调用loop方法,进一步会执行MessageQueue对象的next方法取出消息,交给Handler对象的dispatchMessage方法来处理。这就是Android消息机制一个大致的流程。

    相关文章

      网友评论

        本文标题:Android异步消息机制初探

        本文链接:https://www.haomeiwen.com/subject/eyiarttx.html