Android开发线程间的交互之Handler的学习

2024-08-28 16:58

本文主要是介绍Android开发线程间的交互之Handler的学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、handler的定义

UI线程用于UI的展示交互,子线程用于耗时操作,不能更新UI。handler 主要接受子线程发送的数据, 并用此数据配合主线程更新UI。

二、handler的简单用法。

new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);Message msg = new Message();msg.obj = "你好!";mHandler.sendMessage(msg);} catch (Exception e) {e.printStackTrace();}}
}).start();
Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg) {textView.setText((String)msg.obj);};
};

三、为什么Handler

主要是考虑到多线程并发的问题

四、Handler的机制

涉及到的类:

Looper   MessageQueue    Message  Handler   ThreadLocal
1、ThreadLocal

ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰

2、looper

不管是在主线程还是在其他线程使用handler,首先需要创建Looper。

主线程使用Looper.prepareMainLooper();在activity创建的时候系统就会自动创建
其他线程使用需要手动创建Looper.prepare();

<1> 内部包含一个消息队列MessageQueue,接收handler发送的消息
<2> loop方法一个死循环,不断的从MessageQueue去取消息,如果有消息就处理,没有就等待。

3、Handler实现机制(源码分析)
图解
handler发送消息
Created with Raphaël 2.2.0 UI线程 创建handler 获取UI中对应的looper UI中是否创建looper 使用ThreadLocal get方法得到looper 得到looper中的MessageQueue Handler将消息发送到UI线程的消息队列MessageQueue中 UI创建looper set进ThreadLocal yes no
handler处理消息
Created with Raphaël 2.2.0 MessageQueue存储消息 looper.loop获取消息 调用handler处理消息 等待 yes no

<1> 在prepare()方法中会创建Looper对象和MessageQueue对象,并将Looper对象存放到ThreadLocal

<2> 调用Looper.loop()方法。方法中首先会通过Looper me = myLooper();得到Looper对象(myLooper方法中使用ThreadLocal.get()得到Looper对象)。然后开启一个for死循环,获取MessageQueue中的消息Message msg = queue.next(); 如果消息为空则堵塞, 不为空就调用msg.target.dispatchMessage(msg);将消息回调给handler的dispatchMessage方法内部会回调handleMessage方法

<3> 创建当前线程的handler的子类并实现handleMessage拿到msg消息。创建handler时会获取到Looper对象和MessageQueue对象:mLooper = Looper.myLooper();mQueue = mLooper.mQueue;

<4> 创建Message对象,并使用handler.sendMessage发送msg。sendMessage方法会调用mQueue.enqueueMessage()方法并将handler赋值给msg.target。mQueue.enqueueMessage()会将msg消息存放到MessageQueue队列中(内部是使用单链表存储)

<5> Looper.loop()方法会调用MessageQueue.next()方法去取出msg并将msg删除

注意:在子线程中手动创建Looper的话,在不需要的时候应该将Looper退出,防止子线程一直处于等待状态。Looper提供了quit()和quitSafely()来退出。区别就是quit直接退出,而quitSafely只是设定了一个退出标志,等消息队列中的消息处理完毕后才安全退出。

五、handler的内存泄漏以及解决办法

handler的内存泄漏

经常用到的handler的使用方法:

    Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}};Message message = Message.obtain();message.what = 1;mHandler.sendMessageDelayed(message,10*60*1000);

在Java中,非静态内部类会隐性地持有外部类的引用,而静态内部类则不会。在上面的代码中,Message在消息队列中延时了10分钟,然后才处理该消息。而这个消息引用了Handler对象,Handler对象又隐性地持有了Activity的对象,当发生GC是以为 message – handler – acitivity 的引用链导致Activity无法被回收,所以发生了内存泄露的问题。

解决方法:
1、使用static

在创建handler的时候使用static,由于Handler不再持有外部类对象的引用,导致程序不允许在Handler中操作Activity中的对象了。所以需要在Handler中增加一个对Activity的弱引用(WeakReference)。

static class MyHandler extends Handler{WeakReference<Activity> weakReference;MyHandler(Activity activity){weakReference = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);final Activity activity = weakReference.get();if (activity != null) {((TestHandlerSendActivity)activity).mTextView.setText("我是handler—send更新的");}}
}
2、及时清除消息

正是因为被延时处理的 message 持有 Handler 的引用,Handler 持有对 Activity 的引用,形成了message – handler – activity 这样一条引用链,导致 Activity 的泄露。因此我们可以尝试在当前界面结束时将消息队列中未被处理的消息清除,从源头上解除了这条引用链,从而使 Activity 能被及时的回收。

六、总结:

Handler负责发送消息,Looper负责接收发送的消息,并将消息回传给handler自己。MessageQueue是存储消息的容器。

这篇关于Android开发线程间的交互之Handler的学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1115351

相关文章

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

Java终止正在运行的线程的三种方法

《Java终止正在运行的线程的三种方法》停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作,停止一个线程可以用Thread.stop()方法,但最好不要用它,本文给大家介绍了... 目录前言1. 停止不了的线程2. 判断线程是否停止状态3. 能停止的线程–异常法4. 在沉睡中停止5

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

基于Python开发批量提取Excel图片的小工具

《基于Python开发批量提取Excel图片的小工具》这篇文章主要为大家详细介绍了如何使用Python中的openpyxl库开发一个小工具,可以实现批量提取Excel图片,有需要的小伙伴可以参考一下... 目前有一个需求,就是批量读取当前目录下所有文件夹里的Excel文件,去获取出Excel文件中的图片,并

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx