*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

Android消息机制

这一定是一个被写烂了的专题吧。那本媛也来凑一个热闹吧。哈哈
这篇博客将会涉及以下内容:

  • 消息机制概述
  • UML图解消息机制相关类
  • 从在主线程更新UI的方法带你畅游消息机制的源码,更加方便自己理解
  • Handler
  • Looper
  • MessageQueue和Message
  • 消息机制的应用

消息机制概述

Android系统在设计的初期就已经考虑到了UI的更新问题,由于Android中的View是线程不安全的,然而程序中异步处理任务结束后更新UI元素也是必须的。这就造成了一个矛盾,最简单的解决方法肯定是给View加同步锁使其变成线程安全的。这样做不是不可以,只是会有两点坏处:

  1. 加锁会有导致效率底下
  2. 由于可以在多个地方更新UI,开发就必须很小心操作,开发起来就很麻烦,一不小心就出错了。

基于以上两个缺点,这种方式被抛弃。于是机智如我谷歌爸爸。。。设置一个线程专门处理UI控件的更新,如果其他线程也需要对UI进行更新,不好意思,您把您想做的告诉那个专门处理UI线程的家伙,让它帮你做。大家各有各的任务,井水不犯河水,各司其职,效率才会高,不仅仅对于软件如此,人也是如此,我只管写我的代码,有农民伯伯帮我种吃的、有电脑公司卖给我电脑、有建筑公司给我盖房子(当然了,房子我是万万买不起的… 哼!),这些事儿好像自己也能干,但是都自己干好像就回到了远古时代,人类的进步和发展和社会分工的明确也是离不开的,而且关系很大!
好像扯的有点远了。

那么您也看出来了,消息机制其实可以很简单的用一句话概括,就是:其他线程通过给特定线程发送消息,将某项专职的工作,交给这个特定的线程去做。比如说其他线程都把处理UI显示的工作通过发送消息交给UI线程去做。
实现的原理呢,我是这么理解的,现在要做的主要工作就是切换线程去操作,怎么切换呢?把两条线程看作是两条并行的公路,如果要从一条公路转到另一条公路上,要怎么做呢?是不是只要找到两条公路交叉或者共用某个资源的地方,如果说交叉路口,比如说加油站。当然了,线程是不存在交叉的地方的,那么可以考虑他们公用资源的地方,不同的进程享用不同的内存空间,但是同一个进程的不同线程享用的是同一片内存空间,那让其他线程把要处理的消息放到这个特定的内存空间上,处理消息的线程来这个内存空间上来取消息去处理不就可以了吗。事实正是如此,在Android的消息机制中,扮演这个特定内存空间的对象就是MessageQueue对象,发送和处理的消息就是Message对象。其他的Handler和Looper都是为了配合线程切换用的。
其实不仅仅是线程之间,不同进程之间进行消息传递(IPC机制),也是这个思路,找到公用的一个资源点,文件系统啊,共享内存啊等等,这个以后再讲吧。

这样理解起来是不是就是so easy了呢?

UML图解消息机制相关类

不知道上面的说法您是否可以对消息机制有了一个基本的认知呢?我曾经在想,怎么通过很简洁直观的方式去把消息机制讲明白(讲给自己,也讲给你)呢,后来我就在想,当初设计者的思路是什么样的呢?我想到了UML图,用类图来对消息机制中涉及到的几个类有一个概括的认识;通过时序图,可以很清晰的观察到整个消息机制的处理过程。
消息主要设计到下面几个类:

  • Handler:这是消息的发出的地方,也是消息处理的地方。
  • Looper:这是检测消息的地方。
  • MessageQueue:这是存放消息的地方,Handler把消息发到了这里,Looper从这里取出消息交给Handler进行处理
  • Message:呜呜呜…他们发的是我,处理的也是我。
  • Thread:我在这里专门指代的是,处理消息的线程。消息的发送是在别的线程。

