Android在子线程更新UI没有崩溃?

1,033 阅读2分钟

首先,看一个经常会被提及的问题,在Activity的onCreate()方法中开启子线程更新UI,程序为什么不会崩溃?

原因在于绘制相关操作是由ViewRootImpl完成的,回顾Activity启动流程:ViewRootImpl是在ActivityThread的handleResumeActivity中初始化的,所以在onCreate()中开启子线程更新UI是不会崩溃的,因为还没有开始绘制流程,而如果在子线程加个延迟或在onResume()中开启子线程更新UI,那就会有可能崩溃了,为什么是有可能?接着往下看

我们就拿在子线程调用TextView.setText()示例,先看下源码:

1632822846(1).jpg

我们看到在setText()中调用了checkForRelayout(),点进去看看

1632823083(1).jpg

这是checkForRelayout()的全部逻辑,红框圈出的是重点,稍后再看,先看下requestLayout()

1632823402(1).jpg

我们看到在requestLayout()中有检测线程的代码checkThread(),再去看下checkThread()

1632823512(1).jpg

mThread其实就是ViewRootImpl初始化所在的线程,刚刚提到Activity启动流程中在主线程创建了ViewRootImpl,然后在子线程更新UI,当然就会抛出异常:"Only the original thread that created a view hierarchy can touch its views."

但是我们如果是在绘制流程结束后,再在子线程更新UI,其实也是可以做到不崩溃的,看上面截图中的源码即可,只要不进入requestLayout()中就可以避免线程检查,那我们看下都有哪些条件可以实现不执行requestLayout()

1、宽度不是wrap_content的或者mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth

2、mHint == null || mHintLayout != null

3、mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)

同时满足上述三个条件表明宽度是大于0的固定值,反之如果宽度是wrap_content则肯定会执行requestLayout()执行线程检测

满足上述三个条件后,再往下看

首先要满足mEllipsize != TextUtils.TruncateAt.MARQUEE,然后下述两个条件满足一个即可不执行requestLayout()

1、mLayoutParams.height != LayoutParams.WRAP_CONTENT && mLayoutParams.height != LayoutParams.MATCH_PARENT,也就是高度是固定值

2、mLayout.getHeight() == oldht && (mHintLayout == null || mHintLayout.getHeight() == oldht),如果高度不为固定值,满足该条件即更新前后高度没有变化,并且HintLayout的高度也没有变化

满足以上几个条件的情况下,就不会执行requestLayout(),而是直接调用invalidate()更新,这样就可以在子线程中更新UI了

总结:根据上面分析,我们可以看到,系统为了优化,在View尺寸固定或没有变化的情况下是不会重新Layout的,这些情况下我们是可以在子线程中更新UI的,但是代码规范,我们最好还是在主线程更新UI,这里列出这些避免崩溃的情形,不是为了在开发中故意避免线程检查而使用,只是便于在遇到问题时能够分析出原因,以及加深对源码的理解。