使用过AsyncTask、EventBus、Volley以及Retrofit,必须好好了解handler运行机制

咱们都知道在UI线程中不能进行耗时操做,例如数据读写、网络请求。Android 4.0开始,在主线程中进行网络请求甚至会抛出Android.os.NetworkOnMainThreadException。这个时候,咱们就会开始依赖Handler。咱们在子线程进行耗时操做后,将请求结果经过Handler的sendMessge**() 方法发送出去,在主线程中经过Handler的handleMessage 方法处理请求结果,进行UI的更新。java

后来随着AsyncTask、EventBus、Volley以及Retrofit 的出现,Handler的做用彷佛被弱化,逐渐被你们遗忘。其实否则,AsyncTask实际上是基于Handler进行了很是巧妙的封装,Handler的使用依然是其核心。Volley一样也是使用到了Handler。所以,咱们有必要了解一下Handler的实现机制。android

神奇的Handler

记得好久以前的一天,我在阅读别人的代码时,看到了这样一段:网络

  1.  
    new Handler().postDelayed(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
    Toast.makeText(mContext, "I'm new Handler !", Toast.LENGTH_SHORT).show();
  5.  
    }
  6.  
    }, 1000);

第一印象就是,这不是在子线程中进行UI操做吗?这代码有问题吧,因而乎马上在本身电脑上写了个demo试了一下,结果发现真的没有问题。在一阵懵逼事后,我又写出下面的代码,测试一会儿线程中到底能不能进行UI操做。多线程

  1.  
    new Thread(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
     
  5.  
    try {
  6.  
    Thread.sleep( 2000);
  7.  
    } catch (InterruptedException e) {
  8.  
    e.printStackTrace();
  9.  
    }
  10.  
     
  11.  
    Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();
  12.  
     
  13.  
    }
  14.  
    }).start();

结果很明显,程序一启动马上就奔溃了。并抛出异常java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
因而乎我又在try block 以前添加了Looper.prepare()这行代码。再次运行程序虽然没有奔溃,但也没有任何反应,Toast也没显示。并发

那么Handler究竟是什么呢?他怎么就这么神奇。app

实现机制解析

首先,咱们从总体上了解一下,在整个Handler机制中全部使用到的类,主要包括Message,MessageQueue,Looper以及Handler。less

 

 

好了,为了方便后面的叙述,咱们就首先了解一下这个类图中使用到几个类,及其关键方法。异步

Message

首先看一下Message这个类的定义(截取部分)async

  1.  
    public final class Message implements Parcelable {
  2.  
     
  3.  
    public int what;
  4.  
    public int arg1;
  5.  
    public int arg2;
  6.  
    public Object obj;
  7.  
    /*package*/ Handler target;
  8.  
    /*package*/ Runnable callback;
  9.  
     
  10.  
    /**
  11.  
    * Return a new Message instance from the global pool. Allows us to
  12.  
    * avoid allocating new objects in many cases.
  13.  
    */
  14.  
    public static Message obtain() {
  15.  
    synchronized (sPoolSync) {
  16.  
    if (sPool != null) {
  17.  
    Message m = sPool;
  18.  
    sPool = m.next;
  19.  
    m.next = null;
  20.  
    m.flags = 0; // clear in-use flag
  21.  
    sPoolSize--;
  22.  
    return m;
  23.  
    }
  24.  
    }
  25.  
    return new Message();
  26.  
    }
  27.  
     
  28.  
    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
  29.  
    */
  30.  
    public Message() {
  31.  
    }
  32.  
    }

看到这个类的前四个属性,你们应该很熟悉,就是咱们使用Handler时常常用到的那几个属性。用来在传递咱们特定的信息。其次咱们还能够总结出如下信息:ide

  • Message 实现了Parcelable 接口,也就是说实现了序列化,这就说明Message能够在不一样进程之间传递。
  • 包含一个名为target的Handler 对象
  • 包含一个名为callback的Runnable 对象
  • 使用obtain 方法能够从消息池中获取Message的实例,也是推荐你们使用的方法,而不是直接调用构造方法。

MessageQueue

MessageQueue顾名思义,就是上面所说的Message所组成的queue。

首先看一下构造方法:

  1.  
    MessageQueue( boolean quitAllowed) {
  2.  
    mQuitAllowed = quitAllowed;
  3.  
    mPtr = nativeInit();
  4.  
    }

接收一个参数,决定当前队列是否容许被终止。同时调用 一个native方法,初始化了一个long类型的变量mPtr。

同时,在这个类当中,还定义了一个next 方法,用于返回一个Message 。

  1.  
    Message next() {
  2.  
    // Return here if the message loop has already quit and been disposed.
  3.  
    // This can happen if the application tries to restart a looper after quit
  4.  
    // which is not supported.
  5.  
    final long ptr = mPtr;
  6.  
    if (ptr == 0) {
  7.  
    return null;
  8.  
    }
  9.  
    ……
  10.  
    }

因为这个方法中有一些native调用,未能彻底理解,只知道会返回一个Message对象。

这个next方法至关因而队列出栈,有出栈必然有进栈,enqueueMessage 方法就是完成这个操做;这个咱们后面再说。

Looper

上面说到了MessageQueue,那么这个Queue又是由谁建立的呢?其实就是Looper。关于Looper有两个关键方法:

prepare() 和 loop()

Looper-prepare()

  1.  
    public static void prepare() {
  2.  
    prepare( true);
  3.  
    }
  4.  
     
  5.  
    private static void prepare(boolean quitAllowed) {
  6.  
    if (sThreadLocal.get() != null) {
  7.  
    throw new RuntimeException("Only one Looper may be created per thread");
  8.  
    }
  9.  
    sThreadLocal.set( new Looper(quitAllowed));
  10.  
    }

能够看到,对于每个线程只能有一个Looper。也就是说执行prepare方法时,必然执行最后一行代码
sThreadLocal.set(new Looper(quitAllowed));

咱们再看Looper(quitAllowed)方法:

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

这样,MessageQueue 就被建立了。这里也能够看到,默认状况下,一个MessageQueue的quiteAllow=true。

这里使用到的sThreadLocal 是一个ThreadLocal对象。简单来讲,使用它能够用来解决多线程程序的并发问题。使用set方法,将此线程局部变量的当前线程副本中的值设置为指定值;使用get方法,返回此线程局部变量的当前线程副本中的值。

Looper-loop()

再看一下loop方法(截取主要逻辑)

  1.  
    public static void loop() {
  2.  
    final Looper me = myLooper();
  3.  
    if (me == null) {
  4.  
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  5.  
    }
  6.  
    final MessageQueue queue = me.mQueue;
  7.  
    for (;;) {
  8.  
    Message msg = queue.next(); // might block
  9.  
    if (msg == null) {
  10.  
    // No message indicates that the message queue is quitting.
  11.  
    return;
  12.  
    }
  13.  
    msg.target.dispatchMessage(msg);
  14.  
    msg.recycleUnchecked();
  15.  
    }
  16.  
    }

首先看第一句代码执行的方法:

  1.  
    /**
  2.  
    * Return the Looper object associated with the current thread. Returns
  3.  
    * null if the calling thread is not associated with a Looper.
  4.  
    */
  5.  
    public static @Nullable Looper myLooper() {
  6.  
    return sThreadLocal.get();
  7.  
    }

很明显,这样返回的Looper就是刚才prepare时set进去的那个,由于都是在同一线程。再明确一下,一个线程对应一个Looper。

这样就确保咱们能够在不一样的线程中建立各自的Handler,进行各自的通讯而不会互相干扰

回到代码,后面逻辑就很简单了,在一个死循环中,经过队列出栈的形式,不断从MessageQueue 中取出新的Message,而后执行msg.target.dispatchMessage(msg) 方法,还记的前面Message类的定义吗,这个target属性其实就是一个Handler 对象,所以在这里就会不断去执行Handler 的dispatchMessage 方法。若是取出的Message对象为null,就会跳出死循环,一次Handler的工做整个就结束了。

Handler

上面说了这么多终于轮到Handler,那么就看看在Handler中到底发生了什么。回到咱们一开始的代码。

  1.  
    new Handler().postDelayed(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
    String currentName=Thread.currentThread().getName();
  5.  
    Toast.makeText(mContext, "I'm new Thread "+currentName, Toast.LENGTH_SHORT).show();
  6.  
    }
  7.  
    }, 4000);

这里咱们用Toast弹出了当前线程的name,结果发现这个线程的名字竟然是main,这也是必然结果

