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

相关文章

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

SpringBoot开发中十大常见陷阱深度解析与避坑指南

《SpringBoot开发中十大常见陷阱深度解析与避坑指南》在SpringBoot的开发过程中,即使是经验丰富的开发者也难免会遇到各种棘手的问题,本文将针对SpringBoot开发中十大常见的“坑... 目录引言一、配置总出错?是不是同时用了.properties和.yml?二、换个位置配置就失效?搞清楚加

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

基于Python开发Windows屏幕控制工具

《基于Python开发Windows屏幕控制工具》在数字化办公时代,屏幕管理已成为提升工作效率和保护眼睛健康的重要环节,本文将分享一个基于Python和PySide6开发的Windows屏幕控制工具,... 目录概述功能亮点界面展示实现步骤详解1. 环境准备2. 亮度控制模块3. 息屏功能实现4. 息屏时间

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和