Android基础之异步消息处理机制

今天讲述一下Android的异步消息处理机制,说到异步,我们肯定会想到继承Thread,实现Runnable来处理耗时操作,然后再发消息去处理对应的业务逻辑。相信大家对下面的代码非常熟悉。

public class MainActivity extends Activity {
    private static final int MESSAGE = 1;
    private static Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            Log.i("Log","text");
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        CustomThread thread = new CustomThread();
        thread.start();

        CustomRunnable runnable = new CustomRunnable();
        runnable.run();
    }

    private class CustomThread extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            super.run();
            mHandler.sendEmptyMessage(MESSAGE);
        }

    };

    private class CustomRunnable implements Runnable {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            mHandler.sendEmptyMessage(MESSAGE);
        }
    }
}

然而这次的主要内容就是消息处理的原理。
我们首先了解一下以下各元素:

  • Message:消息
  • MessageQuene:消息队列,可以添加消息,处理消息。
  • Looper:消息循环,用于循环取出消息进行处理。
  • Handler:发送消息;消息循环从消息队列中取出消息后要对消息进行处理。

我们来看Handler的无参构造方法:

    public Handler() {
        this(null, false);
    }
   public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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;
    }

可以看到,在第10行调用了Looper.myLooper()方法来获取一个Looper对象,如果对象为空,会抛出一个RuntimeException。在获取到当前线程保存的Looper实例后,再获取了这个Looper实例中保存的MessageQueue(消息队列),这样handler、Looper、MessageQueue三者之间就关联上了。我们这接着看Looper.myLooper()是怎么处理的。

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

在当前线程会get他的消息循环器Looper,在有get(),就肯定有set(),我们可以想到在Looper.prepare()里面set()。那么我们继续往下看源码:

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

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

这里判断是否已经存在Looper,如果不存在则new一个新的Looper,所以说要先有一个消息循环器Looper,才能创建Handler对象。同时也可以看出每一个线程sThreadLocal只会有一个Looper对象。我们接着看Looper初始化做了些什么?

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

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

在Looper初始化时,新建了一个MessageQueue的对象,赋予mQueue中,MessageQueue的构造方法访问是包可见,所以我们是无法直接使用的。然后还有nativeInit(),nativeInit() 方法创建 NativeMessageQueue 对象,并将这个对象的指针复制给 Android MessageQueue 的 mPtr。关于C++中的nativeXXX方法不做过多分析,需要深究的同学自行查阅资料,我们只要明白mPtr为native层的MessageQueue的指针即可。

此时,回到上面,有同学会说,你这个Looper.prepare(),没有在Handler的构造方法出现啊,是怎么回事。那么我们可以去看一下ActivityThread的main()方法,系统已经帮我们自动调用Looper.prepare()方法了。代码如下:

    public static void main(String[] args) {  
        SamplingProfilerIntegration.start();  
        CloseGuard.setEnabled(false);  
        Environment.initForCurrentUser();  
        EventLogger.setReporter(new EventLoggingReporter());  
        Process.setArgV0("<pre-initialized>");  
        Looper.prepareMainLooper();  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  
        if (sMainThreadHandler == null) {  
            sMainThreadHandler = thread.getHandler();  
        }  
        AsyncTask.init();  
        if (false) {  
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));  
        }  
        Looper.loop();  
        throw new RuntimeException("Main thread loop unexpectedly exited");  
    }  

在第七行调用了Looper.prepareMainLooper()方法。而这个方法又会再去调用Looper.prepare()方法,在最后又会调用Looper.loop()方法,这就是我们平时创建Handler时不写这两个方法的原因。接着我们看prepareMainLooper()代码如下所示:

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    } 

这里Main线程(UI线程)初始化消息循环时会调用prepareMainLooper,传进去的是false,消息循环不可以退出,上面说在默认构造方法时可以。接着是调用myLooper(),创建一个消息循环器Looper,因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。

这样我们大致理顺了Handler的创建过程。那么在创建Handler之后,我们是开线程,线程处理完之后用handler发送消息Message,在文章一开头就演示了一个handler.sendEmptyMessage(arg)。然后我们去翻一下Handler类里面提供了很多发送消息的方法,但是它们最终都是调用sendMessageAtTime(Message msg, long uptimeMillis),这里面发送消息的方法我就不贴了,大家可以去翻翻SDK源码验证下,接着我们直接看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);
    }

