Android-Daily-Interview icon indicating copy to clipboard operation
Android-Daily-Interview copied to clipboard

2019-07-05:子线程能否更新UI?为什么?

Open Moosphan opened this issue 4 years ago • 22 comments

Moosphan avatar Jul 05 '19 01:07 Moosphan

可以更新UI但是不推荐,因为很容易造成carsh。因为Android会checkThread,可以尝试一下在子线程更新setText(),文字是会改变的,但紧接着就会报异常。

SuDreamer avatar Jul 05 '19 01:07 SuDreamer

很早我们就被灌输一个既定事实:子线程不能够更新UI。然而,UI真的无法在子线程更新吗?为什么?这里的题意可能会被误解,所以备注一下。

Moosphan avatar Jul 05 '19 01:07 Moosphan

https://blog.csdn.net/qingchunweiliang/article/details/84727465

DaveBoy avatar Jul 05 '19 01:07 DaveBoy

不可以,因为会出现异常情况;在子线程中更新完后会出现异常现象如下: at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924) 需要在子线程中发送一个message到主线程handlemessage中进行主线程去更新ui控件

gabyallen avatar Jul 05 '19 03:07 gabyallen

子线程是不能直接更新UI的

注意这句话,是不能直接更新,不是不能更新(极端情况下可更新)

绘制过程要保持同步(否则页面不流畅),而我们的主线程负责绘制ui,极端情况就是,在Activity的onResume(含)之前的生命周期中子线程都可以进行更新ui,也就是 onCreate,onStart和onResume,此时主线程的绘制还没开始。

yangfanggang avatar Jul 05 '19 09:07 yangfanggang

不能直接更新,更新方式有三种 1、 new Handler(mContext.getMainLooper()).post(new Runnable() { @Override public void run() { mTextView.setText("update text not in UI Thread"); } }); 2、 `((Activity) context).runOnUiThread(new Runnable() {

@Override
public void run() {
    // 在这里执行你要想的操作 比如直接在这里更新ui或者调用回调在 在回调中更新ui
}

});` 3、 handler发送message给UI线程更新

BGround avatar Jul 09 '19 08:07 BGround

