Android 消息机制总结 [ 十 ]

Android 消息机制深入源码分析 [ 一 ]
Android 消息机制之 ThreadLocal 深入源码分析 [ 二 ]
Android 消息机制之 Looper 深入源码分析 [ 三 ]
Android 消息机制之 Message 与消息对象池的深入源码分析 [ 四 ]
Android 消息机制之 MessageQueue 深入源码分析 [ 五 ]
Android 消息机制之初识Handler [ 六 ]
Android 消息机制之 Handler 发送消息的深入源码分析 [ 七 ]
Android 消息机制之 MessageQueue.next() 消息取出的深入源码分析 [ 八 ]
Android 消息机制之消息的其他处理深入源码分析 [ 九 ]
Android 消息机制总结 [ 十 ]

思考: 如果让我们来设计一个操作系统, 我们会来怎么设计 ?

一般操作系统都会有一个消息系统, 里面有个死循环, 不断的轮询处理其他各种输入设备输入的消息. 比如键盘的输入, 鼠标的移动等. 这些输入信息最终都会进入操作系统, 然后由操作系统的内部轮训机制挨个处理这些信息.

  1. 设计一个类, 里面有一个死循环去做循环操作.
  2. 再用一个类来抽象代表各种输入的信息.
    • 这个信息/消息应该还有一个唯一标识符, 用来区分不同的信息/消息
    • 信息/消息中有个对象来保存对应的键值对, 方便往信息/消息中存放数据.
    • 信息/消息还需要有字段来表明要执行的时间.
  3. 上面说的这些信息/消息又会组合成一个集合, 常用集合有很多, ArrayList, LinkedList, Map. 因为第一点说了使用一个死循环去处理, 那么这个集合最好是线性和排序较好的. 因为输入有先后, 一般都是按照输入的时间先后来构成, 既然这样那就先排除掉 Map, 那么就剩下了 ArrayList, LinkedList. 要知道一个操作系统的事件是非常多的, 也就是说对应的信息/消息很多, 所以这个集合要面临大量的插入操作, 而在插入效率这块, LinkedList 具有明显的优势. 所以这个集合应该是一个链表, 但是链表又分为多种, 因为是线性排序的, 所以只剩下双向链表与单向链表. 又考虑到手机性能的问题, 那就之剩下单向链表了, 因为单向链表在插入和删除上的复杂度明显低于双向链表.
  4. 最后还应该有两个类, 一个负责生产信息/消息, 一个负责消费这些信息/消息. 因为涉及到消费端, 所以在第二点中, 还应该增加一个字段负责指向消费端.

经过这几点的分析, 是不是发现其实和 Handler 机制差不多.

  1. Looper 负责轮询
  2. Message 代表消息, 内部单向链表.
    • what 作为标识符
    • when 代表时间.
    • data 数据存放键值对
    • target 指向消费端
  3. MessageQueue 存放消息集合.
  4. Handler 负责生产和处理消息.

现在将对 Android 消息机制中的这几个组成部分逐个做出总结.

1. Handler

Handler 是线程间传递消息的即时接口, 分为生产线程和消费线程.
生产线程: 在消息队列中创建, 插入或者移除消息

send *
post *
remove *

消费线程: 处理消息.

handleMessage

每个 Handler 都有一个与之关联的 LooperMessageQueue, 有两种创建 Handler 的方式 (这里说的两种不是两个构造函数, 而是构造函数的分类. )

  1. 通过默认的构造方法, 使用当前线程中关联的 Looper.
  2. 显示的指定使用 Looper.

如果没有指定 LooperHandler 是无法工作的, 因为它无法将消息放到消息队列中, 同样的, 也无法获取要处理的消息.

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());
      }
  }

  mLooper = Looper.myLooper();
  if (mLooper == null) {
      throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}

如果使用的是上面的构造函数, 它会检查当前线程有没有可用的 Looper 对象, 如果没有, 就会抛出运行时异常. 如果正常的话, Handler 就会持有 Looper 中的消息队列对象的引用.