mQueue是一个全局变量,在创建Handler的时候,获取到消息循环器Looper之后,会mQueue = mLooper.mQueue。这里面msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间。我们看到queue.enqueueMessage()方法,这里是MessageQueue类里面的方法,MessageQueue的作用我们上面也简述过,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。源码如下:

    final boolean enqueueMessage(Message msg, long when) {  
        if (msg.when != 0) {  
            throw new AndroidRuntimeException(msg + " This message is already in use.");  
        }  
        if (msg.target == null && !mQuitAllowed) {  
            throw new RuntimeException("Main thread not allowed to quit");  
        }  
        synchronized (this) {  
            if (mQuiting) {  
                RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");  
                Log.w("MessageQueue", e.getMessage(), e);  
                return false;  
            } else if (msg.target == null) {  
                mQuiting = true;  
            }  
            msg.when = when;  
            Message p = mMessages;  
            if (p == null || when == 0 || when < p.when) {  
                msg.next = p;  
                mMessages = msg;  
                this.notify();  
            } else {  
                Message prev = null;  
                while (p != null && p.when <= when) {  
                    prev = p;  
                    p = p.next;  
                }  
                msg.next = prev.next;  
                prev.next = msg;  
                this.notify();  
            }  
        }  
        return true;  
    }  

从代码看出,MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。
现在入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?这个就需要看一看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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

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

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

从一开始myLooper()可以看出如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。接着在for (;;)看到,进入的是一个死循环,然后不断地调用的MessageQueue的next()方法,这个next()方法就是消息队列的出队方法。它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,msg的target就是handler对象。【在上面Handler的发送消息enqueueMessage()方法中就首先为meg.target赋值为this】,而Message被处理后会被recycle。当queue.next返回null时会退出消息循环。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示:

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

在第5行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样就可以理解到在handleMessage()方法中可以获取到之前发送的消息了。

另外在Looper类开头注释的地方在着官方给的标准异步消息处理线程的写法:

    class LooperThread extends Thread {
        public Handler mHandler;

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

            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };

            Looper.loop();
        }
    }

上述基本把这个流程解释完毕了,我们简单总结一下:
借郭哥图一用:
这里写图片描述
1. 首先Looper.prepare()在当前主线程会创建一个Looper的实例对象,然后该实例中会创建一个MessaheQueue对象,由于Looper.prepare()在线程中有判断是否已存在消息循环器(Looper),存在则不创建,因此一个Looper对应着一个消息队列(MessageQueue)。
2. 在Looper.loop()方法中会让当前的线程进入到一个死循环中,不断地从MessageQueue中读取消息,然后回调handler的.dispatchMessage(msg)方法。
3. Handler的构造方法,会首先获取当前线程的Looper对象,由于Looper对应着一个MessageQueue对象,因此这三者建立对应的关联。
4. 在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

接着我们来看一下异步消息处理的方式来更新UI线程。
  • Handler post()
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                handler.post(new Runnable() {  
                    @Override  
                    public void run() {  
                        // 在这里进行UI操作  
                    }  
                });  
            }  
        }).start();  

上面是handler.post()的普通写法,为什么这个可以进行UI操作,不用发送消息,然后在Handler的handleMessage里面处理呢?我们现在去看一下源码:

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

看到吧,post的方法这里竟然也是用sendMessageDelayed(),上面说过,sendMessageDelayed()的方法,最后也是会跑到sendMessageAtTime()方法里的,接着的逻辑上面都有我就不重复说了。我们接着看一下getPostMessage(r)的方法:

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

这个方法将消息的callback的值传入一个Runnable对象。大家理顺一下,这个是不是Handler的dispatchMessage()方法中的一开始判断,callback是否为null的,不为null则调用handlerCallback()方法,如果为null则去调用handlerMessage()方法。handlerMessage()大家都清楚的了,那我们看下handleCallback()方法中的代码吧:

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

so,直接调用了一开始传入的Runnable对象的run()方法。
then想必大家还想到一个方法,runOnUiThread()。

  • runOnUiThread
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。


通过上面的源码分析,参考大神的博客,再自己思考、整理、书写,我自己都对Android的异步消息机制理解深刻了一层。希望这篇文章能对大家有灵光一闪的感觉。异步消息处理机制,是Android面试基本会问到的问题,大家一定要掌握好,理解好,能描述得头头是道。希望大家每天进步多一些。谢谢!最后大家有没注意到Message获取对象是通过obtain()方法,因为我打算下次写一篇Java设计模式之享元模式。

注:

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值