[关闭]
@SR1s 2017-02-22T14:54:25.000000Z 字数 2647 阅读 1316

Handler编程范式

编程范式 Handler 消息队列 Android


Handler 机制概要

Android里的Handler机制本质上就是消息队列的实现。这套机制的关键成员有三:HandlerLooperMessageQueue。前两个大家都熟悉,后一个就有点面生了。

当然,有同学可能不服:不是说Handler机制吗,不提Message能行?

还别说,在整个Handler实现机制里,Message还真真是一个不那么重要的主。怪只怪系统的实现太精妙,以至于暴露出来的只有小弟Message,真正的大咖MessageQueue反倒是被忽略了。

这整套逻辑概括起来是这样的:
1. Handler用来封装了发送、处理Message处理逻辑
2. LooperMessage分发到对应的Handler上处理
3. 这两者中间靠MessageQueue关联起来

当我们通过Handler.sendMessage(msg)发送消息时,首先msg.target会赋值为这个handler。然后这个msg就会被加入MessageQueue这个队列里。在我们创建Looper的线程里,Looper会持续地从MessageQueue里取Message出来处理。当msg被处理的时候,就从msg.target里得到处理这个消息的Handler,最后这个msg就由Handler.handleMessage(msg)处理了。

发现了吧,整个过程中,最核心的就是这个MessageQueue,没了它整套机制都玩不动了。整个过程也很好理解,但系统偏偏把MessageQueue很好的隐藏了起来,以至于Handler的运作机制成了一个让人很难理解的点。

Handler 机制的特点

要说Message是不重要的,也不对。毕竟是系统暴露出来给我们的接口,对于应用开发者来说,它和Handler是很好的帮手。用好它能给我们解决一些复杂的问题。

我们来看看,Handler机制有什么特点。

最重要的一点:使用简单。但用好不容易。曾经接手过一个项目,项目里大量使用了Handler作为跨线程交互的机制。一种类型的消息,在各种不同的地方发送,偏偏在发的过程中,有个步骤出错了,整个业务流程就出了问题。当时对Handler机制不理解,排查了好长一段时间也没有结果,以至于后面看到Handler都先头大,自然也很少在项目里使用这个功能。

优雅使用Handler

用好Handler,首先要用对地方。要用对地方,首先要理解它机制和特点。机制和特点前面都已经说过了,它最大特点就是单线程和线程安全。这个特性很适合用来实现状态机。

  1. // StateMachineHandler.java
  2. private static final int MSG_CONNECT = 1;
  3. private static final int MSG_SEND = 2;
  4. private static final int MSG_DISCONNECT = 3;
  5. public class StateMachineHandler extends Handler {
  6. @Override
  7. public void handleMessage(Message msg) {
  8. int type = msg.what;
  9. switch(type) {
  10. case MSG_CONNECT:
  11. // 处理连接事件
  12. break;
  13. case MSG_SEND:
  14. // 处理发送事件
  15. break;
  16. case MSG_DISCONNECT:
  17. // 处理断开连接事件
  18. break;
  19. default:
  20. break;
  21. }
  22. }
  23. }

这样我们就不担心不同线程调用带来的多线程问题了,在实现里都不需要使用锁来同步,因为单线程本来就不存在同步问题。而由于是消息队列,所以我们可以在任意线程发送消息,发送的消息会按发送的先后顺序执行。

但这样就可以了吗?并不是。

这种方式其实对外暴露了整个服务的实现机制:基于Handler的消息队列。上层使用者需要跟我们一样,去了解Handler的机制的是如何运作,才能准确的使用我们提供的服务,然后使用这个Handler来发送对应的消息。这样子,我们的实现就和业务紧紧的耦合起来,倘若以后我们想替换内部实现,不使用Handler实现状态机,或者想完全的把实现换掉,那业务代码也不得不跟着改变。

所以,我们最好能够跟系统一样,把内部实现机制给隐藏起来。怎么做呢?其实很简单:收归、统一对外的接口。

以上面的例子来说,业务需要关心的其实只有三个操作:连接(connect)、发送(send)、断开连接(disconnect)。所以我们对外提供这三个方法,把发送消息的操作隐藏在它们的实现中。细心的同学肯定发现了,在上面的例子里,我们的消息类型设置为private,外部是无法引用到的,这就是为了实现这个目的。

看下代码:

  1. // StateMachineHandler.java
  2. private static final int MSG_CONNECT = 1;
  3. private static final int MSG_SEND = 2;
  4. private static final int MSG_DISCONNECT = 3;
  5. public class StateMachineHandler extends Handler {
  6. @Override
  7. public void handleMessage(Message msg) { ... }
  8. /**
  9. * 进行连接
  10. */
  11. public void connect() {
  12. sendMessage(obtainMessage(MSG_CONNECT));
  13. }
  14. /**
  15. * 发送指定内容
  16. */
  17. public void send(String content) {
  18. sendMessage(obtainMessage(MSG_SEND, content));
  19. }
  20. /**
  21. * 断开连接
  22. */
  23. public void disconnect() {
  24. sendMessage(obtainMessage(MSG_DISCONNECT));
  25. }
  26. }

这样,我们就完全把内部基于Handler的实现机制给隐藏起来,对外部来说,我们只暴露了外部需要了解的几个接口,使用起来也方便清晰了很多。后续内部实现优化,只要不改动约定的接口,把整个实现都换掉都可以。当然,如果我们一直都有意识的去保证我们接口的优雅的话,也可以很方便的把之前的龌蹉实现,换成基于Handler的机智,而不需要担心对业务的改动太大了。

That's all, But not ALL.

以上是我使用Handler的一点心得和偏好,如有别的使用方式,请一定要告诉我!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注