主线程用Thread.sleep致视图变更失效?及后台线程修改视图疑问
问题背景
我想要在应用触发特定事件时添加红色闪烁效果:通过WindowManager创建并添加一个全屏红色视图,初始透明度设为0%。实现思路是将透明度设为100%,延迟25ms后再调回0%。
已知以下代码可正常工作:
view.alpha = 255f handler.postDelayed( Runnable { view.alpha = 0f }, 25)
但直接在主线程调用Thread.sleep实现延迟时,出现异常行为:
view.alpha = 255f Thread.sleep(25) view.alpha = 0f
上述代码中,第一条view.alpha = 255f语句似乎从未执行(或无视觉效果)。
而将这段代码放在后台线程中,却能按预期运行:
Thread( Runnable { view.alpha = 255f Thread.sleep(50) visualizer.alpha = 0f }) .start()
疑问
- 为何主线程休眠前的视图变更未执行(或不可见)?
- 为何可在后台线程修改视图透明度?视图变更不应在UI线程执行吗?
解答
1. 主线程休眠导致视图变更无视觉效果的原因
Android的UI渲染依赖主线程(UI线程),视图属性的变更不会立刻刷新到屏幕——系统会把这些变更暂存,等到下一次UI绘制周期(通常约16ms,对应60fps)统一处理并更新屏幕。
当你在主线程执行view.alpha = 255f后立刻调用Thread.sleep(25),主线程会被完全阻塞,无法执行后续的绘制逻辑。25ms后主线程恢复,马上又执行了view.alpha = 0f,这两次属性变更会被系统合并到同一个绘制周期里,最终屏幕只会显示最后一次的0%透明度,所以看不到红色闪烁效果。
而handler.postDelayed的方式,是把view.alpha = 0f的操作放到25ms后的主线程消息队列中,主线程有足够时间完成第一次透明度变更的绘制,之后再执行第二次变更,因此能正常显示闪烁效果。
2. 后台线程修改视图透明度为何能运行?
首先明确:Android官方强制要求所有视图操作必须在UI线程执行,后台线程直接修改视图属性属于违规操作,正常情况下会触发CalledFromWrongThreadException异常。
你这次能正常运行是特殊情况,原因可能是:View.setAlpha()内部会调用invalidate()触发重绘,但如果当前视图的mAttachInfo(视图与窗口的关联信息)为空,或者部分低版本系统、WindowManager添加的视图在线程检测上存在漏洞,没有立刻抛出异常。但这种写法完全不可靠,在不同设备或系统版本下极大概率崩溃,绝对不推荐使用。
正确做法始终是在UI线程执行视图属性变更,比如用handler.post()把后台线程的操作切换回主线程,或者使用runOnUiThread()方法。
内容的提问来源于stack exchange,提问作者user3504410