不能直接更新Ui,直接更新会崩溃,但你更改的内容还是会变化的,因为你子线程更新了,但此时主线程不知道,主线程用来绘制ui,所以会崩溃。下面方法可以用来在子线程更新UI

  1. new Handler(getMainLooper).post(new Runnable(){}
  2. runOnUiThread(new Runable){}
  3. 通过handler发送message来更新Ui 其实runOnUiThread内部就是调用new Handler(getMainLooper).post,而new Handler(getMainLooper).post内部调用的是sendMessageDelayed,所以更新Ui的原理就是通过handler发送message

18361237136 avatar Jul 12 '19 03:07 18361237136

这句话应该改成: 不能更新非本线程创建的View

yizems avatar Aug 02 '19 05:08 yizems

这句话应该改成: 不能更新非本线程创建的View

源码里面的注释是只能创建view的线程去更新该view,所以理论上是可以子线程创建,然后子线程更新的,但有个问题:子线程创建的View可以不通过主线程显示吗?如果不能越过主线程显示,那结果等价于子线程不能更新UI,因为不能被看到的View毫无意义啊。

fogcoding avatar Aug 05 '19 07:08 fogcoding

@fogcoding

这句话应该改成: 不能更新非本线程创建的View

源码里面的注释是只能创建view的线程去更新该view,所以理论上是可以子线程创建,然后子线程更新的,但有个问题:子线程创建的View可以不通过主线程显示吗?如果不能越过主线程显示,那结果等价于子线程不能更新UI,因为不能被看到的View毫无意义啊。

后来想了想,我说的这句话也不对,为何不能更新主线程的view,源自于 ViewRootImp 对线程做了校验,那么如果 创建一个View,不显示出来,也就没有了ViewRootImp中的线程校验,所以略尴尬=.=

也就是说,如果一个View没有被add到 window中,其实就是通过ViewRootImp.setView 添加到window中,那么就不存在 线程校验这回事

那么我们什么时候需要一个不存在于页面上的View呢??? 我目前最常用的是拼图=.=,生成一个view 然后截图

yizems avatar Aug 05 '19 07:08 yizems

@fogcoding

这句话应该改成: 不能更新非本线程创建的View

源码里面的注释是只能创建view的线程去更新该view,所以理论上是可以子线程创建,然后子线程更新的,但有个问题:子线程创建的View可以不通过主线程显示吗?如果不能越过主线程显示,那结果等价于子线程不能更新UI,因为不能被看到的View毫无意义啊。

后来想了想,我说的这句话也不对,为何不能更新主线程的view,源自于 ViewRootImp 对线程做了校验,那么如果 创建一个View,不显示出来,也就没有了ViewRootImp中的线程校验,所以略尴尬=.=

也就是说,如果一个View没有被add到 window中,其实就是通过ViewRootImp.setView 添加到window中,那么就不存在 线程校验这回事

那么我们什么时候需要一个不存在于页面上的View呢??? 我目前最常用的是拼图=.=,生成一个view 然后截图

再补充一点,有一种 子线程可以更新主线程View的方法就是 在view被添加进window之前,应该是在onResume之前吧,忘记了,有兴趣的可以看看activity 启动源码

yizems avatar Aug 05 '19 07:08 yizems

我翻到了上面有个哥子发了篇博客,博客里说到了更新ui时通常会调用requestLayout方法,最终调用到ViewRootImpl的requestLayout方法。

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

只要条件判断不成立,就能跳过线程检查的步骤。 然后博主搞了个骚操作,先在主线程更新UI,使得performLayout设置mHandlingLayoutInLayoutRequest为true,然后趁着主线程更新状态还没完成,马上又去用子线程更新UI,成功了。

这个无疑是对更新逻辑的利用,但是仍旧无法实现直接在子线程直接更新UI,也并没有回答让子线程创建的View在window中显示出来,并且还能更新UI的问题。 现在暂时没空去花时间仔细研究个结果,但上面的话无疑是对更新UI的知识的推进,所以贴了上来。 有进一步推进理解的,请指教。

fogcoding avatar Aug 06 '19 01:08 fogcoding

checkThread发送在viewRootImpl,其在onResume时创建 子线程在初始化looper且looper.loop开启循环 把子线程运行在oncreate时机更新UI也应该可以

MrCodeSniper avatar Aug 09 '19 05:08 MrCodeSniper

只要操作的View没有被添加到ViewRootImpl中,可以随意在任何线程中操作UI

kobewang123 avatar Sep 18 '19 09:09 kobewang123

子线程能更新UI,但是不推荐这么做。一般说的子线程不能更新UI,是因为执行更新UI操作的时候会进行checkThread检查,checkThread判断如果当前线程不是UI线程就会抛出异常。而checkThread跟ViewRootImpl这个类的对象有关,那么只要ViewRootImpl的对象还未创建,就无法执行checkThread,也就是在子线程更新UI也不会报错。ViewRootImpl的对象是在onResume()之后创建的,因此在onCreate()、onStart()、onResume()中可以做子线程更新UI

Mrsunbw avatar Dec 20 '19 07:12 Mrsunbw

在子线程里更新TextView内容有以下两种场景是不会抛出异常的: 1、TextView还没来得及加入到ViewTree中,比如在Activity onResume执行之前。 2、开启了硬件加速的条件下,已经在ViewTree中的TextView被设置了固定的宽高,或者更新的内容不会导致控件宽高变化,即不会触发重新布局。

结论:在子线程操作View 确实不一定导致Crash,那是因为刚好满足一定的条件没有触发checkThread机制,但这并不代表我们在开发过程中可以这么写。

git4pl avatar Oct 14 '21 01:10 git4pl

子线程是不能用于更新UI的 在子线程更新UI会导致应用崩溃 如果想更新UI就要切换到主线程来更新

mlinqirong avatar Dec 15 '21 01:12 mlinqirong

子线程可以在ViewRootImpl还没有被创建之前更新UI; 访问UI是没有加对象锁的,在子线程环境下更新UI,会造成不可预期的风险; 开发者更新UI一定要在主线程进行操作; Android:为什么子线程不能更新UI

senlinxuefeng avatar Jan 11 '22 13:01 senlinxuefeng

发散一下思维 , Android禁止在子线程更新UI的根本原因是为了维持单线程更新UI,为什么不能多线程更新UI需要认真考虑其缺点和优点

yongzheng7 avatar Feb 03 '23 06:02 yongzheng7

这句话应该改成: 不能更新非本线程创建的View

源码里面的注释是只能创建view的线程去更新该view,所以理论上是可以子线程创建,然后子线程更新的,但有个问题:子线程创建的View可以不通过主线程显示吗?如果不能越过主线程显示,那结果等价于子线程不能更新UI,因为不能被看到的View毫无意义啊。

想想 Toast,Toast 可不可以在子线程弹出呢,答案是可以的,Toast 中 View 创建的时候所处的线程是子线程,弹出时进行checkThread 检查也会直接通过,最后添加到 wms 中,就可以显示出来

LvKang-insist avatar Apr 23 '23 09:04 LvKang-insist

这是来自QQ邮箱的假期自动回复邮件。  

luckilyyg avatar Apr 23 '23 09:04 luckilyyg

这是来自QQ邮箱的假期自动回复邮件。   您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

Empty0Qc avatar Apr 23 '23 09:04 Empty0Qc