上一篇中有段代码,这段子线程中可以成功更新UI,
private void newThreadChange(){
new Thread(){
@Override
public void run() {
mTv.setText("changed");
}
}.start();
}
我现在把代码稍稍修改一下,让他sleep一会,然后更新UI,这个时候会直接报错,报错信息如下图
private void newThreadChange(){
new Thread(){
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
mTv.setText("changed");
}
}.start();
}
我们根据其调用链分析一下
- android.widget.TextView.setText
- android.widget.TextView.checkForRelayout
- android.view.View.requestLayout
- android.view.ViewRootImpl.requestLayout
- android.view.ViewRootImpl.checkThread
从TextView的setText开始,调用了checkForRelayout这个方法,简单的看下下面的这个方法,发现这里面有个两个刷新view的方法 requestLayout()和 invalidate();
private void checkForRelayout() {
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
...
...
requestLayout();
invalidate();
} else {
nullLayouts();
requestLayout();
invalidate();
}
}
根据上面的调用链,我们只看requestLayout()方法,这里是View的requestLayout()的方法
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
...
}
这里的mParent是一个接口,ViewGroup是其一个实现类,这里的mParent也就是包裹该View的外层ViewGroup,然后我们继续看ViewGroup中的requestLayout()
@Override
public void requestLayout() {
super.requestLayout();
mDirtyHierarchy = true;
}
从这里看,发现还是调用了View里面的requestLayout(),
- 然后View(这里的view就是ViewGroup)还是调用mParent中的requestLayout,
- mParent还是包裹该ViewGroup的ViewGroup,
- 就这样一直往上层调用,一直调用最外层的ViewGroup的requestLayout()
根据调用链,我们发现最后走到了ViewRootImpl的requestLayout(),然后在这里我们就发现有个checkThread()的调用, 然后就抛出异常了,
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
但是这还是解释不了,不加sleep的时候不抛异常呀!
现在提供给我们一个线索,Android在WindowManagerGlobal.addView中初始化了ViewRootImpl,不信看下面的代码
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
synchronized (mLock) {
//这里初始化了ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
}
}
现在我们继续追根溯源,找到哪里调用这个方法,以及调用的时机,我们就可以进行相应的分析了,
- 如果是在调用这个方法之前刷新,调用requestLayout,那ViewRootImpl还没有初始化,那mParent==null,就不会调用到ViewRootImpl.requestLayout了,也就不会抛出异常了,
- 如果在这个方法之后,调用了requestLayout,那就会调用到ViewRootImpl.requestLayout里,并且根据线程判断,非主线程刷新就会抛出异常了
那接着找哪里调用了WindowManagerGlobal.addView,
//在WindowManagerImpl中调用了addView
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
这里发现WindowManagerGlobal是个单例的
继续向上查找,在activity中找到了蛛丝马迹
public void setVisible(boolean visible) {
if (mVisibleFromClient != visible) {
mVisibleFromClient = visible;
if (mVisibleFromServer) {
if (visible) makeVisible();
else mDecor.setVisibility(View.INVISIBLE);
}
}
}
//WindowManagerImpl是WindowManager的唯一实现类,WindowManager.addView也就是WindowManagerImpl.addView
//这里调用了WindowManagerImpl的addView
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
继续找哪里调用了makeVisible(),或setVisible() 我们发现在ActivityThread.handleResumeActivity中调用了这个方法
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
r.nextIdle = mNewActivities;
mNewActivities = r;
Looper.myQueue().addIdleHandler(new Idler());
}
这个方法我们都知道是在acitvity的onResume回调时调用的,也就是在onResume的时候,才会有ViewRootImpl的初始化,
这个时候我们就知道了原来是这么个流程
- onCreate的时候ViewRootImpl还没创建,调用requestLayout并不会调用ViewRootImpl的requestLayout,不会checkThread,也不会抛异常
- 有了延时之后,已经执行到onResume了,ViewRootImpl创建完成,这个时候子线程刷新UI,就会checkThread了