为什么可以在子线程通过setText进行更新UI
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
一般情况下在子线程更新UI是会报错的,因为在ViewRootImpl中会通过checkThread进行检查,在ViewRootImpl还没创建的时候是不会检查的,但这里主要是对已经创建ViewRootImpl还可以更新UI进行分析。
我们直接看下通过子线程setText不会报错的代码和运行结果。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text= findViewById(R.id.text);
}
@Override
public void onClick(View v) {
new Thread((Runnable)()->{
text.setText("改变后");
}).start();
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorAccent"
android:text="改变前"
/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:onClick="onClick"
/>
</LinearLayout>
我们能看到在点击后是能顺利进行更新UI的。关键点就在于 android:layout_width=“100dp” android:layout_height=“100dp”
我这里把视图大小进行了固定。出错肯定是触发了checkThread,但是只要不触发就可以顺利更新UI,而setText不是在任何情况下都会触发checkThread的。
在setText方法中会调用 checkForRelayout,而这个方法就展现了不会触发checkThread的原因。checkThread是在requestLayout中才会进行的,所有这里只要不调用requestLayout就不会进行checkThread。
通过下面的源代码就会发现不是所有情况都会调用requestLayout。在两个return那里都是没用调用requestLayout,所以我们可以知道只要View的大小不是wrap_content,而是match_parent或者是固定不变的dp,就都不会触发requestLayout,自然就可以在子线程更新了。
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.
int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
*/
makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {
autoSizeText();
invalidate();
return;
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return;
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}
总结:
1.调用requestLayout是因为内容需要新的视图布局,当不需要的情况下就不会调用。
2.在子线程对UI更新对是通过checkThread来防止在子线程更新。
3.当View更新时调用requestLayout才会触发CheckThred,只要不调用requestLayout就意味着可以在子线程进行更新。