Android Handler使用介绍

        Android 中的 Handler 是用来和线程通信的重要工具。它主要用于在后台线程中执行任务,并将结果传递回主线程以更新用户界面。

一、基本概念

  • 线程间通信: Android 应用通常具有主线程(也称为 UI 线程)和后台线程。Handler 允许您从后台线程向主线程发送消息,从而在更新 UI 时避免出现线程安全问题。
  • 消息队列: Handler 使用消息队列来存储消息。每个消息都可以包含一个数据包,用于在线程之间传递信息。

        Handler 不仅仅用于将消息发送到主线程,您还可以在不同的线程之间进行通信。您可以在某个线程上创建 Handler,并将其传递给其他线程以便它们能够向该 Handler 发送消息。

1、使用案例

  • 在后台线程执行任务后更新 UI。
  • 在网络请求完成后通知 UI 更新。
  • 实现定时器功能,定期执行某个任务。

2、注意事项

  • Handler 与线程安全相关。确保您理解线程之间的通信机制以避免出现竞态条件或死锁。
  • 避免在 Handler 中进行耗时操作,以免阻塞主线程。

        总的来说,Handler 是 Android 开发中非常有用的工具,用于实现多线程通信和管理 UI 线程的消息处理。它为开发者提供了一种简单而有效的方法来处理异步任务和更新用户界面。 

二、使用方法

1、创建Handler

        您可以通过以下方式创建一个 Handler:

Handler handler = new Handler();

        这将创建一个与当前线程的 Looper 相关联的 Handler。

2、发送消息

        要向 Handler 发送消息,可以使用以下方法:

handler.sendMessage(Message msg);

        其中 Message 对象包含您要传递的信息。

3、处理消息

        要在 Handler 中处理消息,您需要重写 handleMessage(Message msg) 方法。例如:

Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 处理收到的消息}
};

4、延迟消息

        Handler 还允许您发送延迟消息,可以使用以下方法:

handler.sendMessageDelayed(Message msg, long delayMillis);

        这样可以在指定的延迟时间后将消息发送到消息队列中。

5、移除消息

        有时候您可能需要在某个时刻取消将要发送的消息。您可以使用以下方法:

handler.removeMessages(int what);

        这将移除具有特定标识符的所有消息。

三、子线程创建Handler

        我们平时使用 Handler 都是在主线程中 new Handler(),那么在子线程中也可以这样创建Handler吗?

private Handler threadHandler;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);new Thread(new Runnable() {@Overridepublic void run() {threadHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {return false;}});}}).start();
}

        了解 Handler 原理的都知道,Handler 的消息处理是通过 Looper.loop() 里的死循环,不断的从消息队列中取出消息并处理,在主线程中已经自动调用了 Looper.loop() 方法,所以我们可以直接使用 new Handler() 创建,而在子线程中需要我们手动创建。

private Handler threadHandler;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();threadHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {return false;}});Looper.loop();}}).start();
}

          上面的代码没有问题,但我们都是使用 handler.sendManager() 发送消息,在其他地方我们怎么创建该子线程对应的 handler 去发送消息呢?因为无法拿到子线程的 Looper,所以无法创建。

        下面我们创建一个自定义子线程,提供获取 Looper 方法:

public class MyHandlerThread extends Thread {Looper looper;MyHandlerThread(String name){super(name);}@Overridepublic void run() {super.run();Looper.prepare();looper = Looper.myLooper();Looper.loop();}Looper getLooper(){return looper;}@Overridepublic synchronized void start() {super.start();}
}

        使用自定义 Thread 创建 handler。

MyHandlerThread handlerThread = new MyHandlerThread("cx");
handlerThread.start();Handler handler = new Handler(handlerThread.getLooper());//子线程使用完成一定要退出,防止内存溢出
handler.getLooper().quit();

        上面方法中 handlerThread.getLooper() 可能存在空指针异常,以为 run() 为异步加载。所以必须要保证 run() 执行完,再去执行 getLooper() 方法,可以使用 wait() 和 notify() 实现。

