TextView的不当使用引起的性能问题

无意间发现进入某个页面时,一个自定义ViewGroup控件的onLayout方法每秒都在输出日志。在它的onLayout方法,会对子View的位置进行计算然后重新排列,这样无疑是非常耗性能的。通过排查发现,是因为我们每秒都在调用TextView的setText方法导致的。为什么调用setTextView会导致父View的onLayout方法被触发呢?

通过代码看一看吧:

TextView.java

1
2
3
4
5
6
7
8
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
...
if (mLayout != null) {
checkForRelayout();
}
...
}

checkForRelayout方法是罪魁祸首,我们来看一下它的实现:

TextView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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.
// 如果width不是wrap_content(后面条件不重要先忽略)则走入if分支
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
...
// 如果ellipsize属性不是 MARQUEE
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
// 如果TextView的height不是wrap_content也不是match_parent,则调用invalidate即可
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.
// 如果TextView的高度没有变化,调用invalidate即可
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();
// 最终通过ViewRootImpl导致整个Viewtree的onLayout方法被调用
requestLayout();
invalidate();
}
}

对应项目中的问题就是,在某一次提交的时候,把这个美妙刷新文字的TextView的width改成了wrap_content,导致了每次调用setText时都会调用relayout方法。

解决方法:为TextView的width设置一个合适的dp值。