在实际开发中,像计时器、状态监控面板等场景会频繁地调用 TextView.setText() 来刷新 UI,但高频刷新 TextView 其实是非常容易造成性能瓶颈。
本篇文章将从 Android 源码角度剖析 TextView.setText() 背后的行为逻辑,揭示它可能引发的 requestLayout() 和 invalidate() 操作。
setText() 为什么可能导致卡顿?
setText() 只是简单地换个文字,为什么会影响性能?关键点在于:
- setText() 可能会触发 requestLayout(),导致整个 View 或 ViewGroup 重新测量和布局,频繁触发就会造成卡顿。
源码分析,TextView.setText() 背后的布局判断
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
...
if (mLayout != null) {
checkForRelayout();
}
...
}
private void checkForRelayout() {
// 如果我们有一个固定宽度,并且文本高度保持不变,或者 View 高度是固定的,
// 就可以尝试直接更新文本布局而不重新测量整个 View。
// 当mLayoutParams.width不是WRAP_CONTENT或者宽度固定
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) {
// 固定高度,仅刷新界面,不重新布局.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {
autoSizeText();
invalidate();
return;
}
// 高度是 wrap_content,但文本高度没变,也不需要 relayout
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return;
}
}
// 否则,文本高度发生了变化,且高度是 wrap_content,必须重新布局
requestLayout();
invalidate();
} else {
//宽度是动态的(wrap_content 或者 max ≠ min),必须重新生成布局并 requestLayout
nullLayouts();
requestLayout();
invalidate();
}
}
总结:什么情况下不会触发 requestLayout()
条件一:宽度是固定的
条件二:当前可用宽度大于 0
条件三:高度是固定的或者高度是 wrap_content,但文本内容高度没变
另外还有一个隐藏条件:如果是 跑马灯模式(MARQUEE),无论高度是否变化,都会执行 requestLayout(),因为需要特殊布局支持
布局建议
- 宽高尽量使用固定值或 match_parent
- 避免使用 ellipsize="marquee",除非确有需求