public class MyHandlerThread extends Thread {Looper looper;MyHandlerThread(String name){super(name);}@Overridepublic void run() {super.run();Looper.prepare();synchronized (this) {looper = Looper.myLooper();notifyAll(); //唤醒wait()}Looper.loop();}Looper getLooper(){synchronized (this) {while (looper == null) {//用while,防止被其他notify唤醒,重新判断try {wait(); //释放锁,并等待} catch (InterruptedException e) {}}}return looper;}@Overridepublic synchronized void start() {super.start();}
}

wait():CPU 释放,线程继续做别的事情。

sleep():睡眠,线程停止等待。

        上面主要是为了理解 wait()、notify() 和 synchronized() 的使用,其实 MyHandlerThread 已经帮我们实现好了,直接使用 HandlerThread 即可。

四、Handler相关问答

1、用一句话概括Handler,并简述其原理

        Handler 是 Android 系统的根本,在 Android 应用被启动的时候,会分配一个单独的虚拟机,虚拟机会执行 ActivityThread 中的 main 方法,在 main 方法中对主线程 Looper 进行了初始化,也就是几乎所有代码都执行在 Handler 内部。Handler 也可以作为主线程和子线程通讯的桥梁。 Handler 通过 sendMessage 发送消息,将消息放入 MessageQueue 中,在 MessageQueue 中通过时间的维度来进行排序,Looper 通过调用 loop 方法不断的从 MessageQueue 中获取消息,执行 Handler 的 dispatchMessage,最后调用 handleMessage 方法。

2、为什么系统不建议在子线程访问UI(为什么不能在子线程更新UI)

        在某些情况下,在子线程中是可以更新 UI 的。但是在 ViewRootImpl 中对 UI 操作进行了 checkThread,但是我们在 onCreate 和 onResume 中可以使用子线程更新UI,由于我们在 ActivityThread 中的 performResumeActivity 方法中通过 addView 创建了 ViewRootImpl,这个行为是在 onResume 之后调用的,所以在 onCreate 和 onResume 可以进行更新UI。
        但是我们不能在子线程中更新 UI,因为 UI 更新本身是线程不安全的,如果添加了耗时操作之后,一旦 ViewRootImpl 被创建将会抛出异常。一旦在子线程中更新 UI,容易产生并发问题。

3、一个Thread可以有几个Looper,几个Handler

        一个线程只能有一个 Looper,可以有多个 Handler。

  • 一个Looper:因为Looper需要循环,循环后面的代码无法执行了,所以一个线程只有一个Looper
  • 多个Handler:为了相互独立,互不干扰,各自处理各自的消息,谁发生的Message谁处理,也可以通过removeMessages清空掉自己的Message。

        线程在初始化时候调用 Looper.prepare() 方法对将静态对象 ThreadLocal 作为 key(整个进程全部线程的 Looper 共用一个 sThreadLocal),创建 Looper 对象作为 value 存储在当前线程的 ThreadLocalMap 中(其实就只有一个 key 对应 value,key 为 sThreadLocal、value 为 looper 对象。这两只有一个,所以 ThreadLocalMap 只有一个键值对)。

        通过线程独有的 ThreadLocal 实现将 Looper 存储在 ThreadLocalMap 这样键值对的数据结构中。

4、Message是如何创建的

        首先考虑一个问题,屏幕刷新率 60Hz(即每秒刷新60次),每次刷新要用到 3 个 Message,也就是每秒钟至少要创建 180 个 Message。这样不断的创建回收,就会出现内存抖动的问题,从而导致 GC、屏幕卡顿等问题。

        为了解决上面的问题,采用 Message 了享元的设计模式,使用 obtain() 方法创建。在Handler 中创建两个线程池队列,一个是我们比较熟悉的 MessageQueue,另一个就是回收池 sPool(最大长度是 50 复用池)。MessageQueue 中 Message 回收时,我们将清空数据的 Message 放回到 sPool 队列中。创建 Manager,我们直接从 sPool 池中取出来就可以了。

        应用场景:地图、股票、RecyclerView复用等对数据的处理都使用了享元模式。

5、主线程中Looper的轮询死循环为何没有阻塞主线程

        Looper 轮询是死循环,但是当没有消息的时候他会 block(阻塞, 阻塞代码没有执行计时操作),ANR 是当我们处理点击事件的时候 5s 内没有响应,我们在处理点击事件的时候也是用的 Handler,所以一定会有消息执行,并且 ANR 也会发送 Handler 消息,所以不会阻塞主线程。Looper 是通过 Linux 系统的 epoll 实现的阻塞式等待消息执行(有消息执行无消息阻塞),而 ANR 是消息处理耗时太长导致无法执行剩下需要被执行的消息触发了 ANR。

6、ANR机制

