Android中在IO协程线程更新TextView未抛出CalledFromWrongThreadException的原因
兄弟,这个问题我之前也踩过坑!你是不是也纳闷明明Android文档反复强调不能在非UI线程更新View,结果自己写的代码跑起来啥事儿没有?别急,我来给你拆解背后的原因:
View尚未附着到窗口时,不会触发UI线程检查
你是在onCreate方法里启动的IO协程,这时候Activity的视图还处于初始化阶段——appTitle虽然已经通过findViewById拿到了实例,但它还没有被附着到窗口上,也就是还没关联上ViewRootImpl对象。
咱们常说的UI线程检查逻辑,其实是在ViewRootImpl的checkThread()方法里实现的。只有当View完成窗口附着(一般在Activity的onResume生命周期之后完成),拥有了对应的ViewRootImpl,修改View的操作才会触发线程检查。
你代码里循环的前几次文本更新,都是在View还没完成附着时执行的,这时候调用setText()只会修改TextView内部的文本状态,不会触发ViewRootImpl的检查逻辑,自然不会抛出CalledFromWrongThreadException。极端场景下的“侥幸”情况
理论上,当View完成窗口附着后,在非UI线程调用setText()肯定会触发线程检查并抛异常。但如果你确实全程没遇到崩溃,可能是以下极端情况:- 测试设备/模拟器的Android版本存在兼容性问题,意外绕过了线程检查;
- 循环执行时,TextView的重绘请求被系统的优化逻辑合并,没有触发即时的线程检查;
- 测试过程中APP意外进入后台,View被临时detach,此时修改文本又不会触发检查。
不过这里必须严肃提醒你:这种“没崩溃”的情况完全是偶然的,属于Android View系统的未定义行为!在不同设备、不同Android版本上,随时可能出现崩溃、UI错乱、数据不一致等问题。正确的写法应该是在更新View时,切换到UI线程执行,比如用withContext(Dispatchers.Main)包裹:
CoroutineScope(Dispatchers.IO).launch { Log.d(TAG, "onCreate Current thread name: ${Thread.currentThread().name}") val arr = arrayOf(appTitle.text.toString(),"Modified App Title") var index = 0; for (i in 1..100){ // 切换到UI线程更新View withContext(Dispatchers.Main) { appTitle.text = arr[index] } delay(500) index = if(index == 0) 1 else 0 } }
内容来源于stack exchange

