`

Android异步消息处理机制完全解析

阅读更多

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9991569

之前也是由于周末通宵看TI3比赛,一直没找到时间写博客,导致已经有好久没更新了。惭愧!后面还会恢复进度,尽量保证每周都写吧。这里也是先恭喜一下来自瑞典的Alliance战队夺得了TI3的冠军,希望明年中国战队能够虎起!

开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。

这种处理方式被称为异步消息处理线程,虽然我相信大家都会用,可是你知道它背后的原理是什么样的吗?今天我们就来一起深入探究一下Handler和Message背后的秘密。

首先来看一下如何创建Handler对象。你可能会觉得挺纳闷的,创建Handler有什么好看的呢,直接new一下不就行了?确实,不过即使只是简单new一下,还是有不少地方需要注意的,我们尝试在程序中创建两个Handler对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.       
  3.     private Handler handler1;  
  4.       
  5.     private Handler handler2;  
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         handler1 = new Handler();  
  12.         new Thread(new Runnable() {  
  13.             @Override  
  14.             public void run() {  
  15.                 handler2 = new Handler();  
  16.             }  
  17.         }).start();  
  18.     }  
  19.   
  20. }  

如果现在运行一下程序,你会发现,在子线程中创建的Handler是会导致程序崩溃的,提示的错误信息为 Can't create handler inside thread that has not called Looper.prepare() 。说是不能在没有调用Looper.prepare() 的线程中创建Handler,那我们尝试在子线程中先调用一下Looper.prepare()呢,代码如下所示:

[java] view plaincopy
  1. new Thread(new Runnable() {  
  2.     @Override  
  3.     public void run() {  
  4.         Looper.prepare();  
  5.         handler2 = new Handler();  
  6.     }  
  7. }).start();  

果然这样就不会崩溃了,不过只满足于此显然是不够的,我们来看下Handler的源码,搞清楚为什么不调用Looper.prepare()就不行呢。Handler的无参构造函数如下所示:

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

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

[java] view plaincopy
  1. public static final Looper myLooper() {  
  2.     return (Looper)sThreadLocal.get();  
  3. }  

这个方法非常简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果没有Looper存在自然就返回空了。因此你可以想象得到是在哪里给sThreadLocal设置Looper了吧,当然是Looper.prepare()方法!我们来看下它的源码:

[java] view plaincopy
  1. public static final void prepare() {  
  2.     if (sThreadLocal.get() != null) {  
  3.         throw new RuntimeException("Only one Looper may be created per thread");  
  4.     }  
  5.     sThreadLocal.set(new Looper());  
  6. }  

 

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

咦?不对呀!主线程中的Handler也没有调用Looper.prepare()方法,为什么就没有崩溃呢?细心的朋友我相信都已经发现了这一点,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:

[java] view plaincopy
  1. public static void main(String[] args) {  
  2.     SamplingProfilerIntegration.start();  
  3.     CloseGuard.setEnabled(false);  
  4.     Environment.initForCurrentUser();  
  5.     EventLogger.setReporter(new EventLoggingReporter());  
  6.     Process.setArgV0("<pre-initialized>");  
  7.     Looper.prepareMainLooper();  
  8.     ActivityThread thread = new ActivityThread();  
  9.     thread.attach(false);  
  10.     if (sMainThreadHandler == null) {  
  11.         sMainThreadHandler = thread.getHandler();  
  12.     }  
  13.     AsyncTask.init();  
  14.     if (false) {  
  15.         Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));  
  16.     }  
  17.     Looper.loop();  
  18.     throw new RuntimeException("Main thread loop unexpectedly exited");  
  19. }  

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

[java] view plaincopy
  1. public static final void prepareMainLooper() {  
  2.     prepare();  
  3.     setMainLooper(myLooper());  
  4.     if (Process.supportsProcesses()) {  
  5.         myLooper().mQueue.mQuitAllowed = false;  
  6.     }  
  7. }  

因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。

这样基本就将Handler的创建过程完全搞明白了,总结一下就是在主线程中可以直接创建Handler对象,而在子线程中需要先调用Looper.prepare()才能创建Handler对象。

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

[java] view plaincopy
  1. new Thread(new Runnable() {  
  2.     @Override  
  3.     public void run() {  
  4.         Message message = new Message();  
  5.         message.arg1 = 1;  
  6.         Bundle bundle = new Bundle();  
  7.         bundle.putString("data""data");  
  8.         message.setData(bundle);  
  9.         handler.sendMessage(message);  
  10.     }  
  11. }).start();  

 

可是这里Handler到底是把Message发送到哪里去了呢?为什么之后又可以在Handler的handleMessage()方法中重新得到这条Message呢?看来又需要通过阅读源码才能解除我们心中的疑惑了,Handler中提供了很多个发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中,这个方法的源码如下所示:

[java] view plaincopy
  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
  2. {  
  3.     boolean sent = false;  
  4.     MessageQueue queue = mQueue;  
  5.     if (queue != null) {  
  6.         msg.target = this;  
  7.         sent = queue.enqueueMessage(msg, uptimeMillis);  
  8.     }  
  9.     else {  
  10.         RuntimeException e = new RuntimeException(  
  11.             this + " sendMessageAtTime() called with no mQueue");  
  12.         Log.w("Looper", e.getMessage(), e);  
  13.     }  
  14.     return sent;  
  15. }  

sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。这个MessageQueue又是什么东西呢?其实从名字上就可以看出了,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。

 

那么enqueueMessage()方法毫无疑问就是入队的方法了,我们来看下这个方法的源码:

[java] view plaincopy
  1. final boolean enqueueMessage(Message msg, long when) {  
  2.     if (msg.when != 0) {  
  3.         throw new AndroidRuntimeException(msg + " This message is already in use.");  
  4.     }  
  5.     if (msg.target == null && !mQuitAllowed) {  
  6.         throw new RuntimeException("Main thread not allowed to quit");  
  7.     }  
  8.     synchronized (this) {  
  9.         if (mQuiting) {  
  10.             RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");  
  11.             Log.w("MessageQueue", e.getMessage(), e);  
  12.             return false;  
  13.         } else if (msg.target == null) {  
  14.             mQuiting = true;  
  15.         }  
  16.         msg.when = when;  
  17.         Message p = mMessages;  
  18.         if (p == null || when == 0 || when < p.when) {  
  19.             msg.next = p;  
  20.             mMessages = msg;  
  21.             this.notify();  
  22.         } else {  
  23.             Message prev = null;  
  24.             while (p != null && p.when <= when) {  
  25.                 prev = p;  
  26.                 p = p.next;  
  27.             }  
  28.             msg.next = prev.next;  
  29.             prev.next = msg;  
  30.             this.notify();  
  31.         }  
  32.     }  
  33.     return true;  
  34. }  

首先你要知道,MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码的16~31行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。
现在入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?这个就需要看一看Looper.loop()方法的源码了,如下所示:

[java] view plaincopy
  1. public static final void loop() {  
  2.     Looper me = myLooper();  
  3.     MessageQueue queue = me.mQueue;  
  4.     while (true) {  
  5.         Message msg = queue.next(); // might block  
  6.         if (msg != null) {  
  7.             if (msg.target == null) {  
  8.                 return;  
  9.             }  
  10.             if (me.mLogging!= null) me.mLogging.println(  
  11.                     ">>>>> Dispatching to " + msg.target + " "  
  12.                     + msg.callback + ": " + msg.what  
  13.                     );  
  14.             msg.target.dispatchMessage(msg);  
  15.             if (me.mLogging!= null) me.mLogging.println(  
  16.                     "<<<<< Finished to    " + msg.target + " "  
  17.                     + msg.callback);  
  18.             msg.recycle();  
  19.         }  
  20.     }  
  21. }  

可以看到,这个方法从第4行开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。不过由于这个方法的代码稍微有点长,我就不贴出来了,它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法的第14行,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示:

[java] view plaincopy
  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. }  

在第5行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

 

因此,一个最标准的异步消息处理线程的写法应该是这样:

[java] view plaincopy
  1. class LooperThread extends Thread {  
  2.       public Handler mHandler;  
  3.   
  4.       public void run() {  
  5.           Looper.prepare();  
  6.   
  7.           mHandler = new Handler() {  
  8.               public void handleMessage(Message msg) {  
  9.                   // process incoming messages here  
  10.               }  
  11.           };  
  12.   
  13.           Looper.loop();  
  14.       }  
  15.   }  

当然,这段代码是从Android官方文档上复制的,不过大家现在再来看这段代码,是不是理解的更加深刻了?

 

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

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

1. Handler的post()方法

2. View的post()方法

3. Activity的runOnUiThread()方法

我们先来看下Handler中的post()方法,代码如下所示:

[java] view plaincopy
  1. public final boolean post(Runnable r)  
  2. {  
  3.    return  sendMessageDelayed(getPostMessage(r), 0);  
  4. }  

原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息,我们来看下这个方法的源码:

[java] view plaincopy
  1. private final Message getPostMessage(Runnable r) {  
  2.     Message m = Message.obtain();  
  3.     m.callback = r;  
  4.     return m;  
  5. }  

在这个方法中将消息的callback字段的值指定为传入的Runnable对象。咦?这个callback字段看起来有些眼熟啊,喔!在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:

[java] view plaincopy
  1. private final void handleCallback(Message message) {  
  2.     message.callback.run();  
  3. }  

也太简单了!竟然就是直接调用了一开始传入的Runnable对象的run()方法。因此在子线程中通过Handler的post()方法进行UI操作就可以这么写:

[java] view plaincopy
  1. public class MainActivity extends Activity {  
  2.   
  3.     private Handler handler;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         handler = new Handler();  
  10.         new Thread(new Runnable() {  
  11.             @Override  
  12.             public void run() {  
  13.                 handler.post(new Runnable() {  
  14.                     @Override  
  15.                     public void run() {  
  16.                         // 在这里进行UI操作  
  17.                     }  
  18.                 });  
  19.             }  
  20.         }).start();  
  21.     }  
  22. }  

虽然写法上相差很多,但是原理是完全一样的,我们在Runnable对象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI。

 

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

[java] view plaincopy
  1. public boolean post(Runnable action) {  
  2.     Handler handler;  
  3.     if (mAttachInfo != null) {  
  4.         handler = mAttachInfo.mHandler;  
  5.     } else {  
  6.         ViewRoot.getRunQueue().post(action);  
  7.         return true;  
  8.     }  
  9.     return handler.post(action);  
  10. }  

原来就是调用了Handler中的post()方法,我相信已经没有什么必要再做解释了。

 

最后再来看一下Activity中的runOnUiThread()方法,代码如下所示:

[java] view plaincopy
  1. public final void runOnUiThread(Runnable action) {  
  2.     if (Thread.currentThread() != mUiThread) {  
  3.         mHandler.post(action);  
  4.     } else {  
  5.         action.run();  
  6.     }  
  7. }  

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。还有什么会比这更清晰明了的吗?

 

通过以上所有源码的分析,我们已经发现了,不管是使用哪种方法在子线程中更新UI,其实背后的原理都是相同的,必须都要借助异步消息处理的机制来实现,而我们又已经将这个机制的流程完全搞明白了,真是一件一本万利的事情啊。

分享到:
评论

相关推荐

    Android代码-Android 一些重要知识点解析整理

    Android 异步消息处理机制完全解析,带你从源码角度彻底理解 Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系 Android消息循环分析 Android Activity developer 官网 (强烈推荐 ...

    android异步消息机制 源码层面彻底解析(1)

    主要为大家详细介绍了android异步消息机制,源码层面彻底解析,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    android异步消息机制 从源码层面解析(2)

    主要为大家详细介绍了android异步消息机制,从源码层面解析,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    Android AsyncTask完全解析 带你从源码的角度彻底理解

    我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制。之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制。 不过为了更加方便我们在子线程...

    计算机毕业设计 - Android模仿易网新闻页面源码(异步加载),保证可靠运行,计算机毕业生可参考,免费资源下载

    数据解析与展示:将获取的新闻数据(通常为JSON格式)进行解析,并使用适配器(Adapter)将数据绑定到新闻列表组件上,实现新闻内容的动态展示。 加载状态与错误处理:在数据加载过程中显示加载状态,如进度条;当...

    Android AsyncTask使用以及源码解析

    在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看Android的消息机制——Handler的工作过程这篇文章)。当然为了简化我们的操作,在Android1.5以后为我们...

    开源中国android版源码

    开源中国源代码编译通过。各种流畅处理显示效果。lisytview异步加载显示,多线程处理机制。json ,xml解析等很好的范例参考。

    Android Studio实现简单的健身系统,满分安卓课程设计,新手必看!

    涉及知识点:MD5加密,文件存储,Sqlite数据库,VideoView视频播放器,Thread多线程编程,OkHttp网络通信框架,Toolbar工具栏,RecycleView列表,Handler异步消息机制,ViewPager轮播图,Json数据解析,Glide图片...

    Android代码-看知乎安卓App

    所有异步任务采用了回调机制; 对于Json的解析交由JavaBean自己解析,降低耦合度; 采用了三级图片缓存(文件、软引用,LRU),并解决图片错位等问题,仅需调用CacheTool的cacheImage即可; 对Adapter进行了抽象,...

    xmljava系统源码-AndroidNode:安卓节点

    ##Android中的异步任务机制 Android Activity (强烈推荐 dev guide) Android Intent 评论 29,85,96 序列化 * Android线程间通信 评论 19 Android UI 及布局 Android 网络访问 OOM 问题 ANR 问题 Android 事件分发...

    Android代码-XHttp2 一个功能强悍的网络请求库

    XHttp2 一个功能强悍的网络请求库,使用RxJava2 Retrofit2 OKHttp组合进行封装。...支持失败重试机制,可以指定重试次数、重试间隔时间。 支持根据key删除网络缓存和清空网络缓存。 提供默认的标准Api

    Android典型技术模块开发详解

    2.5.2 事件处理 2.6 本章小结 第二篇 Android开发关键组件 第3章 Activity(活动) 3.1 什么是任务 3.2 Activity的生命周期 3.3 基本用法 3.3.1 创建Activity 3.3.2 启动Activity 3.3.3 窗口Activity 3.3.4 Activity...

    积分管理系统java源码-MiniHttpServer:用于Java(android)的迷你http服务器

    Nio,异步机制,相比传统IO,有更高的性能。 体积小,代码少,支持Http部分协议,满足绝大部分需求。 支持POST表单数据和多文件上传。 支持PUT上传文件,自动保存到file目录。 支持发送文件到客户端。 支持发送...

    黑马程序员 安卓学院 万元哥项目经理 分享220个代码实例

    |--消息机制 mesage looper |--滑动之左右滑动的两种定义 |--电话簿读取联系人信息 |--监听 |--监听之CheckBox是否选中监听 |--监听之EditText内容变化监听 |--监听之GridView条目点击监听 |--监听之ListView条目...

    .net 稳定 高效 易用 可同步 TCP 通信框架

    7、简单的事件处理机制。使用更加简单。 8、支持同步处理,使程序的开发更架简单,不需要另行回调处理。 下载地址: 使用方式: 首选需要 引用 DataUtils.v1.1.dll。DataUtils 内包含客户端与服务器端 处理...

    java开源包1

    该框架封装了线程处理、异步读/写等方面。 Java多线程程序死锁检查 JCarder JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 ...

Global site tag (gtag.js) - Google Analytics