Android中为何从非主线程更新View有时可行有时抛出异常?
为什么两段更新UI的线程代码结果不一样?
哈哈,这个问题真的戳中了很多Android新手的疑惑点!明明都是在子线程里更新UI,为啥一个能跑一个就炸?核心原因其实是Android对View的线程检查不是随时都在的,咱们慢慢说:
先把规则摆清楚
Android有个铁律:只有创建View的那个线程(也就是主线程/UI线程)才能修改View的状态。系统靠View.checkThread()这个方法来把关,但这个检查有个前提——只有当View已经被“绑定”到Window上之后,这个检查才会生效。
为啥第一段代码能正常跑?
看这段代码:
new Thread(){ @Override public void run() { btn.setText("Test"); } }.start();
你大概率是在Activity的onCreate或者onStart里启动的这个线程对吧?这时候线程一启动就立刻调用setText,但此时btn这个按钮还没完成和Window的绑定——Activity的视图加载、绑定到Window是个异步的流程,需要一点时间才能走完。
这时候调用setText,View内部还没触发那个线程检查,所以侥幸没抛出异常。但这绝对不是“正确”的写法,纯属时机巧合——要是线程启动慢个几百毫秒,View绑定完了,照样给你抛异常。
那第二段代码为啥炸了?
再看这段带sleep的代码:
new Thread(){ @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } btn.setText("Test"); } }.start();
sleep 2秒之后,Activity的视图早就加载完成了,btn也已经稳稳绑定到Window上了。这时候再调用setText,系统就会触发checkThread()检查,发现当前线程不是创建View的主线程,直接就抛出那个经典的异常:Only the original thread that created a view hierarchy can touch its views.
最后再啰嗦两句
- 第一段代码能跑只是运气好,本质还是违反了Android的UI操作规范,千万别这么写;
- 要在子线程更新UI,正确姿势永远是切换回主线程:比如用
runOnUiThread()、View.post()、Handler,或者现在更流行的Coroutine协程,这些才是安全可靠的方式。
内容的提问来源于stack exchange,提问作者im dev
相关产品推荐
相关产品推荐

