Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Moosphan opened this issue Jul 5, 2019 · 22 comments
Open

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

Moosphan opened this issue Jul 5, 2019 · 22 comments

Comments

@Moosphan
Copy link
Owner

Moosphan commented Jul 5, 2019

No description provided.

@SuDreamer
Copy link

SuDreamer commented Jul 5, 2019

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

@Moosphan Moosphan changed the title 2019-07-05:子线程能否更新UI? 2019-07-05:子线程能否更新UI?为什么? Jul 5, 2019
@Moosphan
Copy link
Owner Author

Moosphan commented Jul 5, 2019

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

@DaveBoy
Copy link

DaveBoy commented Jul 5, 2019

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

@gabyallen
Copy link

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

@yangfanggang
Copy link

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

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

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

@BGround
Copy link

BGround commented Jul 9, 2019

不能直接更新,更新方式有三种
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线程更新

@18361237136
Copy link

不能直接更新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

@yizems
Copy link

yizems commented Aug 2, 2019

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

@fogcoding
Copy link

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

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

@yizems
Copy link

yizems commented Aug 5, 2019

@fogcoding

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

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

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

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

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

@yizems
Copy link

yizems commented Aug 5, 2019

@fogcoding

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

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

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

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

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

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

@fogcoding
Copy link

fogcoding commented Aug 6, 2019

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

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

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

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

@MrCodeSniper
Copy link

MrCodeSniper commented Aug 9, 2019

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

@kobewang123
Copy link

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

@Mrsunbw
Copy link

Mrsunbw commented Dec 20, 2019

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

@git4pl
Copy link

git4pl commented Oct 14, 2021

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

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

@mlinqirong
Copy link

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

@senlinxuefeng
Copy link

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

@yongzheng7
Copy link

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

@LvKang-insist
Copy link

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

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

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

@luckilyyg
Copy link

luckilyyg commented Apr 23, 2023 via email

@Empty0Qc
Copy link

Empty0Qc commented Apr 23, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests