开发过程中在子线程访问控件的时候会出现Only the original thread that created a view hierarchy can touch its views。那么异常是从哪里来的呢?
通过查询源码可见:
class ViewRootImpl{
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
}
可见ViewRootImpl创建的时候保存了线程信息,checkThread检查了线程信息。那么,哪些方法调用了checkThread呢?
@Override public void requestFitSystemWindows() {//设置试图不覆盖系统窗口的时候 checkThread(); mApplyInsetsRequested = true; scheduleTraversals(); }
@Override public void requestLayout() {//刷新布局的时候 if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
}
void setWindowStopped(boolean stopped) { checkThread(); if (mStopped != stopped) { mStopped = stopped;
}
}
@Override public void requestTransparentRegion(View child) { // the test below should not fail unless someone is messing with us checkThread(); if (mView != child) { return; } }
@Override public void requestChildFocus(View child, View focused) { if (DEBUG_INPUT_RESIZE) { Log.v(mTag, "Request child focus: focus now " + focused); } checkThread(); scheduleTraversals(); }
@Override public void clearChildFocus(View child) { if (DEBUG_INPUT_RESIZE) { Log.v(mTag, "Clearing child focus"); } checkThread(); scheduleTraversals(); }
@Override public void focusableViewAvailable(View v) { checkThread(); if (mView != null) { if (!mView.hasFocus()) {
}
}
}
@Override public void recomputeViewAttributes(View child) { checkThread(); if (mView == child) { mAttachInfo.mRecomputeGlobalAttributes = true; if (!mWillDrawSoon) { scheduleTraversals(); } } }
@Override public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) { checkThread(); try { final AudioManager audioManager = getAudioManager(); if (mFastScrollSoundEffectsEnabled
@Override public View focusSearch(View focused, int direction) { checkThread(); if (!(mView instanceof ViewGroup)) { return null; } return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction); }
@Override public View keyboardNavigationClusterSearch(View currentCluster, @FocusDirection int direction) { checkThread(); return FocusFinder.getInstance().findNextKeyboardNavigationCluster( mView, currentCluster, direction); }
void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mRemoved) {
我们平时最常用的大概就是刷新布局了。当异步数据请求完毕的时候,要填充数据时就要刷新布局,checkThread就生效了。
由此可见 只要 ViewRootImpl构造函数中存储的线程和调用刷新时的线程一致,那么就不会报错。所以也并不是不能在子线程刷新布局,只要ViewRootImpl构造的时候也在同一个子线程就可以了。但是一般不建议这么做。至于ViewRootImpl什么时候创建实例,篇幅比较长,我们在讲Activity启动的时候再说。