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

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

一.Handler

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

//代码片1
public class MainActivity extends Activity {private Handler handler1;private Handler handler2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler1 = new Handler();new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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()中的代码了,如下所示:

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

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

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

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

代码如下:

//代码片5private 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()方法,代码如下所示:

//代码片7public 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() {@Overridepublic 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方法干了什么。源码如下:

//代码片10public final boolean sendMessage(Message msg) {return sendMessageDelayed(msg, 0);}
//代码片11public 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);}
//代码片13private 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,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。

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

二.MessageQueue

在一中已经讲到handler发出的消息保存到了MessageQueue 中。消息队列在Android中指的是MessageQueue ,MessageQueue主要包括两个操作: 插入和读取,分别对应enqueueMessage和next,其中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.nextprev.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.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示(Handler,class中):

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

在第5~6行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。那 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 somethingbreak;default://do somethingbreak;}};};

至于handleCallback的代码如下:

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

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

//代码片20
public class MainActivity extends Activity {private Handler handler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler = new Handler();new Thread(new Runnable() {@Overridepublic 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 blockif (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行开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法(即代码片15部分,也就是 二中MessageQueue的next()部分),我想你已经猜到了,这个next()方法就是消息队列的出队方法。在二中已经分析过了。

总结一下,Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 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方法,会给msg的target赋值为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()方法:

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

这里分析一下getPostMessage®方法:

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

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

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

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

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

//代码片26public 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的异步消息处理机制全部分析完毕。

Android异步消息处理机制 全解析相关推荐

  1. Android异步消息处理机制完全解析,带你从源码的角度彻底理解

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9991569 之前也是由于周末通宵看TI3比赛,一直没找到时间写博客,导致已经有好久 ...

  2. [学习总结]6、Android异步消息处理机制完全解析,带你从源码的角度彻底理解

    开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一 ...

  3. Android异步加载全解析之引入二级缓存

    Android异步加载全解析之引入二级缓存 为啥要二级缓存 前面我们有了一级缓存,为啥还要二级缓存呢?说白了,这就和电脑是一样的,我们电脑有内存和硬盘,内存读取速度快,所以CPU直接读取内存中的数据, ...

  4. Android异步消息处理机制 深入理解Looper、Handler、Message三者关系

    转载子:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自[张鸿洋的博客] 很多人面试肯定都被问到过,请问Android中 ...

  5. Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

    很多人面试肯定都被问到过,请问Android中的Looper , Handler , Message有什么关系?本篇博客目的首先为大家从源码角度介绍3者关系,然后给出一个容易记忆的结论. 1. 概述 ...

  6. Android异步消息处理机制

    Android异步消息常用汇总 android常用异步框架分为handler.AsyncTask.handlerThread.IntentService. 什么是handler android消息机制 ...

  7. Hander异步消息处理机制完全解析

    那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接 ...

  8. Android 异步消息处理机制(Handler 、 Looper 、MessageQueue)源码解析

    1.Handler的由来 当程序第一次启动的时候,Android会同时启动一条主线程( Main Thread)来负责处理与UI相关的事件,我们叫做UI线程. Android的UI操作并不是线程安全的 ...

  9. Android异步消息处理机制之looper机制

    在android系统的应用程序中,与java程序相似,线程之间都是靠消息来驱动的,与此工作相关的是由handler,looper以及message,messagequene来完成.简单来说就是每一个线 ...

最新文章

  1. rtklib 天线相位中心_基于传输线匹配理论的OAM透射阵天线设计
  2. Rust 阴阳谜题,及纯基于代码的分析与化简
  3. Keil精确测量代码运行时间
  4. python JSON ValueError: Expecting property name: line 1 column 2 (char 1)
  5. 7-20(排序) 奥运排行榜(25 分)
  6. 日常工作部门及体系相关单词
  7. HTML5新增的标签
  8. MySQL数据类型--日期时间
  9. spring boot 异常汇总
  10. 微信小程序websocket连接服务器(接收信息)
  11. linux arch 包管理,Archlinux使用包管理方式安装MyEclipse
  12. python列表添加字符串_2.python基础之—列表,元组,字典,集合,字符串的使用方法...
  13. html5 响应式背景图
  14. Android ProGuard 代码压缩混淆与打包优化
  15. openwrt信号弱掉线_QCA9880 openwrt 信号非常差
  16. 用AI为金融行业赋能 一览群智发布金融行业智能产品
  17. 柳州计算机一级考试报名时间,2021计算机考试报名时间和考试时间
  18. Ext JS 6学习文档–第1章–ExtJS入门指南
  19. 算法篇----求两数的最大公约数和最小公倍数
  20. 刘韵洁:未来网络技术发展趋势与展望

热门文章

  1. eslint 无法格式化ts_vscode-eslint的踩坑实践--typescript无法格式化
  2. arraylist从大到小排序_初学Python最简易入门之十四排序算法10对字典排序
  3. 组队练习赛(Regionals 2012, North America - East Central NA)
  4. 【新功能】MaxCompoute禁止Full Scan功能开放
  5. Nginx+Tomcat动静分离及Nginx优化(企业案例)
  6. c#接口和抽象类对比学习
  7. 突发奇想写的一篇小科幻(如果可以叫做科幻的话)
  8. 【R语言学习】时间序列
  9. 关于移动端架构的好的学习网站
  10. [Nand Flash]软件ECC