让咱们一步一步看看,神奇的Handler究竟是怎样工做的。就从这个代码开始解读。首先看一下Handler的构造方法。

  1.  
    public Handler() {
  2.  
    this(null, false);
  3.  
    }
  4.  
     
  5.  
    ---------------
  6.  
     
  7.  
    public Handler(Callback callback, boolean async) {
  8.  
    if (FIND_POTENTIAL_LEAKS) {
  9.  
    final Class<? extends Handler> klass = getClass();
  10.  
    if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
  11.  
    (klass.getModifiers() & Modifier.STATIC) == 0) {
  12.  
    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
  13.  
    klass.getCanonicalName());
  14.  
    }
  15.  
    }
  16.  
     
  17.  
    mLooper = Looper.myLooper();
  18.  
    if (mLooper == null) {
  19.  
    throw new RuntimeException(
  20.  
    "Can't create handler inside thread that has not called Looper.prepare()");
  21.  
    }
  22.  
    mQueue = mLooper.mQueue;
  23.  
    mCallback = callback;
  24.  
    mAsynchronous = async;
  25.  
    }

这里作的事情很简单,就是完成了一些初始化的工做,调用Looper.myLooper()赋值给当前mLooper,关联MessageQueue;这里因为代码中调用的是不带任何参数的构造函数,所以会建立一个mCallback=null且非异步执行的Handler 。

接下看postDelayed 方法。

  1.  
    public final boolean postDelayed(Runnable r, long delayMillis)
  2.  
    {
  3.  
    return sendMessageDelayed(getPostMessage(r), delayMillis);
  4.  
    }
  5.  
     
  6.  
    private static Message getPostMessage(Runnable r) {
  7.  
    Message m = Message.obtain();
  8.  
    m.callback = r;
  9.  
    return m;
  10.  
    }

这里经过getPostMessage(Runnable r) 方法,把咱们在Activity里写的Runnable 这个线程赋给了Message 的callback这个属性。

平时你们使用Handler也发现了,他为咱们提供了不少方法

 

 

所以,上面的postDelayed通过了各类展转反侧,最终来到了这里:

  1.  
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  2.  
    MessageQueue queue = mQueue;
  3.  
    if (queue == null) {
  4.  
    RuntimeException e = new RuntimeException(
  5.  
    this + " sendMessageAtTime() called with no mQueue");
  6.  
    Log.w( "Looper", e.getMessage(), e);
  7.  
    return false;
  8.  
    }
  9.  
    return enqueueMessage(queue, msg, uptimeMillis);
  10.  
    }

通过以前的构造方法,mQueue显然不为null,继续往下看

  1.  
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  2.  
    msg.target = this;
  3.  
    if (mAsynchronous) {
  4.  
    msg.setAsynchronous( true);
  5.  
    }
  6.  
    return queue.enqueueMessage(msg, uptimeMillis);
  7.  
    }

注意,注意,注意 这里进行了一次赋值:

msg.target = this;

前面提到,这个target就是一个Handler对象,所以这里Message就和当前Handler关联起来了。enqueueMessage,哈哈,这就是咱们以前在MessageQueue中提到的进栈操做的方法,咱们看一下:

  1.  
    boolean enqueueMessage(Message msg, long when) {
  2.  
    if (msg.target == null) {
  3.  
    throw new IllegalArgumentException("Message must have a target.");
  4.  
    }
  5.  
    if (msg.isInUse()) {
  6.  
    throw new IllegalStateException(msg + " This message is already in use.");
  7.  
    }
  8.  
     
  9.  
    synchronized (this) {
  10.  
    if (mQuitting) {
  11.  
    IllegalStateException e = new IllegalStateException(
  12.  
    msg.target + " sending message to a Handler on a dead thread");
  13.  
    Log.w(TAG, e.getMessage(), e);
  14.  
    msg.recycle();
  15.  
    return false;
  16.  
    }
  17.  
     
  18.  
    msg.markInUse();
  19.  
    msg.when = when;
  20.  
    Message p = mMessages;
  21.  
    boolean needWake;
  22.  
    if (p == null || when == 0 || when < p.when) {
  23.  
    // New head, wake up the event queue if blocked.
  24.  
    msg.next = p;
  25.  
    mMessages = msg;
  26.  
    needWake = mBlocked;
  27.  
    } else {
  28.  
    // Inserted within the middle of the queue. Usually we don't have to wake
  29.  
    // up the event queue unless there is a barrier at the head of the queue
  30.  
    // and the message is the earliest asynchronous message in the queue.
  31.  
    needWake = mBlocked && p.target == null && msg.isAsynchronous();
  32.  
    Message prev;
  33.  
    for (;;) {
  34.  
    prev = p;
  35.  
    p = p.next;
  36.  
    if (p == null || when < p.when) {
  37.  
    break;
  38.  
    }
  39.  
    if (needWake && p.isAsynchronous()) {
  40.  
    needWake = false;
  41.  
    }
  42.  
    }
  43.  
    msg.next = p; // invariant: p == prev.next
  44.  
    prev.next = msg;
  45.  
    }
  46.  
     
  47.  
    // We can assume mPtr != 0 because mQuitting is false.
  48.  
    if (needWake) {
  49.  
    nativeWake(mPtr);
  50.  
    }
  51.  
    }
  52.  
    return true;
  53.  
    }

这个方法就是典型的队列入队操做,只不过会根据Message这个对象特有的一些属性,以及当前的状态是否inUse,是否已经被quit等进行一些额外的判断。

这样,咱们就完成消息入队的操做。还记得咱们在Looper中说过,在loop方法中,会从MessageQueue中取出Message 并执行他的dispatchMessage 方法。

dispatchMessage

  1.  
    public void dispatchMessage(Message msg) {
  2.  
    if (msg.callback != null) {
  3.  
    handleCallback(msg);
  4.  
    } else {
  5.  
    if (mCallback != null) {
  6.  
    if (mCallback.handleMessage(msg)) {
  7.  
    return;
  8.  
    }
  9.  
    }
  10.  
    handleMessage(msg);
  11.  
    }
  12.  
    }

到这里,就很明确了,在以前的postDelayed 方法中,已经经过getPostMessage,实现了 m.callback = r;这样这里就会执行第一个if语句:

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

这样,就会执行咱们在Activity 的Runnable 中的run 方法了,也就是显示Toast。

到了这里,咱们终于明白了,使用Handler 的postDelay 方法时,其Runnable中的run方法并非在子线程中执行,而是把这个Runnable赋值给了一个Message对象的callback属性,而这个Message会被传递到建立Handler所在的线程,也就是这里的主线程,因此这个Toast的显示依旧是在主线程中。这也和postDelay API 中所声明的内容是一致的。