并且, 同一个线程中会有多个 Handler, 但是他们共享同一个消息队列, 因为他们共享的是同一个 Looper 对象.
 


2. Message

Message 是容纳任意数据的容器, 生产线程发送消息给 Handler, Handler 将消息加入到 MessageQueue 中等待执行. Message 提供了 3 种额外的信息, 以供 HandlerMessageQueue 处理时使用.

  1. what: 一种标识符, Handler 使用它来区分不同的消息, 从而采用不同的处理方法.
  2. when: 告诉消息队列何时处理.
  3. targe: 表示是哪一个 Handler 应该处理这个消息.

 
Message 一般是通过以下几种方式来创建的.

public final Message obtainMessage()
public final Message obtainMessage(int what)
public final Message obtainMessage(int what, Object obj)
public final Message obtainMessage(int what, int arg1, int arg2)
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)

Message 通过从消息对象池中获取得到, 方法中提供的参数会放到消息体对应的字段中. Handler 同样可以设置消息的目标为其自身, 这允许我们进行链式调用. 例如

mHandler.obtainMessage(MSG_SHOW_IMAGE, mBitmap).sendToTarget();

消息对象池是一个消息对象的单向链表集合, 它最大长度是 50, 在 Handler 处理完一条消息后, 这条消息就会返回到消息对象池中, 并且重置其所有字段.

当使用 Handlerpost 方法来执行一个 Runnable 的时候, Handler 就会隐式的为我们创建了一个新的消息, 并且设置 Callback 参数来存储这个 Runnable.

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

 

3. MessageQueue

MessageQueue 是一个消息体对象的无界的单向链表集合, 它按照时序将消息插入队列, 最小时间戳的消息将会被优先处理.

MessageQueue 通过 SystemClock.uptimeMillis() 获取系统时间, 维护一个阻塞阈值, 当一个消息体的时间戳低于这个值的时候, 消息就会分发给 Handler 进行处理.

从 MessageQueue 中调用 next 方法循环获取消息的时候, 如果消息为 Barrier 障栅的时候, 该队列中的同步消息全部都会被拦截掉, 而放行所有的异步消息.

从 MessageQueue 中调用 next 方法循环获取消息的时候, 在第一次循环, 并且没有消息或者头部消息的执行时间未到的情况下, 会执行 IdleHandler. 直到下次调用 MessageQueue.next() 方法. (MessageQueue.next() 方法在 Looper.loop() 死循环中被调用.)

MessageQueue 中的 mPendingIdleHandlers 数组, 初始化的时候, 最小长度为 4.

没有消息或者消息未到执行时间, MessageQueue 将会调用 nativePollOnce 方法进行阻塞, 有新的消息入队会根据情况唤醒. 内部的消息执行时间到了, 也会自动唤醒.
 


4. Looper

Looper 在 loop() 方法的死循环中调用 MessageQueue.next() 方法获取消息, 然后分发给 Handler 处理. 一旦消息超过阻塞阈, 那么 Looper 就会在下一轮循环中读取到它. Looper 在没有消息分发的时候会进行阻塞状态(其实是 MessageQueue 进行阻塞.), 当有消息时, 会继续轮询.

每个线程只能关联一个 Looper, 给线程附加另外的 Looper 会抛出运行时异常. 通过使用 Looper 的 ThreadLocal 对象可以保证每个线程只关联一个 Looper 对象.

调用 Looper.quit() 方法会立即终止 Looper 的死循环. 并移除消息队列中所有的消息.(延迟消息与非延迟消息).

调用 Looper.quitSafely() 方法将消息队列中所有延迟消息移除, 非延迟消息则派发出去让 Handler 处理.

只有消息队列正在关闭退出的情况下, 在 Looper.loop() 方法死循环中调用 MessageQueue.next() 才会返回 null.

 

至此 Handler 消息机制已经分析完毕. 另外还将会有一篇番外的 HandlerThread.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351