标签:
今天晚上被弟弟告知他在子线程中更新了UI,问我是不是版本的问题,我果断说是他的代码写错了,不过分分钟被打脸,经过我一番仔细的探查最终发现了原因,或许这件事的结果不是多么的重要,但是我认为探查的过程还是有一定的参考价值的.
首先,遇见这种问题时下意识的是去google,所以我采取了下面的措施(请忽视我不堪入目的英语,相信google的强大….)
然而我发现我并没有得到我想要的结果,大部分的答案是告诉我如何在子线程中转到主线程中更新UI,好吧,难道是我不应该用?号,所以,我做了下面的事.
可悲的是google觉得我表达的是一个意思…(可能是我英语太差了,请不要告诉我这个事实),没办法了,只能自己上阵了,感谢google搜索不到,才让自己有了这次探索的经历!
首先,我们先看一下代码,代码的意思很简单,出乎意料的时,它正确运行了,并且在手机界面上显示的是Changed,这打破了我们在非主线程中不能更新UI的认识
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv = (TextView) findViewById(R.id.tv_test);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("Changed");
}
}).start();
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv = (TextView) findViewById(R.id.tv_test);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tv.setText("Changed");
}
}).start();
}
}
正确的错误
终于出现了,请看一下令人高兴的久违的错误
然后我们就探查tv.setText("Changed");
内部做了什么,不断的跟进内部方法,我们会走到这个方法中,我们会注意到,最终都会调用invalidate()方法重新绘制,这也是非常符合自然逻辑的,所以我们就去探索invalidate()中做了什么
/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
*/
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) {
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)) {
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();
}
}
p.invalidateChild(this, damage);
这句代码,p是一个ViewParent,熟悉View绘制流程的小伙伴看到ViewParent就会恍然大悟,著名的ViewRootImpl就是ViewParent的子类,所以我们直接去ViewRootImpl中搜寻invalidateChild方法 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// 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();
}
}
// Damage the entire IsolatedZVolume receiving this view‘s shadow.
if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
checkThread()
,点进去看这个函数的实现后发现他做的事是我们再熟悉不过的了,熟悉的代码熟悉的报错信息,到此一切都真相大白了,检查当前线程是否是主线程的逻辑在ViewRootImpl方法中,熟悉View绘制流程的小伙伴肯定知道ViewRootImpl是在onResume方法中去创建的,所以说,只要在onResume方法调用之前,都是可以在子线程中更新UI的 @Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
return null;
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这次的探查过程给了我很大的启示,遇见问题时,在必要时首先要回忆之前遇到的相似问题,并合理利用网上搜索的信息去自己探索问题的真相,一个根据关键信息推导出来的合理假设将使我们事半功倍,并且注意不要盲目的相信网上的一些结论,纸上得来终觉浅,绝知此事要躬行!
标签:
原文地址:http://blog.csdn.net/geekerhw/article/details/52270496