智能座舱进阶-应用框架层-Handler分析

news/2024/12/21 13:38:10/

首先明确, handler是为了解决单进程内的线程之间的通信问题的。我也需要理解Android系统中进程和线程的概念, APP启动后,会有三四个线程启动起来,其中,有一条mainUITread的线程,专门用来处理UI事件,以及显示组件的生命周期,多个Activity的线程都是同一个。此外还要知晓,单进程内的多线程是共享内存、信号量、运行指令。如下图:
在这里插入图片描述

除了私有栈区,不能相互共享, 代码的方法指令、数据都可以在进程内任意线程执行。这里大家先记住这点, 在后面的问题3的解释中会用到。
enqueueMessage中的消息的存入,是可以设置时间先后的;
我们本篇幅文章只讨论三个问题:
1. Handler 的消息发送过程;
2. Message 被消耗的过程;
3. 为何需要Handler这个类的原因;

基础知识

跟handler相关的类:
Handler:发送消息的快递员(属于某个快递公司的职员)
Message:承担消息的包裹(可以放置很多东西的箱子)
MessageQueue:消息中心(这里会按照消息带的时间长短,一个排列好消息的单链表)
Looper: 真正在消费消息的永动机(这个是在主线程的,上面三个都在另一个线程里)

问题1#:Handler 的消息产生和发送过程

我们在Activity中的应用场景:
创建Handler的对象,里面也重写了真正去使用msg的回调方法:
//创建 Handler对象,并关联主线程消息队列

mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);···略···}}
};

发送消息:

Message msg = new Message();
msg.setData(bundle);
msg.what = 2;
mHandler.sendMessage(msg)

消息的产生, 可以在任何地方通过Message 去创建,它是一个比较简单的实体类, 包含1、what,when,target等。 这里面说一下:
2、what:就是携带的数据,Int类型;
3、when:这个一般不怎么用, 但是是可以使用,具体是输入时间长短后,当在msg发送到MessageQueue的队列里, 会根据when和当前时间来推送,需要去消费它的具体时间, 跟队列里的消息进行对比,然后排列插进去;
4、target: 这个一般我们没有赋值,是因为他在Messag的实例化对象的时候【obtain()方法】或者是在message的后续传递中底层会帮我们赋值, 这个在最后looper里消费的时候, 会使用,用它找到这个消息是用哪个handler来消费,调用他的handleMessage()方法。 这里也就说明了, 一个looper是支持绑定多个hander对象的,同时也不会混乱。

我们现在看一下handler的实例化过程:

public Handler(Callback callback, boolean async) {//......mLooper = Looper.myLooper();  //返回与当前线程关联的Looper对象,在后面Looper会讲到if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;  //返回Looper对象的消息队列,在后面MessageQueue会讲到mCallback = callback;  //接口回调mAsynchronous = async; //是否异步}public interface Callback {public boolean handleMessage(Message msg); //这个函数大家都很熟悉了,暂不细说,总之都知道是用来回调消息的}

整个构造方法的过程中会确立以下几件事:

• 获取当前Handler实例所在线程的Looper对象:mLooper = Looper.myLooper(),这个looper获取的就是主线程那个loop
• 如果Looper不为空,则获取Looper的消息队列,赋值给Handler的成员变量mQueue:mQueue = mLooper.mQueue
• 可以设置Callback 来处理消息回调:mCallback = callback。

looper这里面为什么没有创建直接获取呢, 因为我们的Activity在每次创建的时候,
ActivityThread的1个静态的main();
而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象。为进程中的管理进行初始化。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “ActivityThreadMain”);

    // Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();  // 在这里创建新的looper

ActivityThread thread = new ActivityThread();
thread.attach(false);

    if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();// 获取Handler}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();// 开始调用loop()方法

好了,言归正传, handler创建完成调用