话不多说,先来看一张图(UML忘的差不多了,刚补的,如果有错误,麻烦大家指出)

畅游源码

图在这里了,怎么看呢,整个消息机制相关的类密密麻麻支撑了一张网,咋个看嘛,,,不急不急,咱们先来思考一下咱们常用的更新UI是怎么一个操作步骤。

  1. 在主线程新建一个Handler对象,在构造方法中传入一个实现Handler.Callback接口的匿名类的对象,实现接口中的handleMessage方法
  2. 在非UI线程使用Handler的sendMessage或者post方法发送一个消息
  3. 然后handleMessage方法会在不久的将来马上执行,实现更新UI的操作。

那咱们就跟着这个思路来看一看这张图,先看Handler类,你会发现,Handler真的是个相当关键的核心(当然,其他部分也是不可或缺的),他几乎拥有所有其他相关对象的引用。

  • Handler拥有Looper的引用,通过得到Looper对象获得Looper中保存的MessageQueue对象
  • Handler拥有MessageQueue的引用,使Handler得以拥有发送消息(将Message放入MessageQueue)的能力
  • Handler拥有Handler.Callback的引用,使得Handler可以方便的进行消息的处理。

来思考一个问题:为什么Handler在其他线程发送消息之后,就跑到了主线程的handleMessage方法中去更新UI?

这个问题暂时先放着,等下回过头再来看。
我们现在先跟着第1,2,3步看看系统都帮我们做了什么操作呢?这就是在看源码,不要觉得很高深
下面是鲜活的代码,为了方便您查看,我帮您摘出来了。如果有兴趣,您也可以在AS里点开看看

//这是在主线程中Handler handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case 1:Toast.makeText(mContext, "你真漂亮", Toast.LENGTH_SHORT).show();break;case 2:Toast.makeText(mContext, "你也很帅呢", Toast.LENGTH_SHORT).show();break;default:break;}return false;}});//这是我们在主线程中创建Handler时会使用的构造方法public Handler(Callback callback) {this(callback, false);//调用了下面的这个构造方法↓}//先不要管第二个参数。跟紧主线,别跟丢了public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}//在这里获取到Looper对象,怎么获取的,稍后再看mLooper = Looper.myLooper();//如果获取的mLooper为空,直接抛出异常,说你不能在一个没有调用Looper.prepare()方法//的线程里创建Handler//如此看来,Looper.prepare()方法重要的嘞if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}//通过mLooper对象获取MessageQueue这个消息队列(单链表)mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}

到此为止,一个Handler就创建好了,(还有一个问题是Looper.prepare方法很重要,但是我们还没有去考虑他是干嘛的,不急不急,先顺着一条线看,不然看源代码的过程会把你搞死翘翘的)先面就该进行第二步,看看Handler.sendMessage干了啥.代码段又来喽

    //在一个新建的线程里使用创建好的Handler发送一个消息new Thread(new Runnable() {@Overridepublic void run() {//在这儿干点你想干的吧,一些耗时的计算或者网络操作啥的Message message = new Message();message.what = 1;handler.sendMessage(message);}}).start();//直接调用的是这个函数public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}//转而调用了这个函数public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}//转而又来到了这里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);}//最后的最后来到了这里private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {//target就是Message绑定的Handler,看看类图,上面有这个细节msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}//最后的最后,调用了MessageQueue的enqueueMessage方法return queue.enqueueMessage(msg, uptimeMillis);}//再看一下MessageQueue的enqueueMessage方法,//我把其他一些无关的细节给删掉了,只为了更加容易阅读boolean enqueueMessage(Message msg, long when) {synchronized (this) {Message p = mMessages;if (p == null) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;} else {//下面的错误就是遍历单链表,找到链表的尾部,这个没有难度的吧?Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {//找到了尾部,现在的结构是这样的。//A->B->C...->pre(p)->nullbreak;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;//把next插入链表的尾部}}return true;} 