         启动 Service 为例,APP进程 startService,system_service 进程通知 ActivityManager开始计时,同时 Service 进程开始启动,启动完成后会发送消息到 ActivityManager。

        如果时前台 Service,ActivityManager 20秒没有收到启动完成的消息就会产生ANR。

7、使用Hanlder的postDealy()后消息队列会发生什么变化

        Handler 发送消息到消息队列,消息队列是一个时间优先级队列,内部是一个单向链表。发动 postDelay 之后会将该消息进行时间排序存放到消息队列中。

8、点击页面上的按钮后更新TextView的内容,谈谈理解

        点击按钮的时候会发送消息到 Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个 target 为 null 的消息,这样在使用消息队列的 next 获取消息的时候,如果发现消息的 target 为 null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将 target 为 null 的消息移除。(同步屏障:拦截同步消息执行,优先执行异步消息。 View 更新时,draw、requestLayout、invalidate 等很多地方都调用异步消息的方法)。

        同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用 Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的setAsynchronous(true)时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

9、生产者-消费者设计模式

        在生产者-消费者模式中如果消费者消费能力大于生产者,或者生产者生产能力大于消费者,需要一个限制,在 java 里有一个 blockingQueue。当目前容器内没有东西的时候,消费者来消费的时候会被阻塞,当容器满了的时候也会被阻塞。

        这里 Handler.sendMessage 相当于一个生产者,MessageQueue 相当于容器,Looper 相当于消费者。Handler 不使用 java 的 blockingQueue 的原因是除了我们上层需要使用到 Handler,其实底层的消息都是需要传递给 Handler 处理,比如:驱动层需要发事件给APP、屏幕点击事件、底层刷新通知等,所以我们使用的是 Native 层提供的 MessageQueue 实现消息队列。

10、Handler是如何完成子线程和主线程通信的

        在主线程中创建 Handler,在子线程中发送消息,放入到 MessageQueue 中,通过 Looper.loop 取出消息进行执行 handleMessage,由于 looper 我们是在主线程初始化的,在初始化 looper 的时候会创建消息队列,所以消息是在主线程被执行的。

11、关于ThreadLocal

        ThreadLocal 实例进程内只有一个(静态实例),但其内部的 set 和 get 方法是获取的当前线程的 ThreadLocalMap 对象,ThreadLocalMap 是每个线程有一个单独的内存空间,不共享,ThreadLocal 在 set 的时候会将数据存入对应线程的 ThreadLocalMap中,key = ThreadLocal,value = 值。

12、Handler内存泄漏问题及解决方案

        内部类持有外部类的引用导致了内存泄漏,如果 Activity 退出的时候,MessageQueue中还有一个 Message 没有执行,这个 Message 持有了 Handler 的引用,而 Handler 持有了 Activity 的引用,导致 Activity 无法被回收,导致内存泄漏。使用 static 关键字修饰,在 onDestory 的时候将消息清除。

        简单理解,当 Handler 为非静态内部类时,其持有外部类 Actviity 对象,所以导致 static Looper -> mMainLooper -> MessageQueue -> Message -> Handler -> MainActivity,这个引用链中 Message 如果还在 MessageQueue 中等待执行,则会导致 Activity 一直无法被释放和回收。 

        导致的原因是因为 Looper 需要循环,所以一个线程只有一个 Looper,但一个线程中可有多个 Handler,MessageQueue 中消息 Message 执行时不知道要通知哪个 Handler 执行任务,所以在 Message 创建时中存入了 Handler 对象 target 用于回调执行的消息。如果 Handler 是 Activity 这种短生命周期对象的非静态内部类时,则会让创建出来的 Handler 对象持有该外部类 Activity 的引用,Message 还在队列中导致引用着 Handler,而非静态内部类 Handler 引用外部类 Activity 导致 Activity 无法被回收,最终导致内存泄漏。

        解决办法:

  • Handler 不能是 Activity 这种短生命周期的对象类的内部类;
  • 在 Activity 销毁时,将创建的 Handler 中的消息队列清空并结束所有任务。
  • 将 handler 设置成 static,static 变量是全局变量,不能够自动引用外部类变量,这时Handler 就不再持有 MainActivity,MainActivity 就可以正常释放。

13、Handler异步消息处理(HandlerThread)

        内部使用了 Handler+Thread,并且处理了getLooper 的并发问题。如果获取 Looper 的时候发现 Looper 还没创建,则 wait,等待 looper 创建了之后在 notify。

14、子线程中的Looper,无消息时如何处理

        子线程中创建了 Looper,当没有消息的时候子线程将会被 block,无法被回收,所以我们需要手动调用 quit 方法将消息删除并且唤醒 looper,然后 next 方法返回 null 退出 loop。

15、多个Handler往MessageQueue中添加数据(Handler可能处于不同线程),如何确保线程安全

        在添加数据和执行 next 的时候都加了 this 锁,这样可以保证添加的位置是正确的,获取的也会是最前面的。

16、关于IntentService

        HandlerThread + Service 实现,可以实现 Service 在子线程中执行耗时操作,并且执行完耗时操作时候会将自己 stop。

17、Glide如何维护生命周期

        一般想问的应该都是这里为什么会判断两次 null :

@NonNull
private RequestManagerFragment getRequestManagerFragment(@NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) {RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if (current == null) {current = pendingRequestManagerFragments.get(fm);if (current == null) {current = new RequestManagerFragment();current.setParentFragmentHint(parentHint);if (isParentVisible) {current.getGlideLifecycle().onStart();}pendingRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;
}

1)在多次调用 with 的时候,commitAllowingStateLoss 会被执行两次,所以我们需要使用一个 map 集合来判断,如果 map 中已经有了证明已经添加过了。
2)handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(),我们需要将 map 里面的记录删除。

18、handler主线程阻塞了怎么办

        Android 系统事件驱动系统,loop 循环处理事件,如果不循环程序就结束了。

19、Handler底层为什么用epoll,为什么不用select和poll

        Socket 非阻塞 IO 中 select 需要全部轮询不适合,poll 也是需要遍历和 copy,效率太低了。epoll 非阻塞式 IO,使用句柄获取 APP 对象,epoll 无遍历,无拷贝。还使用了红黑树(解决查询慢的问题)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/761869.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

操作系统面经-程序和进程的区别

1.程序、进程、线程简述 程序的基本概念 程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。 进程的基本概念 在多道程序环境下,允许多个程序并发执行,此时它们将失去封闭性&#xf…

Verilog刷题笔记41

题目:Create 8 D flip-flops with active high asynchronous reset. All DFFs should be triggered by the positive edge of clk. 解题: module top_module (input clk,input areset, // active high asynchronous resetinput [7:0] d,output [7:0]…

哪些事是你当了领导才明白的?

哪些事是你当了领导才明白的? 毕业5年,17年开始带团队,确实很多事不做到管理这一层,就真的意识不到。 带着【执行者】和【管理者】这2个视角,再结合我毕业至今这5年的所有职场经历,聊聊“职场潜规则”。 …

docker swarm 集群创建

1,目的: 通过docker swarm 工具将一台或者多台安装了docker的服务器组成一个完整的集群,该集群中的node节点可以通过Leader节点管理。在使用docker stack部署时,可以将容器自动分发到合适的节点上。 2,服务器准备&am…

python统计分析——t分布、卡方分布、F分布

参考资料:python统计分析【托马斯】 一些常见的连续型分布和正态分布分布关系紧密。 t分布:正态分布的总体中,样本均值的分布。通常用于小样本数且真实的均值/标准差不知道的情况。 卡方分布:用于描述正态分布数据的变异程度。 F分…

R语言Meta分析核心技术:科研论文写作与数据可视化技巧

R语言作为一种强大的统计分析和绘图语言,在科研领域发挥着日益重要的作用。其中,Meta分析作为一种整合多个独立研究结果的统计方法,在R语言中得到了广泛的应用。通过R语言进行Meta分析,研究者能够更为准确、全面地评估某一研究问题…

ShardingSphere+JPA+Druid实现分表操作

要在SpringBoot项目中实现分表操作,本文使用的是ShardingSphereJPADruid实现。过程中出现问题记录一下。 准备MySQL数据库表 这里准备的是一张主表test_cost,两张从表test_cost_0和test_cost_1,结构需要相同,主表只是声明了表结构…

用大语言模型控制交通信号灯,有效缓解拥堵!

城市交通拥堵是一个全球性的问题,在众多缓解交通拥堵的策略中,提高路口交通信号控制的效率至关重要。传统的基于规则的交通信号控制(TSC)方法,由于其静态的、基于规则的算法,无法完全适应城市交通不断变化的…

Tensorflow2.0笔记 - 链式法则例子

本笔记简单记录链式法则的原理,关于链式法则,本身和高等数学中的链式求导法则是一样的,深度学习中相关资料可以参考这里: 【深度学习之美22】BP算法详解之链式法则 - 知乎10.5 什么是计算图?我们知道, 神经…

Windows环境下编译ffmpeg 6.1源码--Virtual Studio + Msys2方式

环境准备 约定:源码全部放到sources下,目录结构说明 /d/java/ffmpeg #工程工目录 ├── build #存放编译文件的目录,子目录为具体模块的构建目录 │ ├── fdk-aac │ ├── ffmpeg │ └── x264 ├── instal…

提升水库大坝安全与效率:现代技术云平台的应用

在我国,水库大坝的数量居世界之首,它们在推动国民经济发展中扮演着不可或缺的角色。然而,要想让这些水利工程充分发挥其价值,不仅需要精准的调度与高效的管理,更重要的是要确保其安全无虞。一旦发生事故,后…

鸿蒙Harmony应用开发—ArkTS-全局UI方法(列表选择弹窗)

列表弹窗。 说明: 从API Version 8开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 本模块功能依赖UI的执行上下文,不可在UI上下文不明确的地方使用,参见UIContext说明。 从API version 10开始&#xff0…

数据结构——循环队列的实现

💞💞 前言 hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 💥个人主页&#x…

【回顾练习】静态路由配置综合实验报告

一、分析要求 为R6设备配置公有IP地址,并确保只能进行IP地址配置,无法进行其他配置。为R1-R5设备分配私有IP地址。为R1、R2、R4配置两个环回地址,为R5、R6配置一个环回地址。在R3上配置DHCP服务,以供两台PC自动获取IP地址。使用动…

数字电源浅析

电力电子技术是关于能量转换、调节、控制和管理等方面的学科,而数字电源则是电力电子技术的一种应用,是利用数字电路技术实现电源控制和管理的新型电源。 一、什么是数字电源 数字电源是一种数字控制的电源设备,可以通过数字控制芯片(DSP、MCU等)实现输出电压、电流、功…

向上生长

(1) 我记得2010年,在中国的苹果应用商店里,充斥的App还有很多:日历App、天气App、电池省电App、记事本App…。但这已经过去了2007-2008-2009三年,这些应用仍然很欢。 我有一个朋友算是中国最早一批开发iOS …

U盘插入电脑没有显示怎么办?

U盘可以备份/转移数据,还可以作为启动盘重装系统,但很多用户在使用u盘的时候,都遇到过这样的问题,就是u盘插入电脑没有反应,在电脑上找不到u盘设备。那么今天就跟大家等下U盘插入电脑没有显示怎么办。 原因/解决方法一…

html5cssjs代码 036 CSS默认值

html5&css&js代码 036 CSS默认值 一、代码二、解释 CSS默认值(也称为浏览器默认样式)是指当HTML元素没有应用任何外部CSS样式时,浏览器自动为这些元素赋予的一组基本样式。这些样式是由浏览器的默认样式表(User Agent sty…

关系型数据库mysql(3)索引

目录 一.索引的概念 二.索引的作用 三.创建索引的原则依据 四.索引的分类 五.索引的创建 5.1 普通索引 5.1.1 直接创建索引 5.1.2 修改表方式创建 5.1.3 创建表的时候指定索引 5.2 唯一索引 5.2.1 直接创建唯一索引 5.2.2 修改表方式创建 5.2.3 创建表的时候指…

机器学习——决策树(三)预剪枝

观前提示:这是本人机器学习决策树内容的第三篇博客,沿用了之前相关的代码,包括信息增益计算函数、结点类、预测函数和分类精度计算函数 完整代码指路 DrawPixel/decisionTree.ipynb at main ndsoi/DrawPixel (github.com) 前两篇博客详见…