查看原文
其他

Android异步消息处理机制解析

码农西哥 技术最TOP 2022-08-27

点击上方“Android技术杂货铺”,选择“标星”

干货文章,第一时间送达!


本文来自手指尖的按恋投稿,博客地址:https://blog.csdn.net/gaolh89/article/details/90814343

Android异步消息处理机制主要是指Handler的运行机制以及Handler所附带的MessageQueueLooper的工作过程。

本文将通过结合源码(api-28)的形式,全面解析HandlerMessageQueueLooper的关系。并分析Android异步消息机制的相关原理。在分析之前,先给出结论性的东西,便于在分析过程中有一个主脉络。

在这里插入图片描述
一、Handler

在分析Handler源码之前,我们尝试在程序中创建两个Handler对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:

//代码片1
public class MainActivity extends Activity {

    private Handler handler1;

    private Handler handler2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler1 = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler2 = new Handler();
            }
        }).start();
    }

}

运行代码之后,会报错: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
并且提示是第16行代码报错.说是不能在没有调用Looper.prepare() 的线程中创建Handler。

那我们在第16行代码前面尝试先调用一下Looper.prepare()呢,即代码如下:

//代码片2
new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        handler2 = new Handler();
    }
}).start();

运行后发现的确没有报错了。

或许我们有疑问,为什么在子线程中加了Looper.prepare() 就不再报错了呢? 并且为什么第12行代码没有加Looper.prepare()却没有报错。

先带着第一个问题(即 子线程加了Looper.prepare()后不报错),来看看Handler的源码吧:

//代码片3
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;
        mAsynchronous = async;
    }

可以看到,在第5行调用了Looper.myLooper()方法获取了一个Looper对象,如果Looper对象为空,则会抛出一个运行时异常,提示的错误正是Can't create handler inside thread that has not called Looper.prepare()!那什么时候Looper对象才可能为空呢?这就要看看Looper.myLooper()中的代码了,如下所示:

//代码片4
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

这个方法非常简单,就是从sThreadLocal对象中调用get()方法。

从名字我们可以大胆猜测一下,这是从sThreadLocal取出Looper,如果没有Looper存在自然就返回空了。

既然有get,应该也有set()方法,我们搜索一下(在Looper.java类中)sThreadLocal.set,果然有:

代码如下:

//代码片5
 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.prepare()方法。

可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。

现在我们来通过源码分析 代码片1第12行代码没有添加Looper.myLooper() 方法也没有报错 的原因。这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:

//代码片6
public static void main(String[] args) {
   .....
   //核心代码如下
    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");
}

可以看到,在第5行调用了Looper.prepareMainLooper()方法,而这个方法又会再去调用Looper.prepare()方法,代码如下所示:

//代码片7
 public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
//代码片8
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对象,从而不需要再手动去调用Looper.prepare()方法了。

简单总结一下: Android的主线程是ActivityThread,主线程的入口为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper对象,同时也会生成其对应的MessageQueue对象,即主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象需手动通过Looper.prepare()创建。

学习完了Handler如何创建对象,我们看一下如何发送消息:

这个流程相信大家也已经非常熟悉了,new出一个Message对象,然后可以使用setData()方法或arg参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了,示例代码如下:

//代码片9
new Thread(new Runnable() {
    @Override
    public void run() {
        Message message = new Message();
        message.arg1 = 1;
        Bundle bundle = new Bundle();
        bundle.putString("data""data");
        message.setData(bundle);
        handler.sendMessage(message);
    }
}).start();

那我们来一下sendMessage方法干了什么。源码如下:

//代码片10
 public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0);
   }
//代码片11
 public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
  }
//代码片12
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);
    }
//代码片13
   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;       //把当前的Hanlder实例对象作为msg的target属性
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);      //执行接下来即将分析的代码片14
    }

代码片10~代码片13这四个方法都是连贯在一起的。

sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数( SystemClock.uptimeMillis() )再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueueenqueueMessage()方法中。

也说明了handler发出的消息msg,最终会保存到消息队列(MessageQueue )中去。

二、MessageQueue

在一中已经讲到handler发出的消息保存到了MessageQueue中。消息队列在Android中指的是MessageQueue ,MessageQueue主要包括两个操作: 插入和读取,分别对应enqueueMessagenext,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。

需要说明的是,MessageQueue 虽然叫消息队列,但它的内部实现并不是用的队列,而是通过一个单链表的数据结构来维护消息队列,单链表在插入和删除上比较有优势。

1. enqueueMessage源码

我们先来分析MessageQueue 的enqueueMessage源码:

//代码片14
boolean enqueueMessage(Message msg, long when) {

       .....
       //仅贴出核心代码

        synchronized (this) {
           .....
          //仅贴出核心代码

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //若消息队列里没有消息,则将当前插入的消息作为队头 & 若此时消息队列处于等待状态,则唤醒
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
               //若消息队列中有消息,则根据消息(message)创建的时间插入到队列中
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.

                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

观察上面的代码核心代码我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。

2. next 源码

入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?

这里先告诉结论,在Looper.loop()中有这样一行代码(见 稍后会分析的代码片21的第12行)。

next()方法在MessageQueue.class中。这段代码比较长,我们只贴出核心代码:

//代码片15
Message next() {
      ......
      //仅贴出核心代码

      // 该参数用于确定消息队列中是否还有消息,从而决定消息队列应处于出队消息状态 or 等待状态
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

           // nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;

                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }

               // 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                     // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1。下次循环时,消息队列则处于等待状态
                    nextPollTimeoutMillis = -1;
                }    

              ......
              //仅贴出核心代码
        }    // 因为是死循环,回到分析原处
    }

MessageQueue的next()方法关键思路在源码中我已经通过中文注释标明了,它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法的代码片第12行,每当有一个消息出队,就将它传递到msg.targetdispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示(Handler,class中):

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

在第5~6行进行判断,如果mCallback不为空,则调用mCallbackhandleMessage()方法,否则直接调用HandlerhandleMessage()方法,并将消息对象作为参数传递过去。那 handleMessage(msg)、handleCallback(msg) 具体是干嘛的呢? 我们继续看:

//代码片17
public void handleMessage(Message msg) {
}

可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。例如:

//代码片18
private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg){
            switch (msg.what){
            case value:
                //do something
                break;
            default:
              //do something
                break;
            }
        };
    };

至于handleCallback的代码如下:

//代码片19
 private static void handleCallback(Message message) {
        message.callback.run();
    }

执行的是Message 回调的run方法.其实就是我们平时写的如下代码中的run方法:

//代码片20
public class MainActivity extends Activity {

    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                 //do  something
            }
        }).start();
    }
}
三 、Looper

Looper的源码分析在一、二部分或多或少提到过 ,这里集中分析。

Looper主要是prepare()loop()两个方法。

1. Looper.prepare()方法:

在Handler部分的代码片3、4、5、6、7、8及说明中已经谈论过,这里略过。

2. Looper.loop()方法

这段代码比较长,我们还是看核心代码:

//代码片21
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;

        .....

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

          ......

            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

          ......

            msg.recycleUnchecked();      //释放资源
        }
    }

可以看到,这个方法从第11行开始,进入了一个死循环,然后不断地调用的MessageQueuenext()方法(即代码片15部分,也就是 二中MessageQueuenext()部分),我想你已经猜到了,这个next()方法就是消息队列的出队方法。在二中已经分析过了。

总结一下,Looper主要作用:

  • 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。

  • loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。

到此,这个流程已经解释完毕,让我们首先总结一下

  • 1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

  • 2、Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。

  • 3.Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。

  • 4.Handler的sendMessage方法,会给msgtarget赋值为handler自身,然后加入MessageQueue中。(代码片13第3行有中文注释说明)

  • 5.在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

看完了上述总结后,一个最标准的异步消息处理线程的写法应该是这样:

//代码片22
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();
      }
  }

那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:

在这里插入图片描述
四 、 其他在子线程操作UI的方法

另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:

1、 Handler的post()方法
2、 View的post()方法
3、 Activity的runOnUiThread()方法

我们先看Handler的post()方法:

//代码片23
 public final boolean post(Runnable r)  {
       return  sendMessageDelayed(getPostMessage(r), 0);
  }

这里分析一下getPostMessage(r)方法:

//代码片24
private final Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息.

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

到了这里的sendMessageAtTime方法,其实已经和第11个代码片完全一样了.

然后再来看一下View中的post()方法,代码如下所示:

//代码片26
 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

如果attachInfo 不为空,就调用Handler中的post()方法。

如果为空,我们看看一下代码片25第10行代码:

//代码片27

private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

即 看getRunQueue().post(action)做看什么,只需要在HandlerActionQueue类中查看post()方法即可。
继续看(HandlerActionQueue.class类)

//代码片28
public void post(Runnable action) {
        postDelayed(action, 0);
 }

public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

结合这篇文章(点击跳转),可以看出,如果attachInfo 为空,最终也会调用Handler中的post()方法。

我们最后再来看一下Activity中的runOnUiThread()方法:

//代码片29
   /**
     * Runs the specified action on the UI thread. If the current thread is the UI
     * thread, then the action is executed immediately. If the current thread is
     * not the UI thread, the action is posted to the event queue of the UI thread.
     *
     * @param action the action to run on the UI thread
     */

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

如果当前线程不是UI线程,就调用Handler的post()方法,反之直接运行(也可以结合方法上的注释看)。

对此,Android的异步消息处理机制全部分析完毕。

--- end---

推荐阅读:

一个炫酷的Material Design 引导View

Android 基于Transform实现更高效的组件化路由框架

一款Android开发者神器,从此不怕Show case

每一个“在看”,我都当成真的喜欢

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存