mHandler.sendMessage(msg)方法进行发送:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //获得当前的消息队列
if (queue == null) { //若是在创建Handler时没有指定Looper,就不会有对应的消息队列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) {msg.target = this;   //这个target就是前面我们说到过的if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}

消息通过这个方法 queue.enqueueMessage(msg, uptimeMillis)进入到了queue队列里了。

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {  //判断msg的所属Handlerthrow new IllegalArgumentException("Message must have a target.");}//......synchronized (this) {  //因为是队列,有先后之分,所以用了同步机制//......msg.when = when;Message p = mMessages;  //对列中排在最后的那个Message //......if (p == null || when == 0 || when < p.when) {    //若队列为空,或者等待时间为0,或者比前面那位的等待时间要短,就插队msg.next = p;  //此处的next就是前面我们在Message提到的,指向队列的下一个结点mMessages = msg;//......} else { //......Message prev;for (;;) {     //此处for循环是为了取出一个空的或者when比当前Message长的一个消息,然后进行插入prev = p;p = p.next;if (p == null || when < p.when) {break;}//......}msg.next = p;       // 置换插入prev.next = msg;  // 置换插入}//......}return true;}

这个就消息新建产生,然后发送到了queue的消息队列里,对待looper去取然后分发消费了。

问题2#:Message的消费过程

这个我们要看一下looper.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;Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {// 无限循环Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}......final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {// 处理消息msg.target.dispatchMessage(msg);......}

前面有代码片里说到 ActivityThread.mian-> Looper.prepareMainLooper()->mLooper.loop():
我们只要看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;Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {// 无限循环Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}......final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {// 处理消息msg.target.dispatchMessage(msg);......

这里面就看到,两件事情,根据target来判断是哪个handler,同时调用它的dispatchMessage来分发消息:

 public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

最终调用了 我们附带了的callback的handleMessage()方法。

这里面还有一个疑惑, looper是怎么能够一直处理消息的。 大家看loop()方法中, 有一个for(;;)死循环,这个其实就是永远在轮询队列中的消息,for循环是可以被阻塞的, 在这里就是这个函数Message msg = queue.next(); 就是如果队列没有消息了,它就会阻塞, 类似thread.sleep(1000)的方法。 当有新消息, queue.next()就会正常,继续执行消息。

问题3#: 为什么需要handler

需要handler,是因为在Android的设计里,不允许除了UI主线程外的其他线程对UI进行操作,最终都会走到View.requestLayout()里面判断当前线程是否是主线。
所以我们就需要想办法,需要子线程执行的某个阶段,有需求更新UI,那我们就必须通知在主线程里面去执行刷新UI的代码,这个在很多地方都有这种设计, 设计一个轮转片的循环,线程内的通信,通过消息来通知。 比如JS这种单线程设计语言, 就有Emit的设计。
1.创建了一个Looper对象保存在ThreadLocal中。这个Looper同时持有一个MessageQueue对象。
2.创建Handler获取到Looper对象和MessageQueue对象。在调用sendMessage方法的时候在不同的线程(子线程)中把消息插入MessageQueue队列。

3.在主线程中(UI线程),调用Looper的loop()方法无限循环查询MessageQueue队列是否有消息保存了。有消息就取出来调用dispatchMessage()方法处理。这个方法最终调用了我们自己重写了消息处理方法handleMessage(msg);这样就完成消息从子线程到主线程的无声切换。

最后补充:我们经常使用Handler没有创建Looper调用Looper.pepare()和Looper.loop()是因为在ActivityThread中已经创建了主线程的Looper对象,保存在了ThreadLocal中,我们在创建Hander的时候就会从ThreadLocal中取出来这个Looper。


http://www.ppmy.cn/news/1556937.html

相关文章

Android笔记【20】

问题1&#xff1a;val student:Student?intent.getParcelableExtra("data",Student::class.java) 这啥意思 以及用途 解答 这一行代码的意思是从一个 Intent 中获取一个可选的 Student 对象。具体解释如下&#xff1a; 解析代码 val student: Student? intent.ge…

单元测试使用记录

什么是单元测试 简单来说就是对一个类中的方法进行测试&#xff0c;对输出的结果检查判断是否符合预期结果 但是在多年的工作中&#xff0c;从来没有哪个项目中真正系统的用到了单元测试&#xff0c;因此对它还是很陌生的&#xff0c;也就造成更加不会在项目中区使用它。 如何…

thinkphp8自带分页bootstrap

tp8引用的是bootstrap3.4.1这个版本&#xff1b; 前端结构&#xff1a; <ul class"pagination"><li><a href"/index.php?page4"></a></li><li><a href"/index.php?page1">1</a></li>…

画图,matlab,

clear;close all;clc;tic;dirOutput dir(*.dat); % 罗列所有后缀-1.dat的文件列表&#xff0c;罗列BDDATA的数据 filenames string({dirOutput.name}); % 提取文件名%% 丢包统计 FILENAMES [""]; LOSS_YTJ [ ]; LOSS_RAD [ ]; LOSS_ETH [ ]…

docker-6.共享内存

在使用 pytorch dataloader 时,出现了当把num_workers 设置不为0即报错的问题,本文记录两种此类错误的解决方案。 Dataloader - num_workers Pytorch 中加载数据的模块Dataloader有个参数num_workers,该参数表示使用dataloader时加载数据的进程数量,可以理解为为网络搬运数…

基于51单片机的交通灯设计—夜间、紧急、复位、可调时间、四个数码管显示

基于51单片机的交通灯设计 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1、采用四方向数码管设计&#xff0c;更加符合真实的交通信号灯设计&#xff1b; 2、左侧按键从上到下依次为…

20241220在荣品开发板PRO-RK3566的buildroot下适配gc2093

20241220在荣品开发板PRO-RK3566的buildroot下适配gc2093 2024/12/20 16:00 余顺?PRO-RK3566开发板 挂 gc2093模块。刷 buildroot的预编译固件。 update-pro-rk3566-buildroot-hdmi-20231130-034633.img 1、现在发现 qcamera的 拍照Capture、Record录像模式都是640x480分辨率…

【C++读写.xlsx文件】OpenXLSX开源库在 Ubuntu 18.04 的编译、交叉编译与使用教程

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-12-17 …