Android | 开发UI时候要注意的(单线程模型 导致事件执行到最后都需要回归主线程)

在 Android 开发中,所有涉及到 UI 操作的代码必须在主线程(UI 线程)上执行的原因如下所述,以及程序员在开发过程中需要注意的一些事项:

1. 原因:

  • 安全性和稳定性: Android 系统是单线程模型的,即 UI 框架和相关的 View 层次结构只能由主线程访问和操作。如果在非主线程上直接操作 UI,可能会导致多线程并发问题,例如数据竞争和线程安全性问题,从而引发应用崩溃或不稳定的行为。

  • 框架设计限制: Android 框架设计中,所有的 UI 操作(如更新视图、处理触摸事件、启动动画等)必须在主线程上执行。这是为了保证系统的一致性和预测性,以及最大限度地避免因并发访问而引发的问题。

2. 好处:

  • 避免异常和崩溃: 在主线程外更新 UI 可能会导致 CalledFromWrongThreadException 异常。通过在主线程上执行 UI 操作,可以避免这类异常,提升应用的稳定性和可靠性。

  • 提升用户体验: 主线程上良好的响应性能确保了用户界面的流畅和及时响应用户交互,提升用户体验。

  • 简化代码和维护: 遵循主线程操作 UI 的规则可以减少开发中的并发问题,简化代码逻辑和维护成本。

3. 程序员需要注意的事项:

  • 使用异步处理: 当需要执行耗时操作时(如网络请求、数据库查询等),务必在后台线程(非主线程)执行,然后通过合适的方式(如 HandlerAsyncTask、RxJava 等)切换回主线程更新 UI。

  • UI 线程阻塞问题: 长时间运行的计算、I/O 操作或者同步网络请求等可能导致主线程阻塞,从而引起 ANR(Application Not Responding)错误。因此,应避免在主线程上执行耗时操作。

  • 合理使用异步任务和线程池: 使用适当的线程池和异步任务管理机制(如 ExecutorServiceThreadPoolExecutor)来执行耗时操作,确保不影响主线程的响应性。

  • 主线程与后台线程的通信: 使用合适的线程切换机制(如 HandlerLooper、RxJava 的 observeOn 等)来在主线程和后台线程之间进行通信和数据传递。

总之,遵循在主线程上执行 UI 操作的规则是 Android 开发的基本原则之一,它能够保证应用的稳定性、用户体验以及代码的可维护性。程序员需要在开发过程中时刻注意这一点,并结合合适的异步处理机制来处理耗时操作,以确保应用在不同条件下的稳定运行。