到此为止,第二步就结束了,成功把一个消息插入到了MessageQueue的尾部。可是你很快就会发现,第三步好像从这条路探寻不下去了。接下来就等着别人来调用Handler中的方法了,可是是谁调用的,在哪儿调用的?我们现在好像毫无头绪了?怎么办?怎么办?我们刚才不是看到一个Looper.myLooper(),和Looper.prepare()方法,说是很重要但是一直没看吗,既然现在搜寻不下去了,是不是可以回头看看了?还有一点,Looper,看起来是在循环,循环什么玩意儿呢?我们去好好看看Looper类吧。
一共就三百来行代码,仔细看看,你会发现有一个核心方法:Looper.loop();
同样的,我把影响阅读的非主线代码剔除了,发现Looper.loop方法就长这样:

    /*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/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);} finally {}}}

清晰可见的是,Looper.loop()方法一直遍历MessqgeQueue,阻塞线程,直到获取到一个Message,然后调用了Message的一个成员变量target(其实就是Handler)的dispatchMessage(msg)方法,嗨,还真的又跟Handler扯上关系了,既然这里扯上关系了,而且还是一个分发消息的方法,可以大胆猜测就是让Handler去处理这个消息的。
那么我们来看看这个方法:

    /*** Handle system messages here.* 如果Message中callback对象不为空(这是调用handler.post(Runnable)方法发送的消息),* 就调用callback的run方法* 否则如果创建Handler的时候如果设置了Callback就调用创建时候的传入的* 实现Handler.Callback接口的类的对象的handleMessage方法,看这就是回调方法被调用的地方。* 再如果没有mCallback对象,就调用自身的handleMessage方法,为了Handler的子类复写了该方法的时候,方便调用,如,IntentService里的ServiceHandler就是继承自Handler的,并且重写了handleMessage方法。*/public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}private static void handleCallback(Message message) {message.callback.run();}//ServiceHandler继承自Service并且重写了handleMessage方法private final class ServiceHandler extends Handler {public ServiceHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {onHandleIntent((Intent)msg.obj);stopSelf(msg.arg1);}}

到了这里,三步走已经看完了,我想消息机制在我们心里已经又清晰了一层,但是不用急,咱们前边提的一个问题不是还没有解决吗,先把他解决掉吧,一起继续来看源码。
咱们现在已知的是这样的,在主线程创建的Handler发送了一个消息,发送消息的代码运行在其他线程,将代码加入消息队列也是在其他线程(加了线程同步锁)。然后handleMessage发生在主线程,那么调用该方法的dispatchMessage方法也是运行在主线程的,dispatchMessage是在Looper.loop方法中调用的,也就是说loop方法也运行在主线程,那么问题就明朗了,可是loop方法是谁调用的,在哪里调用的呢?当然是系统启动的时候创建主线程之后再主线程的run方法中调用了Looper.prepare和Looper.loop方法,但是这点我还没看,留着以后再看吧。
然后通过上面的分析,我们是不是可以自己来试着建立这样一个模型:

  1. 创建一个线程A
  2. 在这个线程的run方法中调用Looper.prepare和Looper.loop方法使该线程阻塞,等待消息发过来,然后处理
  3. 在该线程中创建一个Hanlder,用来处理looper发送来的待处理的消息
  4. 创建一些其他的线程a、b、c,做一点操作之后,通过Handler把消息传递出去,让线程A去处理。
public class MyThread extends Thread {private static final String TAG = "MyThread";Handler handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case 1:Log.i(TAG,"你真漂亮");break;case 2:Log.i(TAG,"你也很帅呢");break;default:break;}return false;}});public Handler getHandler() {return handler;}@Overridepublic void run() {super.run();Looper.prepare();Looper.loop();}@Overridepublic void destroy() {super.destroy();Looper.myLooper().quit();}
}//private void testMyThread() {MyThread thread = new MyThread();thread.start();final Handler handler = thread.getHandler();Message message = new Message();message.what = 1;handler.sendMessage(message);new Thread(new Runnable() {@Overridepublic void run() {try {sleep(400);Message message = new Message();message.what = 2;handler.sendMessage(message);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}

