# Java_Implement_Android_Handler **Repository Path**: cqupt/java_implement_android_handler ## Basic Information - **Project Name**: Java_Implement_Android_Handler - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-10-21 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README https://blog.csdn.net/u011057800/article/details/109248788 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201024104425277.png#pic_center) # 前言 在上一篇[Android on Linux(在Linux主机上运行Android可执行程序)](https://blog.csdn.net/u011057800/article/details/109010099)文章中,我们完成了直接在Linux主机上运行Android的可执行程序。其可以用来做一些自动化测试的工作,目前项目中服务端的代码是Android C/C++代码,编译成一个可执行程序,而客户端的代码是一个Java写的Android APK。可以将核心代码移植成一个纯JAVA项目,直接在Linux主机上使用JAVA VM来执行,从而达到自动化测试的目的。 移植过程中发现,其主要需要移植的就是Android Handler消息机制。本文主要重点是使用Java的`Object.notify()`和`Object.wait()`来替代Android中的`nativeWake()`和`nativePollOnce()`。本文提到的源码在此下载:[https://gitee.com/cqupt/java_implement_android_handler](https://gitee.com/cqupt/java_implement_android_handler)。 # Handler基本原理 ## 概述 目前解读Android Handler的实现的文章非常多,下面先列出相关文章,读者自行阅读。 [Android Handler消息机制原理最全解读(持续补充中)](https://blog.csdn.net/wsq_tomato/article/details/80301851) [Android全面解析之由浅及深Handler消息机制](https://blog.csdn.net/weixin_43766753/article/details/108968666) [Android Handler机制](https://blog.csdn.net/u012545728/article/details/80548339) [ThreadLocal的介绍](https://www.jianshu.com/p/3c5d7f09dfbd) [深入理解MessageQueue](https://www.jianshu.com/p/8c829dc15950) [Android消息机制1-Handler(Java层)](https://blog.csdn.net/dodod2012/article/details/82899574) [Android framework学习(2)——Handler Native层](https://blog.csdn.net/dodod2012/article/details/82899574) 简单概括:Handler机制是Android中基于单线消息队列模式的一套线程消息机制。也就是提供了一套API,可以方便的让子线程相互通讯。 总结归纳如下: >1、在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证。Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,但是只能有一个Looper和一个MessageQueue。 2、Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。 3、Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,然后通过handler将消息分发传回handler所在的线程。 来看一个例子: ```java import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; public class MessageMain { private static final String TAG = "MessageMain"; public static class HandlerThread extends Thread { private Looper mLooper; @Override public void run() { super.run(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Looper.loop(); } public Looper getLooper() { if (!isAlive()) { return null; } synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; } } public static void main(String[] args) { Looper.prepareMainThread(); HandlerThread handlerThread = new HandlerThread(); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.d("TAG", "testSendEmptyMessageDelayed: " + msg.what); System.exit(0); } }; Message msg = handler.obtainMessage(); msg.what = 100; Log.d("TAG", "testSendEmptyMessageDelayed: "); handler.sendEmptyMessageDelayed(msg.what, 400); Looper.loop(); } } ``` 1、开启HandlerThread线程; 2、`HandlerThread`线程的`run()`方法中,首先通过`Looper.prepare()`准备一个对象,然后通过 `Looper.myLooper()`获取一个`mLooper`对象,再`Looper.loop()`开启循环,处理`Message`消息; 3、`new Handler(handlerThread.getLooper()) `获取上一步中线程的`mLooper`对象,传递给`Handler`构造方法,并重载`handleMessage()`; 4、在主线程中通过`handler.obtainMessage()`获取一个`Message`对象,然后通过`sendEmptyMessageDelayed()`发送一个延时消息到`HandlerThread`线程。 可以总结成如下流程图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201023210929359.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwNTc4MDA=,size_16,color_FFFFFF,t_70#pic_center) 图中ThreadA即HandlerThread线程,ThreadB即主线程。图片来源:[Android Handler消息机制原理最全解读(持续补充中)](https://blog.csdn.net/wsq_tomato/article/details/80301851) ## ThreadLocal 前面提到每个线程中都需要通过`Looper.prepare()`构建一个的Looper对象,然后通过静态方法`Looper.myLooper()`获取此对象。我们必须保证每个线程都有一个独立的Looper对象,而不是多个线程共享同一个Looper对象。**那么此处是静态方法它是如何做到在不同线程返回不同对象的了呢**?此处使用的就是`ThreadLocal`类。 >ThreadLocal是Java中一个用于线程内部存储数据的工具类,通过它可以在指定的线程中存储数据,存储后只有在指定线程中获取到存储的数据,其他线程则无法获取到数据。 更多关于它的讲解请查看:[ThreadLocal的介绍](https://www.jianshu.com/p/3c5d7f09dfbd)。 ## Looper >Looper可以说是Handler机制中的一个非常重要的核心。Looper相当于线程消息机制的引擎,驱动整个消息机制运行。Looper负责从队列中取出消息,然后交给对应的Handler去处理。如果队列中没有消息,则MessageQueue的next方法会阻塞线程,等待新的消息的到来。每个线程有且只能有一个“引擎”,也就是Looper,如果没有Looper,那么消息机制就运行不起来,而如果有多个Looper,则会违背单线操作的概念,造成并发操作。 具体实现过程参考前面的文章,此处我们直接拿Android的代码来用即可。 ## Handler >Handler是作为整个消息机制的消息发起者与处理者,消息在不同的线程通过Handler发送到目标线程的MessageQueue中,然后目标线程的Looper再调用Handler的dispatchMessage方法来处理消息。 调用Handler不同参数方法发送Message最终都会调用到该方法,注意此处我们重写时在enqueu时对`msg.when`都赋值,后续会详细说明。 ```java public boolean sendMessageAtTime(Message msg, long time) { msg.when = time; return mQueue.enqueueMessage(msg); } ``` 其它函数接口几乎一致,可以精简后直接在Java中使用。 ## Message >Message的作用就是承载消息,他的内部有很多的属性用于给用户赋值。同时Message本身也是一个链表结构,无论是在MessageQueue还是在Message内部的回收机制,都是使用这个结构来形成链表。同时官方建议不要直接初始化Message,而是通过Message.obtain()方法来获取一个Message循环利用。一般来说我们不需要去调用recycle进行回收,在Looper中会自动把Message进行回收,后面会讲到。 此类可以直接精简一下后在Java中使用。 ## MessageQueue >MessageQueue中enqueueMessage的目的有两个: 1.插入消息到消息队列 2.通过`nativeWake(mPtr)`,唤醒Looper中等待的线程(如果是及时消息并且线程是阻塞状态) 同时我们知道了MessageQueue的底层数据结构是单向链表,MessageQueue中的成员变量mMessages指向的就是该链表的头部元素。 >MessageQueue中的next()方法主要通过`nativePollOnce(ptr, nextPollTimeoutMillis)`来阻塞等消息。 1.当首次进入或所有消息队列已经处理完成,由于此刻队列中没有消息(mMessages为null),这时nextPollTimeoutMillis = -1 ,然后会处理一些不紧急的任务(IdleHandler),之后线程会一直阻塞,直到被主动唤醒(插入消息后根据消息类型决定是否需要唤醒)。 2.读取列表中的消息,如果发现消息屏障,则跳过后面的同步消息。 3.如果拿到的消息还没有到时间,则重新赋值nextPollTimeoutMillis = 延时的时间,线程会阻塞,直到时间到后自动唤醒 4.如果消息是及时消息或延时消息的时间到了,则会返回此消息给looper处理。 详细讲解请查看:[深入理解MessageQueue](https://www.jianshu.com/p/8c829dc15950) 此类在Android中主要使用了`nativeWake`和`nativePollOnce`等方法,这就是我们需要想办法替换成Java的方法。 # 已有案例 ## 使用ArrayDeque来实现 在[用Java实现一个类似AndroidHandler的消息循环](https://www.jianshu.com/p/18767f7b50a3)文章中,实现了线程间的通讯。 他使用了ArrayDeque来装载Message对象,在`enqueueMessage()`方法中 ```java public void enqueueMessage(Message message) { synchronized (mQueue) { mQueue.notify(); mQueue.add(message); } } ``` 在`next()`方法中 ```java public Message next() { while (true) { synchronized (mQueue) { try { if (!mQueue.isEmpty()) { return mQueue.poll(); } mQueue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 他主要使用Java里Object的两个final native方法wait和notifyAll,完成了Handler的基本通讯机制,但是**未实现延时发送消息功能**,只能做到立即处理。 ## 使用DelayQueue来实现 在[使用java实现android的handler消息机制](https://blog.csdn.net/xgq330409675/article/details/84999621)文章中,他使用了`DelayQueue`类,来实现延时操作。 >DelayQueue是一个支持延时获取元素的无界阻塞队列,队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。 关于DelayQueue更多信息,请查看:[DelayQueue详解](https://www.cnblogs.com/myseries/p/10944211.html)。 此方案解决了不能发送延时消息的问题,满足了需求,但是实现方式依赖DelayQueue,显得不够简单,有没有更简单的方式呢? # 开始重写 ## 说明 在[Android 中 MessageQueue 的 nativePollOnce](https://www.cnblogs.com/jiy-for-you/p/11707356.html)文章中提到: >. nativePollOnce 和 nativeWake 的核心魔术发生在 native 代码中. native MessageQueue 利用名为 epoll 的 Linux 系统调用, 该系统调用可以监视文件描述符中的 IO 事件. nativePollOnce 在某个文件描述符上调用 epoll_wait, 而 nativeWake 写入一个 IO 操作到描述符, epoll_wait 等待. 然后, 内核从等待状态中取出 epoll 等待线程, 并且该线程继续处理新消息. 如果您熟悉 Java 的 Object.wait()和 Object.notify()方法,可以想象一下 nativePollOnce 大致等同于 Object.wait(), nativeWake 等同于 Object.notify(). >但它们的实现完全不同: nativePollOnce 使用 epoll, 而 Object.wait 使用 futex Linux 调用. 值得注意的是, nativePollOnce 和 Object.wait 都不会浪费 CPU 周期, 因为当线程进入任一方法时, 出于线程调度的目的, 该线程将被禁用(引用Object类的javadoc). 但是, 某些事件探查器可能会错误地将等待 epoll 等待(甚至是 Object.wait)的线程识别为正在运行并消耗 CPU 时间, 这是不正确的. 如果这些方法实际上浪费了 CPU 周期, 则所有空闲的应用程序都将使用 100% 的 CPU, 从而加热并降低设备速度. 从上述来看,我们也可以用 Object.wait()和Object.notify()替代nativeWake和nativePollOnce,下面开始吧。 ## 核心方法 `enqueueMessage()`方法 ```java public boolean enqueueMessage(Message msg) { synchronized (lock) { Message p = mMessages; if (p == null || msg.when == 0 || msg.when < p.when) { // 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 || msg.when < p.when) { break; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } nextPollTimeoutMillis = 0; lock.notify(); } return true; } ``` `next()`方法 ```java public Message next() { for (; ; ) { synchronized (lock) { try { if (nextPollTimeoutMillis < 0) { // 死等 lock.wait(); } else if (nextPollTimeoutMillis > 0) { // 超时等nextPollTimeoutMillis lock.wait(nextPollTimeoutMillis); } // else nextPollTimeoutMillis = 0; 链表中还有Message未处理,不等 final long now = System.currentTimeMillis(); Message msg = mMessages; if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mMessages = msg.next; msg.next = null; if (mMessages != null) { nextPollTimeoutMillis = (int) Math.min(mMessages.when - now, Integer.MAX_VALUE); nextPollTimeoutMillis = Math.max(nextPollTimeoutMillis, 0); } else { nextPollTimeoutMillis = -1; } return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; if (DEBUG) Log.d(TAG, "No more messages."); } } catch (InterruptedException e) { e.printStackTrace(); return null; } } } } ``` 总结如下: 1、Message采用链表的形式,根据时间插入对应的位置,插入取出的方式保持和Android源码一致。 2、在`enqueueMessage()` 中不管是及时消息还是延时消息,每次将`nextPollTimeoutMillis`置0,同时`notify`。 3、在`next()`中判断`nextPollTimeoutMillis`的值,`nextPollTimeoutMillis`小于0时,表示下条消息还没到来,一直等;`nextPollTimeoutMillis`大于0时,超时等待`nextPollTimeoutMillis`毫秒,处理延时消息;`nextPollTimeoutMillis`等于0时,链表中还有Message未处理,直接去取出消息。 ## 食用方法 下载源代码:[https://gitee.com/cqupt/java_implement_android_handler/tree/master](https://gitee.com/cqupt/java_implement_android_handler/tree/master) 此项目是一个Android Studio工程,可以使用Android Studio打开运行。 根目录有一个`build_and_run.sh`文件,可以直接运行MessageMain.java的测试用例。 ```bash #!/bin/bash # 自动测试 # ./build_run_test_fingerprintd.sh -d dir=$(cd $(dirname $0) && pwd) cd $dir/app/src/main/java # # clean find . -name "*.class" | xargs rm -f # build javac MessageMain.java # run java MessageMain if [ $? -ne 0 ]; then REVAL=-1 else REVAL=0 fi cd - >/dev/null 2>&1 function my_return() { return $1 } echo 测试结果:$REVAL my_return $REVAL ``` `app/src/main/java/TestHandler.java`是Android的单元测试用例,对其进行了简单的修改,方便测试。 ```java protected void setUp() throws Exception { super.setUp(); // TODO:没有主线程的loop,这里为了测试通过,需要loop() new Thread(new Runnable() { @Override public void run() { Looper.loop(true); } }).start(); } ``` 此致,我们使用了简单易懂的方式用纯Java代码,实现了Android的Handler机制,在学习使用中可以查看此源码理解的Android Handler大致实现的原理。 此时还有一个疑问,既然可以比较简单的使用Java的`Object.notify()`和`Object.wait()`来替代Android中的`nativeWake()`和`nativePollOnce()`,那么为什么Android会使用C++代码来实现呢? 我猜可能是为了复用C++代码中已经实现好了的代码,小伙伴们有什么想法呢,欢迎评论区留言讨论(/奸笑/奸笑)。更多信息查看下一篇:[在C++代码中使用Android Handler消息机制(Android or Linux)](https://blog.csdn.net/u011057800/article/details/109254866)。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201024104510117.png?#pic_center)