在 Android 开发中,所有涉及到 UI 操作的代码必须在主线程(UI 线程)上执行的原因如下所述,以及程序员在开发过程中需要注意的一些事项:
1. 原因:
-
安全性和稳定性: Android 系统是单线程模型的,即 UI 框架和相关的 View 层次结构只能由主线程访问和操作。如果在非主线程上直接操作 UI,可能会导致多线程并发问题,例如数据竞争和线程安全性问题,从而引发应用崩溃或不稳定的行为。
-
框架设计限制: Android 框架设计中,所有的 UI 操作(如更新视图、处理触摸事件、启动动画等)必须在主线程上执行。这是为了保证系统的一致性和预测性,以及最大限度地避免因并发访问而引发的问题。
2. 好处:
-
避免异常和崩溃: 在主线程外更新 UI 可能会导致
CalledFromWrongThreadException
异常。通过在主线程上执行 UI 操作,可以避免这类异常,提升应用的稳定性和可靠性。 -
提升用户体验: 主线程上良好的响应性能确保了用户界面的流畅和及时响应用户交互,提升用户体验。
-
简化代码和维护: 遵循主线程操作 UI 的规则可以减少开发中的并发问题,简化代码逻辑和维护成本。
3. 程序员需要注意的事项:
-
使用异步处理: 当需要执行耗时操作时(如网络请求、数据库查询等),务必在后台线程(非主线程)执行,然后通过合适的方式(如
Handler
、AsyncTask
、RxJava 等)切换回主线程更新 UI。 -
UI 线程阻塞问题: 长时间运行的计算、I/O 操作或者同步网络请求等可能导致主线程阻塞,从而引起 ANR(Application Not Responding)错误。因此,应避免在主线程上执行耗时操作。
-
合理使用异步任务和线程池: 使用适当的线程池和异步任务管理机制(如
ExecutorService
、ThreadPoolExecutor
)来执行耗时操作,确保不影响主线程的响应性。 -
主线程与后台线程的通信: 使用合适的线程切换机制(如
Handler
、Looper
、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 操作在主线程上执行,而其他耗时操作则应在后台线程或线程池中进行。事件处理最终需要将结果或需要更新的数据传递回主线程,以保证应用的稳定性和良好的用户体验。