上一个  observeOn 切回主线的一段代码:

    /*** 为绑定按钮添加 请求数据*/private fun addListItem() {val listPopupWindowButton = view.findViewById<Button>(R.id.list_popup_window)listPopupWindowButton.setOnClickListener {getOkHttpData { selectItems ->if (selectItems != null) {val listStr = LinkedList<String>()listStr.add("111111")listStr.add("22222")val charSequenceList: List<CharSequence> = listStr.map { it as CharSequence }// 创建一个 ListPopupWindow 对象,使用当前 Fragment 的上下文,指定样式为系统默认的 listPopupWindowStyleval listPopupWindow = ListPopupWindow(requireContext(), null, R.attr.listPopupWindowStyle)// 在主线程上操作 UIObservable.just(charSequenceList).observeOn(AndroidSchedulers.mainThread()) // 切换到主线程进行后续操作.subscribe { charSequences -> // 订阅 Observable 发射的数据流val listPopupWindow = ListPopupWindow(requireContext(), null, R.attr.listPopupWindowStyle)// 创建 ListPopupWindow 对象,使用当前 Fragment 的上下文,指定样式为系统默认的 listPopupWindowStyleval adapter = ArrayAdapter<CharSequence>(requireContext(),popupItemLayout,  // 如果有自定义的布局,可以使用自定义的布局,否则使用系统提供的默认布局charSequences.toTypedArray()  // 将 List<CharSequence> 转换为 Array<CharSequence>,作为适配器的数据源)listPopupWindow.setAdapter(adapter) // 设置 ListPopupWindow 的适配器listPopupWindow.anchorView = listPopupWindowButton // 设置 ListPopupWindow 的锚点 View,即显示在哪个 View 下方listPopupWindow.setOnItemClickListener { parent, view, position, id ->Snackbar.make(requireActivity().findViewById(android.R.id.content),adapter.getItem(position).toString(), // 获取点击项的数据并转换为字符串显示Snackbar.LENGTH_LONG).show()listPopupWindow.dismiss() // 点击列表项后关闭 ListPopupWindow}// 显示 ListPopupWindow  注意!! 如果不切回主线程,这里无法正常显示!!!!!listPopupWindow.show() }} else {// 处理获取数据失败的情况,例如显示错误信息等showToast(requireContext(), "获取数据失败")}}}}private fun getOkHttpData(callback: (List<SelectItem>?) -> Unit) {val selectItems: MutableList<SelectItem> = mutableListOf() // 使用 mutableListOf 替代 LinkedListval url = "https://www.xxxxxxxxxxxxx.com" // 替换为你的目标 URLval client = OkHttpClient()val request = Request.Builder().url(url).build()client.newCall(request).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {// 处理请求失败的情况e.printStackTrace()val errorMessage = "请求失败: " + e.messageshowToast(requireContext(), errorMessage)callback(null) // 请求失败时回调传递 null}override fun onResponse(call: Call, response: Response) {try {if (!response.isSuccessful) {throw IOException("Unexpected code $response")}val responseBody = response.body?.string()// 使用 Gson 解析 JSON 字符串val gson = Gson()val itemType = object : TypeToken<List<SelectItem>>() {}.typeval items: List<SelectItem> = gson.fromJson(responseBody, itemType)Log.i("BindMachine", "请求成功!$responseBody")callback(items) // 请求成功时回调传递 selectItems} catch (e: IOException) {e.printStackTrace()val errorMessage = "请求失败: " + e.messageshowToast(requireContext(), errorMessage)callback(null) // 请求失败时回调传递 null}}})}

如果案例中的:listPopupWindow.show()  不用 observeOn 切回主线程,那么就不会正常显示,证明了【在 Android 开发中,所有涉及到 UI 操作的代码必须在主线程(UI 线程)上执行】的真实性。

综上所述,Android 的单线程模型要求开发者确保 UI 操作在主线程上执行,而其他耗时操作则应在后台线程或线程池中进行。事件处理最终需要将结果或需要更新的数据传递回主线程,以保证应用的稳定性和良好的用户体验。

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

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

相关文章

初识c++(引用,inline,nullprt)

一、引用 1、定义 引用不是新定义⼀个变量&#xff0c;而是给已存在变量取了⼀个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c; 它和它引用的变量共用同⼀块内存空间。 类型& 引用别名 引用对象; #include<iostream> using namespace std; in…

342. 4的幂

哈喽&#xff01;大家好&#xff0c;我是奇哥&#xff0c;一位专门给面试官添堵的职业面试员 文章持续更新&#xff0c;可以微信搜索【小奇JAVA面试】第一时间阅读&#xff0c;回复【资料】更有我为大家准备的福利哟&#xff01; 文章目录 一、题目二、答案三、总结 一、题目 …

python--del

在Python中&#xff0c;del是一个关键字&#xff0c;用于删除对象。当你想删除列表的某个元素或者整个变量时&#xff0c;可以使用del。 以下是使用del的一些示例&#xff1a; 删除列表中的特定索引处的元素&#xff1a; my_list [1, 2, 3, 4, 5] del my_list[2] # 删除索引为…

C++ 算法——二分查找

如果要你在一个升序序列中查找一个值的位置&#xff0c;你是否还会傻乎乎的用下面这个 O ( n ) \mathcal O(n) O(n) 的代码暴力查找&#xff0c;如果是&#xff0c;我告诉你&#xff0c;其实根本不用这么做。 int find(int a[],int n,int k) {for(int i0;i<n;i) if(a[i]k)…

在Spring Boot项目中集成监控与报警

在Spring Boot项目中集成监控与报警 大家好&#xff0c;我是微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 1. 引言 在当今的软件开发中&#xff0c;监控和报警系统是保证系统稳定性和可靠性的重要组成部分。Spring Boot…

华为机考真题 -- 篮球游戏

题目描述: 幼儿园里有一个放倒的圆桶,它是一个线性结构,只能在桶的右边将放入篮球,但是可以在桶的左边或者右边将取出篮球。每个篮球有单独的编号,老师可以一次性放入一个或者多个篮球,小朋友可以在桶左边或者右边取出篮球,当桶里只有一个篮球的情况下,只能从桶的左边…

视频监控技术在食品安全监管中的关键应用

视频监控技术在食品安全监管中的关键应用 1、视频监控技术在食品安全监管中的作用 在食品安全监管中&#xff0c;视频监控技术发挥着不可替代的作用。通过安装视频监控系统&#xff0c;可以实现对食品生产、运输、储存等各个环节的实时监控和录像存储。这不仅有助于监管部门及…

Linux的前世今生

Unix的起源和发展 1969年&#xff0c;AT&T贝尔实验室的Ken Thompson和Dennis Ritchie等人开发了Unix操作系统。Unix的设计理念强调小而简洁的工具&#xff0c;文本流和系统模块化&#xff0c;这些理念后来成为Linux开发的重要基础。1973年&#xff0c;Unix用C语言重新编写…

深度学习-数学基础(四)

深度学习数学基础 数学基础线性代数-标量和向量线性代数-向量运算向量加和向量内积向量夹角余弦值 线性代数-矩阵矩阵加法矩阵乘法矩阵点乘矩阵计算的其他内容 人工智能-矩阵的操作矩阵转置&#xff08;transpose&#xff09;矩阵与向量的转化 线性代数-张量&#xff08;tensor…

GEE代码实例教程详解:湖泊面积变化分析

GEE代码实例教程详解&#xff1a;湖泊面积变化分析 简介 在本篇博客中&#xff0c;我们将通过Google Earth Engine (GEE) 探索湖泊面积随时间的变化。通过分析MODIS数据集中的归一化差异水体指数&#xff08;NDWI&#xff09;&#xff0c;我们可以识别湖泊区域并监测其面积变…

达梦数据库kill会话

达梦数据库kill会话 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;可以使用 SP_CLOSE_SESSION 存储过程来终止会话。这个存储过程需要提供会话 ID (sid) 作为参数&#xff0c;用于指定哪个会话需要被终止。 下面是使用 SP_CLOSE_SESSION 存储过程的详细步骤…

第5章 Vite高级功能(二)

文章目录 6 缓存和持久化6.1 配置缓存目录 7 压缩与最小化7.1 启用压缩7.2 配置压缩选项 8 生产环境优化8.1 移除调试信息8.2 配置环境变量 9 静态资源优化9.1 压缩图像9.2 合并和压缩 CSS9.3 使用 HTTP/2 10 分析构建结果10.1 使用分析插件10.2 生成包分析报告 6 缓存和持久化…

PostgreSQL的使用

PostgreSQL的使用 1.首先&#xff0c;使用docker进行安装pgvector数据库&#xff0c;具体的安装步骤可以查看我之前发的博文。 2.docker exec -it pgvector /bin/bash 进入docker容器内部&#xff0c;操作数据库&#xff0c;上述命令是以交互式命令进入了容器的内部&#xf…

卷技术还是卷应用?李彦宏给出了明确答案

如何理解李彦宏说的“不要卷模型&#xff0c;要卷应用” 引言 7月4日&#xff0c;2024世界人工智能大会在上海世博中心召开。百度创始人兼CEO李彦宏在产业发展主论坛上呼吁&#xff1a;“大家不要卷模型&#xff0c;要卷应用&#xff01;”这句话引起了广泛讨论。李彦宏认为&a…

Python股票计算小程序(字符串格式化练习)

要求&#xff1a;打印的第一行使用f控制&#xff0c;第二行打印使用占位符&#xff0c;股价输出保留两位小数。 # 股价计算小程序 name"周氏集团" stock_price19.99 stock_code "9283" stock_price_daily_growth_factor1.2 growth_days7print(f"公司…

多线程场景下,使用QEventLoop使界面不卡顿的同时过滤用户输入

背景描述 有耗时的操作需要放到子线程中处理&#xff0c;但是此过程中不允许界面执行其他操作&#xff0c;不能使用阻塞界面的模态对话框实现。 可以通过QEventLoop在子线程执行的同时开启一个事件循环&#xff0c;使UI界面不卡顿&#xff0c;能够正常响应。但是这种不卡顿只是…

【Python进阶】继承进阶和私有权限

目录 一、继承进阶 1、方法重写 2、调用父类方法 3、多层继承 二、私有权限 1、私有属性 2、私有方法 面向对象基础&#xff1a;小白也能看懂的Python基础教程&#xff08;8&#xff09;-CSDN博客 一、继承进阶 1、方法重写 当父类的同名方法达不到子类的要求&#x…

阿里云上kubesphere安装配置 - 使用阿里云负载均衡

教程参考 https://www.kubesphere.io/zh/docs/v3.3/installing-on-linux/public-cloud/install-kubesphere-on-ali-ecs/ 环境配置 关闭防火墙关闭selinux关闭swap分区时间同步hosts解析内核参数设置检查DNS安装ipvs安装依赖组件安装、设置docker每台机器都需要操作 #安装工具…

crossJoin笛卡尔积

crossJoin笛卡尔积 在Spark中&#xff0c;crossJoin方法用于执行两个数据集之间的笛卡尔积操作。具体来说&#xff0c;如果有两个数据集&#xff08;DataFrame或Dataset&#xff09;&#xff0c;调用crossJoin方法将会生成一个新的数据集&#xff0c;其中包含两个原始数据集中所…

Monaco 中添加 CodeLens

CodeLens 会在指定代码行上添加一行可点击的文字&#xff0c;点击时可以触发定义的命令&#xff0c;效果如下&#xff1a; 通过调用 API 注册 LensProvider&#xff0c;点击时触发 Command&#xff0c;首先要注册命令&#xff0c;通过 editor.addCommand () 方法进行注册。三个…