Android消息机制和应用

Posted 严振杰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android消息机制和应用相关的知识,希望对你有一定的参考价值。

版权声明:转载必须注明本文转自严振杰的博客: http://blog.yanzhenjie.com

本文主要讲的是android消息机制的Java层,Android消息机制对Android开发者来说是一个基础知识,网络上介绍Android消息机制的文章很多,为了本文不显得多余,我争取从不同的角度来做一个解析,包括一些基础和源码分析。

我们知道Android的消息机制主要指HandlerMessageQueueLooper的运作机制、要想完全搞清楚Android的消息机制势必要先理解Binder IPC机制、Linux pipe/epoll机制,由于篇幅会过长,本文不会深入讲IPC机制和pipe/epoll机制,涉及到以上两点时会讲述它的基本作用和原理。


本文主要介绍的内容

本文主要要介绍的类:

  • ThreadLocal
  • Handler、Looper和MessageQueue
  • HandlerThread
  • ActivityThread
  • ApplicationThread
  • IntentService

读完本文后可以了解到的主要内容:

  1. 子线程为什么不能直接new Handler()
  2. Handler是如何切换线程的?
  3. Looper.loop()死循环为什么不会导致主线程发生ANR?
  4. ANR是如何发生的?
  5. Activity的生命周期是如何在Looper.loop()死循环外执行的?

ThreadLocal

看到ThreadLocal的第一感觉就是该类和线程有关,确实如此,但是要注意它不是线程,否则它就该叫LocalThread了。

ThreadLocal是用来存储指定线程的数据的,当某些数据的作用域是该指定线程并且该数据需要贯穿该线程的所有执行过程时就可以使用ThreadnLocal存储数据,当某线程使用ThreadnLocal存储数据后,只有该线程可以读取到存储的数据,除此线程之外的其他线程是没办法读取到该数据的。

一些读者看完上面这段话应该还是不理解ThreadLocal的作用,我们举个栗子:

ThreadLocal<Boolean> local = new ThreadLocal<>();
// 设置初始值为true.
local.set(true);

Boolean bool = local.get();
Logger.i("MainThread读取的值为:" + bool);

new Thread() 
    @Override
    public void run() 
        Boolean bool = local.get();
        Logger.i("SubThread读取的值为:" + bool);

        // 设置值为false.
        local.set(false);
    
.start():

// 主线程睡1秒,确保上方子线程执行完毕再执行下面的代码。
Thread.sleep(1000);

Boolean newBool = local.get();
Logger.i("MainThread读取的新值为:" + newBool);

读者朋友先不要往下看,请接合上面方的ThreadLocal介绍猜测一下打印结果。

我想读者朋友应该都猜中了结果,第一条Log无可置疑,因为设置了值为true,因此打印结果是:

MainThread读取的值为:true

对于第二条Log,根据上方介绍,某线程使用ThreadLocal存储的数据,只能被该线程读取,因此第二条Log的结果是:

SubThread读取的值为:null

紧接着在子线程中设置了ThreadLocal的值为false,然后第三条Log将被打印,原理同上,子线程中设置了ThreadLocal的值并不影响主线程的数据:

MainThread读取的值为:true

实验结果证实:就算是同一个ThreadLocal对象,任一线程对其的set()get()方法的操作都是相互独立互不影响的。

这就是开篇第一个问题的答案的基础知识,但是要完全理解第一问题还需要后面的内容的铺垫,因此我们先不揭晓第一个问题的完整答案。

Handler、Looper和MessageQueue

HandlerLooper组成了一个生产者消费者模式,Handler作为生产者向MessageQueue添加产物MessageLooper作为消费者,在Looper#loop()方法的死循环中从MessageQueue#next()循环取出Message进行消费。

Handler

我们从Handler的使用开始一步步翻阅源码做个简单的解析。我们在日常开发中使用Handler最多的场景是子线程向主线程发送消息更新UI,一般我们是这样向主线程发送消息的:

Handler mHandler = new Handler() 
    @Override
    public void handleMessage(Message msg) 
        // 这里是主线程
        ...
    
;

class UIThread implements Runnable 
    @Override
    public void run() 
        // 这里是主线程
        ...
    


class SubThread extends Thread 

    private Handler mHandler;

    SubThread(Handler handler) 
        this.mHandler = handler;
    

    @Override
    public void run() 
        ... // 耗时操作。

        // 第一种方法
        mHandler.obtainMessage(1).sendToTarget();

        ... // 耗时操作。

        // 第二种方法
        Message message = new Message();
        message.what = 2;
        message.obj = ...
        mHandler.sendMessage(message);

        ... // 耗时操作。
        
        // 第三种方法
        mHandler.post(new UIThread());
    

上述代码看起来比较笨拙,但是列出了Handler向主线程发送消息的常用方法,不过这都是Handler为了使发送消息更加简单而提供的封装方法,他们都会产生一个Message对象,包括Handler#post(Runnable)最终也是剩成一个Message

public class Handler 
...

public final boolean sendMessage(Message msg)
    return sendMessageDelayed(msg, 0);


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


private static Message getPostMessage(Runnable r) 
    Message message = Message.obtain();
    message.callback = r;
    return message;


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

最终它们都走向了Handler#enqueueMessage()然后转而调用MessageQueue#enqueueMessage(),其目的都是为了向MessageQueue添加一个Message:

public class Handler 
...

boolean sendMessageAtTime(Message msg, long uptime) 
    MessageQueue queue = mQueue;
    if (queue == null) 
        return false;
    
    return enqueueMessage(queue, msg, uptimeMillis);


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

看了上述代码,勤奋好学的我们得抛出一个问题,MessageQueue中的消息是如何被读取的呢?可想而知的是,MessageQueue中有消息进入,肯定是有消息出去的,想必是有成对的方法提供出来吧。

MessageQueue

我们在MessageQueue的源码中发现了成对出现的进出方法(以下代码经过大量精简,读者应该自行翻阅一下源码,以免产生误导):

public class MessageQueue 
...

Message mMessages;
boolean needWake;

/**
 * 消息进入的方法。
 */
boolean enqueueMessage(Message msg, long when) 
    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;
            
        
        msg.next = p;
        prev.next = msg;
    

    // 上面省去了对needWake的赋值逻辑
    if (needWake) 
        // 唤醒阻塞
        nativeWake(mPtr);
    
    return true;


/**
 * 消息出去的方法。
 */
Message next() 
    int nextPollTimeMls = 0;
    for (;;) 
        // 尝试阻塞
        nativePollOnce(ptr, nextPollTimeMls);

        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) 
            do 
                prevMsg = msg;
                msg = msg.next;
             while (msg != null);
        
        if (msg != null) 
            if (now < msg.when) 
                nextPollTimeMls = Math.min(msg.when - now, MAX_VALUE);
             else 
                if (prevMsg != null) 
                    prevMsg.next = msg.next;
                 else 
                    mMessages = msg.next;
                
                msg.next = null;
                return msg;
            
         else 
            nextPollTimeMls = -1;
        

        nextPollTimeMls = 0;
    

从上述两个方法结合更多源码可以看出,MessageQueue采用的是单向链表数据结构,mMessage是链表的第一个元素,Messagenext字段保存链表的下一个元素。

消息出去的方法在本文中非常重要,因此我们主要看一下MessageQueue#next()方法,next()里面有一个for(;;)循环,循环体内调用了nativePollOnce(long, int)方法,这是一个Native方法,实际作用是通过Native层的MessageQueue阻塞当前调用栈线程nextPollTimeMls毫秒的时间。

下面是nextPollTimeMls取值的不同情况的阻塞表现:

  1. 小于0,一直阻塞,直到被唤醒
  2. 等于0,不会阻塞
  3. 大于0,最长阻塞nextPollTimeMls毫秒,期间如被唤醒会立即返回

MessageQueue中有一个nativeWake(long)的Native方法,可以唤醒nativePollOnce()的阻塞。

