如果你在网上搜索CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views. 那么你肯定能看到很多文章说Android里子线程不能刷新UI。这句话不能说错,只是有些不太严谨。其实线程能否刷新UI的关键在于ViewRoot是否属于该线程。
让我们一起看看代码吧!
首先,CalledFromWrongThreadException这个异常是有下面的代码抛出的:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
该段代码出自 framework/base/core/java/android/view/ViewRoot.java
其次,看看RootView的构造函数:
public ViewRoot(Context context) {
super();
if (MEASURE_LATENCY && lt == null) {
lt = new LatencyTimer(100, 1000);
}
// For debug only
//++sInstanceCount;
// Initialize the statics when this class is first instantiated. This is
// done here instead of in the static block because Zygote does not
// allow the spawning of threads.
getWindowSession(context.getMainLooper());
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this, context);
mInputMethodCallback = new InputMethodCallback(this);
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
}
最后,我们看看ViewRoot.checkThread的调用顺序:
com.david.test.helloworld.MainActivity$TestThread2.run
-> android.widget.TextView.setText
-> android.widget.TextView.checkForRelayout
-> android.view.View.invalidate
-> android.view.ViewGroup.invalidateChild
-> android.view.ViewRoot.invalidateChildInParent
-> android.view.ViewRoot.invalidateChild
-> android.view.ViewRoot.checkThread