美文网首页程序员Android开发经验谈Android开发
Android 关于子线程更新UI的那些事

Android 关于子线程更新UI的那些事

作者: JingChen_ | 来源:发表于2020-12-02 15:33 被阅读0次

相信大家都有听过,子线程更新UI的操作。但这种说法,不是很明确。有些人说子线程更新UI会挂,而有些人说子线程可以更新UI。接下来分析下这两种情况。

先来说说子线程更新UI会挂的问题吧。

在Activity中onCreate完后,会生成一个ViewRootImpl,View的绘制都是同个它来实现的,而ViewRootImpl调用到requestLayout()来完成View的绘制操作。看下源码:

//ViewRootImpl.java
 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

但布局绘制或者发生变化时,会调用requestLayout(),而里面有checkThread(),来看下它的源码:

//ViewRootImpl.java
void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

其中mThread是在ViewRootImpl创建时初始化的,把创建时的线程和mThread绑定,而ViewRootImpl又是在主线程初始化的,所以mThread表示主线程。假如更新UI,会调用requestLayout() -> checkThread() -> mThread != Thread.currentThread(),判断是否在主线程,如果在子线程中,会抛异常,所以子线程更新UI才会挂。

子线程更新UI会挂的思路明确了,再来看看子线程更新UI为什么不会挂吧。

刚才讲到了,在Activity中onCreate完后,会生成一个ViewRootImpl,那么之后它就会去检查你更新UI时在哪个线程。那假如我在onCreate时去开一个子线程更新UI,此时ViewRootImpl还没创建,就不会去检测UI变化,所以在onCreate中子线程是可以更新UI的。

那又有些人说,我不在onCreate里面更新,我就在onCreate后在子线程里面去更新,它还是不会挂,这又是为什么呢?主要还是看下更新UI的方法吧

    text1.setOnClickListener {
        thread {
            it.post {
                text1.text = "change"
            }
        }
    }

这里用一个点击事件,开子线程更新UI,运行后发现没挂,这是为什么?看下post的源码:

    //View.java
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

通过源码分析,你就知道这个mHandler是在ViewRootImpl里面赋值的,mHandler是主线程的Handler,而又掉了handler.post(),所以只要Handler在主线程,那么它post的所有的UI操作都是主线程。看起来像在子线程,实际是回到主线程更新UI。

继续看这种

class MyTestActivity:AppCompatActivity() {
    private val handler = Handler()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.ab)
    }

    override fun onResume() {
        super.onResume()
        getHandler()
    }
    private fun getHandler(){
        thread {
            handler.post {
                text0.text = "change"
            }
        }
    }
}

由于创建handler时是在主线程,所以这个handler是属于主线程的,所以其他的步骤就更上面的一样了。另一种handler的发消息更新的方式我就不写出来了,只要通过handler更新UI的,只要handler是主线程的,必定不会挂。之前有写过Handler使用的文章,有兴趣的同学可以看看~

还有一种就是调用runOnUiThread{}

class MyTestActivity:AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.ab)
    }

    override fun onResume() {
        super.onResume()
        thread()
    }
    private fun thread(){
        thread {
            runOnUiThread {
                text.text = "change"
            }
        }
    }
}

顾名思义,在主线程运行,只要调了这个方法,所有操作都在主线程里面,所有只要的操作不会挂。

总结一点,并不是说子线程可以更新,仔细点说:子线程中可以在调用主线程的Handler去更新UI,或者子线程可以调用runOnUIThread{}切换到主线程更新UI。

之前还遇到一种特殊情况,在onResume中更新UI不会挂,要是在最后一行加上 view.requestLayout()

 @Override
    protected void onResume() {
        super.onResume();
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                TextView view = findView(R.id.tv_content);
                view.setText("我在子线程更新");
                view.setBackgroundColor(Color.RED);
                view.requestLayout();
            }
        }.start();
    }

再试一下,就崩了

总结一点:实际上,就是只要你改view,不触发checkThread()就没事,而TextView的宽高不改变,也不会去触发requestLayout(),修改背景也同样。不会触发view的位置大小改变。当然,这种情况,不是每个版本的android都有用,还是要规范的去主线程更新UI。

相关文章

网友评论

    本文标题:Android 关于子线程更新UI的那些事

    本文链接:https://www.haomeiwen.com/subject/zdbywktx.html