/**

  1.  
    * Causes the Runnable r to be added to the message queue, to be run
  2.  
    * after the specified amount of time elapses.
  3.  
    * The runnable will be run on the thread to which this handler
  4.  
    * is attached.
  5.  
    * /

到这里,一开始所说的第一个代码块所执行的逻辑已经理清楚了,可是仍是有一点疑问,咱们并无在Handler的构造方法中看到Looper 的prepare()方法和loop() 方法被执行,那么他们究竟是在哪里执行的呢?这个问题我也是疑惑了好久,最终才明白是在
ActivityThread的main方法中执行。简单来讲,ActivityThread是Java层面一个Android程序真正的入口。关于ActivityThread更多的内容能够看看这篇文章

ActivityThread-main方法(截取主要部分)

  1.  
    public static void main(String[] args) {
  2.  
     
  3.  
     
  4.  
    Looper.prepareMainLooper();
  5.  
     
  6.  
    ActivityThread thread = new ActivityThread();
  7.  
    thread.attach( false);
  8.  
     
  9.  
    if (sMainThreadHandler == null) {
  10.  
    sMainThreadHandler = thread.getHandler();
  11.  
    }
  12.  
     
  13.  
    if (false) {
  14.  
    Looper.myLooper().setMessageLogging( new
  15.  
    LogPrinter(Log.DEBUG, "ActivityThread"));
  16.  
    }
  17.  
     
  18.  
    // End of event ActivityThreadMain.
  19.  
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  20.  
    Looper.loop();
  21.  
     
  22.  
    throw new RuntimeException("Main thread loop unexpectedly exited");
  23.  
    }

这个类藏的比较深,你能够在Android-SDK\sources\android-24\android\app 这个目录中找到。

也就是说,在一个Android 程序启动之初,系统会帮咱们为这个主线程建立好Looper。只不过这个方法名字比较特殊,叫作prepareMainLooper。

  1.  
    public static void prepareMainLooper() {
  2.  
    prepare( false);
  3.  
    synchronized (Looper.class) {
  4.  
    if (sMainLooper != null) {
  5.  
    throw new IllegalStateException("The main Looper has already been prepared.");
  6.  
    }
  7.  
    sMainLooper = myLooper();
  8.  
    }
  9.  
    }

注意这里调用prepare时传递的参数值为false,和咱们以前建立普通Looper时是不一样的,这也 能够理解,由于这是主线程,怎么能够被容许被外部代码终止呢。

到这里,咱们终于完整的理解了开头咱们提到的第一个代码块的内容了。

至于第二种使用写法出错的缘由也在明显不过了,主线程会在程序启动时在main方法中帮咱们主动建立Looper,调用loop方法;而咱们本身建立的线程就得咱们主动去调用Looper.prepare(),这样才能保证MessageQueue被建立,程序不会奔溃;可是咱们所指望的Toast依然没有显示出来,这是为何呢?由于,咱们没有调用loop方法。消息被加入队列了,可是没有办法弹出。所以咱们将代码修改以下:

  1.  
    new Thread(new Runnable() {
  2.  
    @Override
  3.  
    public void run() {
  4.  
     
  5.  
    Looper.prepare();
  6.  
     
  7.  
    try {
  8.  
    Thread.sleep( 2000);
  9.  
    } catch (InterruptedException e) {
  10.  
    e.printStackTrace();
  11.  
    }
  12.  
     
  13.  
    Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();
  14.  
     
  15.  
    Looper.loop();
  16.  
     
  17.  
    }
  18.  
    }).start();

这样就没问题了,Toast就能够显示出来了。实际上,平时写代码确定不会这么写,这里只是为了说明问题。

handleMessage

回想一下,咱们使用Handler最多见的场景:

  1.  
    handler = new MyHandler();
  2.  
     
  3.  
    private class MyCallback implements Callback {
  4.  
     
  5.  
    @Override
  6.  
    public void onFailure(Call call, IOException e) {
  7.  
    Message msg = new Message();
  8.  
    msg.what = 100;
  9.  
    msg.obj = e;
  10.  
    handler.sendMessage(msg);
  11.  
    }
  12.  
     
  13.  
    @Override
  14.  
    public void onResponse(Call call, Response response) throws IOException {
  15.  
    Message msg = new Message();
  16.  
    msg.what = 200;
  17.  
    msg.obj = response.body().string();
  18.  
    handler.sendMessage(msg);
  19.  
    }
  20.  
    }

上面的代码是OKHttp的回调方法,因为其回调方法不处于UI 线程,所以须要咱们经过Handler将结果发送到主线中取执行。
那么这又是怎样实现的呢?

前面咱们截图说过,Handler为咱们提供许多sendMessage 相关的方法,所以这里咱们在onResponse 中执行的sendMessage 通过层层传递,异曲同工依然会回到MessageQueue的enqueueMessage方法,也就是说全部的sendMessageXXX方法完成的工做无非就是队列入栈的工做,就是将包含特定信息的Message加入到MessageQueue中。而咱们也知道,经过loop方法,会从MessageQueue中取出Message,执行每个Message 所对应Handler的dispatchMessage方法,咱们再看一次这个方法:

  1.  
     
  2.  
     
  3.  
    public void dispatchMessage(Message msg) {
  4.  
    if (msg.callback != null) {
  5.  
    handleCallback(msg);
  6.  
    } else {
  7.  
    if (mCallback != null) {
  8.  
    if (mCallback.handleMessage(msg)) {
  9.  
    return;
  10.  
    }
  11.  
    }
  12.  
    handleMessage(msg);
  13.  
    }
  14.  
    }

这一次,咱们建立的Message很简单,其callback属性必然是空的,并且在实例化Handler时,调用的是其无参构造函数 ,所以这个时候,就会执行最后一行代码handleMessage(msg) ;

  1.  
    /**
  2.  
    * Subclasses must implement this to receive messages.
  3.  
    */
  4.  
    public void handleMessage(Message msg) {
  5.  
    }

空的 ! 没错,这个方法就是空的,由于这是须要咱们在Handler的继承类中本身实现的方法呀。好比下面这样;

  1.  
    class MyHandler extends Handler {
  2.  
    @Override
  3.  
    public void handleMessage(Message msg) {
  4.  
    super.handleMessage(msg);
  5.  
    loading.setVisibility(View.GONE);
  6.  
    switch (msg.what) {
  7.  
    case 100:
  8.  
    Object e = msg.obj;
  9.  
    Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
  10.  
    break;
  11.  
    case 200:
  12.  
    String response = (String) msg.obj;
  13.  
    tv.setText(response);
  14.  
    break;
  15.  
    default:
  16.  
    break;
  17.  
    }
  18.  
    }
  19.  
    }

咱们在handleMessage方法中,实现了本身的处理逻辑。

总结

好了,这就是Handler的实现机制,这里再作一次总结称述。

  • 经过Looper的prepare方法建立MessageQueue
  • 经过loop方法找到和当前线程匹配的Looper对象me
  • 从me中取出消息队列对象mQueue
  • 在一个死循环中,从mQueue中取出Message对象
  • 调用每一个Message对象的msg.target.dispatchMesssge方法
  • 也就是Handler的dispatchMessage 方法
  • 在dispatchMessage 根据Message对象的特色执行特定的方法。

至此,终于弄明白了Handler的运行机制。

相关文章
相关标签/搜索