为什么需要写一个 Handler 机制?
写一个能用的 Handler 机制是我们的目的,但却不是我们想要得到的东西,最重要的是在完成这个目的地途中,我们对 Handler 设计的思考和理解!就好比旅行的最美好的事物是路途上的所见所闻所思所想。完成之后我们再去研究 Android 系统提供的 Handler 机制,会更加的事半功陪!
自己写一个 Handler 机制难不难?
之前在和同事讨论一个定时任务需求的时候,我们各自总结了一下实现定时任务的几种方式,就说到了使用 Handler 来做定时任务处理,蓦然地想到一个问题,Handler 中的定时任务是如何做到定时的呢?当时脑海中就飘过 Message.next() 函数获取 Message 消息时调用的 MessageQueue.nativePollOnce() 函数,模糊的记得里面好像是传入了一个时间参数,并不是百分百的肯定。
对基本概念模棱两可不是一个好习惯。在日常开发工作中对一个重要概念模棱两可,容易使得 bug 频出,花费大量的时间去解决。既然如此那么我们何不集中花费一些时间,彻底搞懂哪些模棱两可的概念,以避免后续不断的为此问题来花费时间呢。
下面我将按照如下的步骤来阐述我所理解的 Handler 消息处理机制,文章将分为两个部分,第一部分为思考设计一个自制 Handler 处理,第二部分是基于源码分析 Handler 机制和思考面试遇到的 Hanler 问题与冷门 Handler 问题。
- 首先明确 Handler 机制的本质是一个有序的消息处理器,没啥黑科技,利用伪代码表达 Handler 的源码构成和设计思路。
- 将源码精简压缩为伪代码,分析一次 Message 处理的过程。
- 利用伪代码详细讲解组成部分(Handler / Looper / MesaageQueue / Message)是如何完成各自的工作以及协同作业地。
- 总结列举 Handler 机制重要的概念和面试问题。
同时,我还是推荐想要了解 Handler 机制的同学去阅读 Handler 源码,Handler 机制相关源码还是比较简单的阅读难度不大,整体的学习节奏以阅读源码为主,网上的文章(当然包括这篇)为辅,自己独立思考来进行学习。
以下的用到的源码版本为 SDK 29!!!!
Handler 机制的本质是一个有序的消息处理器
明白本质,Handler 没啥神秘的东西
首先我们要明确 Handler 消息处理机制的本质就绪一个有序的消息处理器,其目的是为了实现 UI 的更新只能由单一的线程来执行即单线程更新 UI 模式,避免多线程更新 UI 引发问题。
清楚了这一点之后,我们就来想象一下一个有序的消息处理器应该有哪些部分组成呢?如果让你来设计一个单一的有序的线程处理机制你会怎么设计呢?
- 一个负责发送消息的。
- 一个存储消息的容器,消息发出了总的有个容器装,不然那发送的消息多了没来得及处理咋办?
- 负责不断的从容器中取消息的。
- 负责处理消息的。
- 消息本身的载体,用于规定消息的格式,不能什么类型的消息都能发送与处理。
最后呢应该是形成如下的一个模式:消息单向流动,一次处理一条消息,容器内的消息有序排列。
伪代码自制 Handler 机制和设计思路
基于上面的设计思路呢,我们利用伪代码并结合 Android Handler 实际机制来实现一个单一的有序的消息处理机制!
-
消息载体
这个我们使用一个
Message对象作为消息载体来承载消息,那么其类的伪代码如下:public class Message { public String content;// 消息的内容 public int id;// 消息的识别 id,便于处理分别嘛。 } -
消息容器,直接使用队列结构存储即可,单一有序全都有了。
public class MessageQueue { private Message mMessage; // synchronized 简单粗暴处理线程安全 public void addMessage(Message msg){ if (mMessage != null){ mMessage.next = msg; } else { mMessage = msg; } } // 获取队头消息 public Message next(){ if (mMessage == null) return null; Message current = mMessage; mMessage = current.next; return current; } } -
获取消息并处理,还要不断的获取怎么处理?简单,一个死循环就能搞定了么。另外,这里我们需要处理如何让消息都在目标线程中处理,实现单线程处理消息?
public class Looper { private MessageQueue messageQueue; public void loop(){ for(;;){ Message msg = messageQueue.next(); } } } -
获取到的消息如何处理呢?以及一个最重要的目标,如何保证所有的消息处理都在目标线程中执行呢?
设计思路
首先是第一个目标,如何处理?这简单直接利用一个
Handler类,里面包含处理Message的函数即可。那么第二个目标又如何实现呢? 既然
Handler作为消息的处理类,那么 Handler 类的实例化对象的消息处理肯定的位于目标线程中,其次Lopper是需要直接依赖MessageQueue的,直接将 MessageQueue 的初始化交给 Looper 就好,最后呢就只需要思考 Looper 的设计了。 首先
Looper中有一个死循环操作,那么单个线程中只能有一个 Looper (有多个也没啥用),然后想想 Hnadler 和 Looper 之间的关系,Looper 获取消息而 Handler 处理消息,两者能不能处于不同的线程呢?,除此之外也没啥好设计的了,退出机制?要想的话加一个就行,没消息就退出,或者延时退出都行。处理发送消息
发送消息的目的就是向 MessageQueue 中添加一条 msg,这里我们需要考虑的问题就是如何处理好多线程之间的交互问题,我们在这里使用 Java 线程的 wait / notify 机制实现(早期 android 系统中也是利用这个机制实现),Looper 在死循环获取消息时进行加锁,获取到的消息为空时就使用
wait()进行等待,当有消息添加时notify()唤醒 Looper 线程处理消息。 至此,Handler 和 Looper 的重新改造的模样如下:
// Handler public class Handler { /** * 处理 Message 的方法,需要继承者实现。 **/ public void handlerMessage(Message msg){ // 根据 msg 的 id 来分别处理不同的 msg } } // 添加功能之后的 Looper public class Looper { private MessageQueue messageQueue; public Handler mHandler;// 处理消息的 private Object lock = new Object();// 线程锁,避免死循环消耗资源,控制发送消息线程和 looper 线程安全 public Looper(){ messageQueue = new MessageQueue(); } /** * 给 Looper 添加一个 Handler **/ public void setHandler(Handler handler){ this.mHandler = handler; } public void addMessage(Message msg){ synchronized (lock){ messageQueue.addMessage(msg); // 消息加入,唤醒 loop 运行线程 lock.notify(); } } public void loop(){ for(;;){ synchronized (lock){ Message msg = messageQueue.next(); if(mHandler != null && msg != null){ mHandler.handlerMessage(msg); } else { // 等待 try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } -
使用
public static void main(String[] args){ // 创建 Looper Looper looper = new Looper(); // 创建 Handler 实例,处理消息 Handler handler = new Handler(){ @Override public void handlerMessage(Message msg) { System.out.println("ID:" + msg.id + " Content: " + msg.content); } }; // 给 Looper 设置 Handler looper.setHandler(handler); // 启动子线程,每隔一段时间发送一条消息 Thread thread = new Thread(new Runnable() { int id = 0; @Override public void run() { for (;;){ // 每次先睡 3 秒,控制频率和线程安全 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } Message msg = new Message(); msg.id = id; msg.content = "内容编码" + id; // 发送消息 looper.addMessage(msg); id++; } } }); thread.start(); // 启动循环查询处理消息 looper.loop(); }结果完美
小结
至此我们已经完成了一个近似于 Android 系统中 Handler 机制的消息处理机制,在下篇文章中我将从源码分析 Android Handler 机制以及思考面试中遇到的 Handler 相关的问题与一些冷门问题(唉,为啥面试不问本质净问些偏僻问题呢?)。然后我们在完善一下开头的哪张分析图片。
最后,学习Android系统源码相关问题还是应该基于源码,站在巨人的肩膀上独立思考🤔