我们都知道在android中我们想更新一个ui,一定要在主线程中更新,也就是UI线程。因为这和我们的安卓系统设计有关,安卓的系统设计是单线程模式。
规定 更新ui必须在主线程中
那么会存在一些情况违反了这种规定,但又偏偏没有报错,运行成功。
第一种情况,我们直接在onCreate中开启一个子线程更新ui,这是成功的。为什么呢?因为我们的视图绘制是由ViewRootImpl来管理,而ViewRootImpl是在我们onResume之后创建的,也就是说我们在onResume生命周期之前所进行的ui更新,是不会进行线程的判断。所以会导致出现在非ui线程中更新ui成功的情况。
第二种情况,比如我们创建了一个textview,我们开启一个子线程去更新它里面的文本内容,只要我们的文本内容不超出它设置的宽高,就可以更新ui成功。
这是为什么呢?
这里我们只能来看一下源码是怎么写的。
首先我们是调用了 setText 方法来改变文本内容
经过几层调用最后会执行这个setText方法,里面省掉了一些代码,只留下关键代码。
关键代码就在 checkForRelayout() 检查是否需要重新布局
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
mTextSetFromXmlOrResourceId = false;
......
if (mLayout != null) {
checkForRelayout();
}
sendOnTextChanged(text, 0, oldlen, textLength);
onTextChanged(text, 0, oldlen, textLength);
......
}
我们进入 checkForRelayout 方法里面看一下:
这里的代码我没有进行省掉,原原本本的复制过来了。
我们出现第二种情况的关键就出现在这里。
由于我们文本内容没有超出控件设置的宽高,最后不会去调用requestLayout()方法
mEllipsize != TextUtils.TruncateAt.MARQUEE
这行代码的意思是 控件的文字是否动态赋值 这里没有设置肯定就是true了 所以进入判断最后执行了 autoSizeText(); invalidate(); 那么我们下一步的关键就在invalidate 里面,我们知道invalidate 是重绘,即刷新界面的意思。
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();
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();
}
}
在进入invalidate方法以后经过层层调用最后我们来到了 invalidateInternal 方法里面,这个方法里面也是我们第二步的关键。
我们这里调用了 invalidateChild 方法
因为ViewParent是一个接口,一般子view的ViewParent就是它的父View即ViewGroup,那么进ViewGroup的源码看看如何实现的
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
......
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
}
}
viewgourp中的invalidateChild方法
这里我们先说第二种特殊情况,如果是第二种特殊情况,我们设置的文本内容不超出设置的宽高这里会直接进入 if (attachInfo != null && attachInfo.mHardwareAccelerated) { 判断 从而不会执行下面的代码,也就不会去检查线程是否是主线程。
正常来说会执行invalidateChildInParent方法 在invalidateChildInParent方法中会去检查线程是否是ui线程。
我们来看下invalidateChildInParent方法吧。
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
......
//这一步调用了父类的invalidateChildInParent方法
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread(); //检查线程是否是主线程
....
invalidateRectOnScreen(dirty); //最终调用 scheduleTraversals()方法,会调用performDraw()执行进行重绘的工作
return null;
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
以上就是我说的俩种特殊情况,也介绍了一下我们非ui线程更新ui检查报错的一个源码逻辑。