为了加深理解Android中多线程的使用以及解决主线程阻塞的问题,学习了下Android应用中的消息机制,写了下自己的学习过程,仅供参考。
作为Android开发者来说,应该都知道在Android应用启动时,会默认创建一个主进程(UI进程),并且这个主线程会不断的循环执行。Android为了让我们的代码与主线程进行交互,提供了消息传递机制,在这个主线程中会关联一个消息队列,我们所有的操作都会封装成消息然后交给主线程来处理。这样既减小了开发的难度,提高了消息交互的安全性,也避免了主线程的阻塞。
我们都知道,Andriod提供了Handler、Looper来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环),从而满足线程间的相互通信。
Android中的消息机制,主要指Handler机制,而Handler相信大家已经非常熟悉了,它可以将一个任务切换到Handler所在的线程中去执行。在日常开发过程中,在子线程执行完耗时操作后,需要更新UI,但是Android不运行子线程访问更新UI,所以我们一般采用Handler机制来更新UI,通过Handler将一个消息推送到UI线程中,然后在Handler的handlerMessage方法中进行处理,但是这个Handlder必须在主线程中创建。
简单的例子如下:
private Handler mHandler = new Handler(){ @Override public void handlerMessage(Message msg){ switch(msg.what){ case msg.what: //进行更新ui操作 break; } } }; //创建子线程进行耗时操作 new Thread(new Runnable { public void run(){ //进行一系列耗时操作 mHandler.sendEmptyMessage(123); } }).start;
Handler机制的实现需要依赖于Looper、MessageQueue以及Message,下面分别对其作用做简单的介绍。
Handler:消息的处理者,其作用是获取、发送、处理和移除消息等; Looper:消息循环。会不停地从MessageQueue中取消息,将其交给Handler处理,如果不处理则会阻塞; MessageQueue:以队列的形式对外提供插入和删除的工作,其内部结构是以链表的形式存储消息的。 Message:消息(Message)代表一个行为(what)或者一串动作(Runnable),每一个消息在加入消息队列时,都有明确的目标(Handler)。补充:ThreadLocal,其可以理解为ThreadLocalData,ThreadLocal的作用是提供线程内的局部变量(TLS),这种变量在线程的生命周期内起作用,每一个线程有他自己所属的值(线程隔离)。
下两图可以更清晰的展示他们相互之间的协作。
由上图可以看出以下几点:
Looper依赖于MessageQueue和Thread,每个Thread只对应一个Looper,每个Looper只对应一个MessageQueue(一对一)。工作时,Looper会不断循环获取消息,通过调用dispatchMessage方法来给Handler分发消息; Handler中持有Looper和MessageQueue的引用,可通过handlerMessage方法处理分发过来的消息(所处线程中,一般为主线程)或者通过send/post方法一个Message到消息队列中; MessageQueue依赖于Message,每个MessageQueue中有N个待处理消息(一对N),一个消息到来时,可以通过调用enqueueMessage方法来让消息入队。 Message依赖于Handler来进行处理,每个Message有且仅有一个对应的Handler。(一对一)注:普通的线程是没有looper的,如果需要looper对象,那么必须要先调用Looper.prepare()方法,而且一个线程只能有一个looper。
Android即是通过这一套的消息机制进行进程间的相互通信,下面通过源码来分析其工作原理。
UI线程消息循环的创建
在上面,我们说过,应用程序在启动时,默认会创建一个主线程,并且会关联它的消息循环,从而等待处理封装过来的消息。但是UI线程是如何创建消息循环的呢?答案是在应用的程序入口ActivityThread.main方法中,下面我们来看下它的源码:
public static void main(String[] args) { ... Process.setArgV0(""); //1.创建消息循环,即Looper Looper.prepareMainLooper(); //2.创建主线程 ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { //3.绑定主线程的Handler sMainThreadHandler = thread.getHandler(); } ... // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); //4.执行消息循环 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
默认情况下,只有有且仅有主线程的消息队列,是在ActivityThread.main方法中通过Looper.prepareMainLooper()来创建,最后执行Looper.loop()来启动消息循环。
执行完ActivityThread.main方法后,主线程的就关联了它的消息循环并且获取了属于自己的Handler,自此应用程序就启动了,并且会从自身的消息队列中不断的取出消息,然后处理消息,从而使整个系统运转起来。
注:由于主线程中的Handler过于冗余,这里就不进行详细的介绍,但从源码上可以看出,其主要作用有管理四大组件生命周期的操作、整个Android应用的Application的绑定和销毁以及释放资源等,起着非常重要的作用。
Handler的创建过程
从上面我们知道了主线程消息队列的创建时机,以及他的运转。而系统是通过Handler来将消息投递到消息队列或者是将消息从消息队列中取出并且处理的。那么Handler如何关联消息队列和线程的?我们来看下Handler的构造方法:
public Handler(Callback callback, boolean async) { //callback is null, async is false ... //1.获取Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //2.获取消息队列 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
从默认的构造方法可以看出,在Handler内部会通过Looper.getLooper()这个方法来获取Looper的对象,并于此与之关联,即与消息队列关联。接下来我们来看下Looper.getLooper是如何工作的:
static final ThreadLocalsThreadLocal = new ThreadLocal (); public static @Nullable Looper myLooper() { //这里可以看出Looper是通过ThreadLocal来封装的 return sThreadLocal.get(); }
从上面可以看出,Looper是通过ThreadLocal来封装的,那它是什么时候被关联起来的呢?,我们回到原来的ActivityThread.main中查看prepareMainLooper()。
public static void prepareMainLooper() { //初始化Looper prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } //主线程循环队列 sMainLooper = myLooper(); } } private static void prepare(boolean quitAllowed) { //quitAllowed is false at first if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //ThreadLocal封装Looper sThreadLocal.set(new Looper(quitAllowed)); } private Looper(boolean quitAllowed) { //创建消息队列 mQueue = new MessageQueue(quitAllowed); //关联线程 mThread = Thread.currentThread(); }
从以上方法可以看到,在prepareMainLooper()中调用了prepare()这个方法,在这个方法内创建了一个Looper对象(从上面代码也可以看到,Looper对象封装了一个消息队列,并关联了线程),并将这个对象设置给了sThreadLocal,这样消息队列就与线程关联了,也因此不同的线程不能访问其他人的消息队列。这里我们也可以知道,为什么更新UI的Handler必须要在主线程中进行创建。
消息循环的过程
上面我们已经讲述了Looper的创建过程,但是它是如何执行消息循环的呢,又是如何处理消息的?,细心的人应该发现ActivityThread.main方法中有有个Looper.loop()方法,我们接着来看Looper.loop()方法:
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { //1.获取Looper对象 final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //2.获取消息队列 final MessageQueue queue = me.mQueue; ... //3.死循环,不断轮询消息队列 for (;;) { //4.从队列中取出消息 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ... try { //5.分发消息 msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } ... //6.消息回收 msg.recycleUnchecked(); } }
从以上代码可以看到,Looper.loop()方法建立了一个死循环,然后不断从消息队列中去粗消息,最后处理消息的过程。另外,对于Looper.prepare()方法与Looper.loop()方法一般是承兑出现的。
注:在以前的安卓版本中,这里的死循环使用的while(true)方式,而现在使用的是for(;;)循环的方式,这是为了防止一些高手通过反射来修改系统内容。
消息循环处理过程
讲完了上面的内容,最终我们来通过源码说下消息处理机制。
从上面的Looper.loop()方法中可以看到,系统通过msg.target.dispatchMessage(msg)方法来分发消息,而msg就指的是Message实体类,其声明如下:
public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; public Messenger replyTo; public int sendingUid = -1; /*package*/ static final int FLAG_IN_USE = 1 << 0; /** If set message is asynchronous */ /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; /** Flags to clear in the copyFrom method */ /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; /*package*/ int flags; /*package*/ long when; /*package*/ Bundle data; //消息处理 /*package*/ Handler target; //Runnable类型的回调接口 /*package*/ Runnable callback; // sometimes we store linked lists of these things //下一条消息,消息队列是链式存储结构 /*package*/ Message next; ... }
从上述源码,可以看到Message对象是实现了Parcelable接口的,这也Message可以实现跨进程通讯的原因。里面还封装了一些常见的参数,而最主要的是target,其类型是Handler,这里我们看到,系统其实绕了一个大圈,通过Handler把消息投递给消息队列,又通过消息队列把消息分发给了Handler来处理。下面我们来看下dispatchMessage(msg):
/** * Subclasses must implement this to receive messages. * 消息处理函数,子类通过复写来处理消息 */ public void handleMessage(Message msg) { } /** * Handle system messages here. * 消息分发函数 */ public void dispatchMessage(Message msg) { if (msg.callback != null) { //处理msg handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //处理msg handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
从上述源码可以看到,dispatchMessage的确只是一个分发的方法,通过判断回调接口是否为空,从而通过不同手段处理消息。这里handleCallback(msg)是通过post(Runnable callback)方法来执行,而 handleMessage(msg);是通过sendMessage()方法来执行。二者代码如下:
public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); } public final boolean sendMessage(Message msg){ return sendMessageDelayed(msg, 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } public final boolean sendMessageDelayed(Message msg, long delayMillis){ if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } 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) { //1.设置消息的target为当前的Handler对象 msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } //2.将消息插入消息队列 return queue.enqueueMessage(msg, uptimeMillis); }
从上述代码中可以看出,在post()方法时,会将Runnable对象包装成Message对象,并且将Runnable对象设置给Message对象的callback参数,最后将Message对象插入队列中,而sendMessage则不需要Runnable对象。总之不管是post()方法还是senMessage()方法,最终都会调用sendMessageDelayed()方法,从而向消息队列添加消息。并且与此同时,Looper也不断的从消息队列中读取消息,并分发给Handler处理,也因此消息就会不断的被产生,处理,整个Android应用就是这么运转起来的。
遇到的坑
让我们来看下以下这个情景:
new Thread(new Runnable() { Handler mHandler = null; @Override public void run() { mHandler = new Handler(); } });
上述代码,可以看出,我是打算在子线程中创建Handler。但是运行代码时候就会抛出 Can’t create handler inside thread that has not called Looper.prepare() 异常,这是为什么呢?回顾下Handler的构造函数:
public Handler(Callback callback, boolean async) { //callback is null, async is false ... //1.获取Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //2.获取消息队列 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
从代码中可以看出,当mLooper为空的时候,就会抛出异常。而这是因为该线程中的Looper对象还未创建,因此Looper.myLooper()返回的是null,即Looper对象还未被初始化。解决这个的方法,就是在子线程中,手动初始化Looper对象,这就要用到我们上面说的Looper.prepare()方法与Looper.loop()方法,来为这个线程配对自己的Looper,从而解决问题,修改后代码如下:
new Thread(new Runnable() { Handler mHandler = null; @Override public void run() { //1.创建Looper对象,绑定对应的ThreadLocal Looper.prepare(); mHandler = new Hand ler); //2.启动消息循环 Looper.loop(); } });