试着想一想,如果把线程A看成主线程,在回调方法更新UI,那这不就是Android系统中更新UI使用的套路吗?不错,事实本就如此,工具是工具,用它来更新UI是可以的,那你当然也可以用来做一些其他的工作啊。

在这里,通过以上的分析,不难得到下面的这个整个消息机制运行过程的时序图:

ok啦,源码阅读到此为止。其他的细节,有兴趣的可以再细细研究一下。
下面我们来对涉及到的类进行一下总结。
先来说一下我的个人理解,

Handler

handler在消息机制中,扮演的是消息的发送方和处理方(通过回调函数)。消息在一个线程通过Handler发送到MessageQueue中。Looper获取到Message之后,根据Message中保存的handler对象调用handler对象的dispatch方法,进行消息的处理。

Looper

Looper在这里扮演的是一个轮询消息队列的角色,以为不停地去问MessageQueue要消息,得到消息之后,根据Message中保存的handler对象调用handler对象的dispatch方法,进行消息的处理。

MessageQueue和Message

MessageQueue实质上是一个单链表的结构,里面以链表的形式保存着Handler发送过来的消息,当有新消息发来时放在链表的尾部,Looper要取消息的时候从链表的头部取出消息返回给Looper处理。Message对象中保存在要处理的信息,同时也持有消息发送方(Handler)的引用,Looper在得到该Message的时候,可以从Message中拿到消息的发送方,调用发送方的回调方法将消息传递过去交给Handler进行处理。

消息机制的应用

在Android中有很多消息机制的应用,如:

  • UI的更新
  • HandlerThread
  • IntentService

UI的更新

UI线程持有一个Looper对象,Looper对象的loop方法在UI线程中一直不停的进行死循环,直到有新的消息发来的时候,交给特定的组件进行处理,当然了这个处理也是在主线程运行的(如我们设置的点击事件也是等着被UI线程调用的),正是由于这个原因,我们不能在主线程处理耗时操作。如果我们一个View的点击事件里做了大量耗时的操作,由于这个操作也在主线程中运行,主线程必须等着这个操作操作结束才能去处理其他的消息,这个时候表现的就是系统卡顿甚至报ANR的错误。

HandlerThread

HandlerThread继承自Thread,内部保存一个Looper对象。
这是一个系统帮我们包装好的Thread,这个线程的run方法已经调用了Looper.prepare和Looper.loop(即已经绑定了一个Looper对象,并且可以开始轮询消息),创建该对象之后可以通过获得对象获取到一个Looper对象,将Looper对象传递给Handler,完成Handler和Looper以及MessageQueue的绑定。最后再其他的线程中调用Handler的sendMessage或者post(Runable)方法发送消息,handler中的callback.handleMessage方法会在HandlerThread中运行。即,将消息发送到了特定的线程(此处是HandlerThread)处理。

IntentService

IntentService继承自Service,运行时优先级更高,内部使用了HandlerThread作为处理消息的线程。内部有一个私有内部类ServiceHandler继承自Handler,并且会创建一个ServiceHandler对象。
使用startService()方法启动IntentService时,不会重新创建一个服务,会调用ServiceHandler对象发送包含该Intent的Message对象,该对象通过HandlerThread处理后交给ServiceHandler重写的handleMessage方法进行处理,处理的方式是调用IntentService的onHandleIntent(Intent)方法,所以使用的方式就是创建一个继承自IntentService类的子类,并重写onHandleIntent方法,在该方法中处理startService时传递的Intent。Intent中包含有要交给Service处理的信息。

Android消息机制详解相关推荐

  1. Android 消息机制详解(Android P)

    前言 Android 消息机制,一直都是 Android 应用框架层非常重要的一部分,想更加优雅的进行 Android 开发,我想了解消息机制是非常必要的一个过程,此前也分析过很多次 Handler ...

  2. Android学习笔记——Android 签名机制详解

    Android 签名机制详解 近期由于工作需要在学习 Android 的签名机制,因为没有现成资料,只能通过开发者文档和阅读博客的方式对 Android 签名机制进行大致了解.过程中查阅到的资料相对零 ...

  3. 【Android签名机制详解】二:Android V1、V2、V3、V4签名方案

    前言 书接上回[Android签名机制详解]一:密码学入门,在了解了消息摘要.非对称加密.数字签名.数字证书的基本概念后,我们趁热打铁.直奔主题,讲解签名在Android中的实际应用. 基础知识 An ...

  4. android异步工作,Android异步消息机制详解

    Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...

  5. Android Intent机制详解

    什么是Intent  Intent 是一个将要执行的动作的抽象描述,一般来说是作为参数来使用,由Intent来协助完成android各个组件之间的通讯.比如说调用startActivity()来启动一 ...

  6. Handler消息机制详解

    Handler机制是Android开发中最常见的机制,可以说贯穿整个Android,在探究Handler机制原理之前,我们先来捋一下用法 1.handler.post(Runnable) 2.hand ...

  7. Windows消息机制详解-5

    一. 什么是消息 在解释什么是消息之前,我们先讨论一下程序的执行机制问题.大体上说,程序按照执行机制可以分为两类: 第一类是过程驱动.比如我们最早接触编程时写的C程序,又或者单片机程序.这类程序往往预 ...

  8. windows消息机制详解-3

    1. 引言 Windows 在操作系统平台占有绝对统治地位,基于Windows 的编程和开发越来越广泛. Dos 是过程驱动的,而Windows 是事件驱动的[6],这种差别的存在使得很多Dos 程序 ...

  9. Android事件机制详解

    转自:http://www.codeceo.com/article/android-event.html 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和事件机制,前者用于跨 ...

最新文章

  1. Tensorflow实现神经网络及实现多层神经网络进行时装分类
  2. 文件服务器:共享文件夹的移动和权限设置备份2012-04-28
  3. ABAP开发者上云的时候到了 - 现在大家可以免费使用SAP云平台ABAP环境的试用版了
  4. Lines色线halcon算子,持续更新
  5. 图片添加对话气泡app_刘雨昕、谢可寅、虞书欣代言农夫山泉TOT气泡饮
  6. .net 笔记尝试(二)
  7. 【POJ - 1995】Raising Modulo Numbers(裸的快速幂)
  8. 服务器连接工具mat_将个人笔记本改造成Linux简易服务器
  9. hdu 1333水题
  10. firefox与IE浏览器在web开发上面的一些区别
  11. opera pms 数据库 MySQL_2020年最新版Opera PMS常用报表总结
  12. python自动换壁纸_Python自动更换壁纸爬虫与tkinter结合
  13. java离线地图web
  14. 关于spring boot的web.xml
  15. python视频补帧_我花了三天写了手机补帧神器
  16. iPhone 14分辨率,屏幕尺寸,PPI 详细数据对比 iPhone 14 Plus、iPhone 14 Pro、iPhone 14 Pro Max
  17. layui时间日期控件laydate设置默认值,并且结束时间大于开始时间
  18. Unity 知识点 - 3D游戏 - 视角跟随和键盘移动
  19. HDU-1253-胜利大逃亡
  20. 10、VUE组件基本使用

热门文章

  1. 浮标水质监测站现场水质自动监测仪
  2. 7-6 图着色问题 (25 分)
  3. 一个简单的英文分词程序
  4. 帝国CMS数据库数据表详细说明
  5. html在侧边栏,js+css实现全屏侧边栏
  6. openharmony学习
  7. CODEVS 1391 伊吹萃香 多层图最短路
  8. windows2008计划任务无法运行解决方案
  9. PyQt5 menu菜单栏设置
  10. 接雨水(多种方法解决)