现在回到next()方法体中,我们看到循环开始前nextPollTimeMls的值是0,那么nativePollOnce()方法将会立刻返回,此时尝试取出下一个Message元素,如果没有下一个元素,nextPollTimeMls的值被修改为-1,此时nativePollOnce()进入阻塞状态,等待下一个Message的进入并唤醒阻塞,然后取出Message对象返回。

Looper

现在知道消息从哪里出去的,那么接下来就看看Looper是如何消费,Looper类的代码很少,我贴出关键部分的代码(经过大量精简):

public class Looper 
...

static ThreadLocal<Looper> sThreadLocal = ...;
private static Looper sMainLooper;

MessageQueue mQueue;

// 获取当前线程的的Looper
public static Looper myLooper() 
    return sThreadLocal.get();


// 初始化当前线程的Looper
public static void prepare() 
    if (myLooper() == null) 
        sThreadLocal.set(new Looper());
    


// 初始化主线程的Looper
public static void prepareMainLooper()     
    if (sMainLooper == null) 
        prepare();
        sMainLooper = myLooper();
    


// 获取主线程的Looper
public static Looper getMainLooper() 
    return sMainLooper;


// Looper的构造方法中初始化MessageQueue
private Looper() 
    mQueue = new MessageQueue();


// 循环处理当前线程的消息队列中的消息
public static void loop() 
    final Looper nowLooper = myLooper();
    if (me == null) 
        throw new RuntimeException("No Looper; Looper.prepare() 
            wasn't called on this thread.");
    

    final MessageQueue nowQueue = nowLooper.mQueue;

    for (;;) 
        Message msg = nowQueue.next(); // 从消息队列读取下一条消息
        if (msg == null) 
            // 如果读取到空消息,退出循环,退出该方法
            return;
        

        ...

        // 通过Handler分发消息
        msg.target.dispatchMessage(msg);

        ...
    

上述代码中,在Looper的构造方法中,初始化了LooperMessageQueue对象;初始化Looper和获取Looper的方法使用到了ThreadLocal,在ThreaqLocal中我们介绍了ThreadLocal#get()只能获取到当前线程保存的数据;在Looper#loop()方法中首先判断了当前线程的Looper是否为空,为空就抛出运行时异常,中断当前操作,不为空则进入死循环读取消息队列中的消息,把消息发回发送消息的Handler去分发。

因此到这里我们可以得出第一个问题的答案了:

子线程为什么不能直接new Handler()

我们回到Handler中看看调用Handler的空构造发生了什么:

public class Handler 
...

final MessageQueue mQueue;
final Callback mCallback;

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 " 
            + Thread.currentThread() + " that has not called 
            Looper.prepare()");
    

    mQueue = mLooper.mQueue;
    mCallback = callback;

我们看到调用new Handler()时会判断Looper.myLooper()方法获取当前线程的Looper,如果为空则会抛出运行时异常中断当前线程,不为空则拿当前线程的Looper对象中的MessageQueue对象,等待Handler#sendMessage()等方法向消息队列中添加消息。

因此,在子线程中直接new Handler()时,当前子线程的Looper对象势必为空,为空则不能继续消费Handler产生的Message了,自然得抛出一个异常。

Handler/Looper运行机制梳理

简单的学习了HandlerMessageQueueLooper后我们不难发现,当某个线程要使用Android的Handler消息机制时,首先要调用Looper#prepare()静态方法为当前线程生成一个Looper对象,紧接着调用Looper#loop()静态方法后,会拿出该线程的Looper对象的MessageQueue开始循环调用MessageQueue#next()方法获取消息队列的下一个Message并处理。

根据上面MessageQueue中的分析,当MessageQueue中没有下一个Message时,next()方法会调用MessageQueue#nativePollOnce()阻塞当前线程,直到下一个Message被加入并通过MessageQueue#nativeWake()唤醒阻塞,此时便可以拿出下一个Message返回给LooperLooper通过msg.target.dispatchMessage(msg)分发消息。

为了理解的更加全面,接下来看看Handler#dispatchMessage(Message)方法:

public class Handler 
...

final MessageQueue mQueue;
final Callback mCallback;


public interface Callback 
    public boolean handleMessage(Message msg);


public Handler() 
    this(null, false);


public Handler(Callback callback) 
    this(callback, false);


public Handler(Callback callback, boolean async) 
    mLooper = Looper.myLooper();
    ...

    mQueue = mLooper.mQueue;
    mCallback = callback;


// Looper中调用的分发方法
public void dispatchMessage(Message msg) 
    if (msg.callback != null) 
        handleCallback(msg);
     else 
        if (mCallback != null) 
            if (mCallback.handleMessage(msg)) 
                return;
            
        
        handleMessage(msg);
    


// 处理Runnable的Message
private static void handleCallback(Message message) 
    message.callback.run();


// 处理非Runnable的Message
public void handleMessage(Message msg) 

看到这里也就知道了,文章开头的Handler的接受消息为什么那么用了。在Handler#dispatchMessage(Message)中,首先判断了该Message是否是Runnable,如果是,则直接执行Runnable#run()方法,如果不是则看当前Handler是否有Callback对象,如果有的话就回调到Callback#handleMessage(Message)方法去,如果没有则调用Handler#handleMessage(Message)方法。

明白了以上的流程,到了我们得出第二个问题的答案的时候了:

Handler是如何切换线程的?

上面提到了,当某个线程要使用Android的消息机制时,首先必须要调用Looper#prepare()方法为当前线程生成一个Looper对象,然后在该线程中调用Looper#loop()拿出该线程的Looper对象的MessageQueue开始循环处理其中的消息,如果消息队列为空,那么该线程就会被MessageQueue#nativePollOnce()阻塞起来,只要该队列中进来消息时,该线程同时被MessageQueue#nativeWake()唤醒。其他线程要向该线程发送消息时,只要拿到该线程的Looper并在其他线程实例化Handler,在其他线程中使用Handler发送消息即可向该线程的MessageQueue中添加一个消息,此时该线程的Looper#loop()方法即可获取到消息并在该线程中处理了。

HandlerThread

根据以上原理我们来模拟一下使用流程,先封装一段代码:

public class HandlerThread extends Thread 

    private Looper mLooper;
    private Handler mHandler;

    public HandlerThread() 
    

    @Override
    public void run() 
        Looper.prepare();
        mLooper = Looper.myLooper();
        Looper.loop();
    
    
    public Looper getLooper() 
        return mLooper;
    

    public Handler getHandler() 
        if (mHandler == null) 
            mHandler = new Handler(getLooper());
        
        return mHandler;
    

    public void quit() 
        if (mLooper != null) 
            mLooper.quit();
        
    

上述类中,在线程运行时,初始化了该线程的Looper,并作为成员变量保存了起来,然后调用Looper#loop()静态方法让该Looper去处理发送到该线程的消息。

怎么用这个封装类呢?首先初始化一下让这个线程运行起来:

public class AbcThread implements Runnable 
...

HandlerThread thread = new HandlerThread();
thread.start();

此时该线程应该阻塞在Looper#Loop()处,如果我们发送一个Runnable在这个线程执行,那么我们可以:

public class AbcThread implements Runnable 
...

thread.getHandler().post(new Runnable() 
    @Ovvride
    public void run() 
        // 该处代码执行在 HandleThread#run() 方法中
    
);

另一种使用方式:

public class AbcThread implements Runnable 
...

Looper looper = thread.getLooper();
Handler mHandler = new Handler(looper) 
    @Override
    public void handleMessage(Message msg) 
        // 该处代码执行在 HandlerThread#run() 方法中
    
;

Message message = new Message();
...
mHandler.sendMessage(message);

上述代码都是伪代码,读者明白原理即可,不要照抄。Android SDK中也提供了HandlerThread以上是关于Android消息机制和应用的主要内容,如果未能解决你的问题,请参考以下文章

Android消息机制和应用

Android消息机制之同步障碍机制和应用

Android消息机制之同步障碍机制和应用

Android消息机制之同步障碍机制和应用

Android源码——应用程序的消息处理机制

消息机制