子线程可以修改UI吗?为什么会产生这样的问题,可能是因为在开发过程中遇到了
"Only the original thread that created a view hierarchy can touch its views."
这个异常信息,又或者是常用Handler将子线程中的数据更新到UI上,又或者是其他的一些原因,如果你思考到了为什么要在主线程中更新UI或者子线程中可以更新UI吗这样的问题,说明你有一颗探寻问题本质的心,对于技术从业者来讲,是极为有益的,知其然更要知其所以然,接下来,就一起看看其中的逻辑。
先说结论,子线程是可以修改的UI的,例如下面这行代码
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.yourlayoutid);
new Thread(new Runnable() {
@Override
public void run() {
mContentTv.setText("我是来自子线程的更新信息");
}
}).start();
}
但是如果给这个线程做个延迟,或者使用按钮点击来执行,就会看到上面熟悉的异常信息了,那么我们从这个异常信息入手,这个异常信息是在ViewRootImpl中抛出的
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
如果你不熟悉ViewRootImpl,先不要跳出去看ViewRootImpl,对于刚开始看源码的同学而言,切记不要一看到陌生的类,就跳出去看,在跳出去看的过程中,可能又会遇到不熟悉的类,所以,相关的类的知识点,只需要知道它的大致功能就好,等理解了当前的知识点后,在去看这些陌生的类的细节,刚开始可能会比较痛苦,但是一段时间之后,看源码的效率会大大提高。
ViewRootImpl可以先暂时理解为一个View的管理类,它在执行一些View相关操作的时候,会去检查线程是否为主线程,如果不是,就抛出这个异常。
那么接下来的关注点就是ViewRootImpl何时被创建?
既然是创建,我们就从构造方法入手
public ViewRootImpl(Context context, Display display) {
...
}
可以看到在WindowManagerGlobal的addView方法里,有着ViewRootImpl的初始化操作
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
}
而WindowManagerGlobal的addView方法会在WindowMangerImpl被调用
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl是WindowManager的实现类,而WindowsManager继承自ViewManager,在Activity里的makeVisible方法里有着WindowMangerImpl的addView方法的调用
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
而makeVisible方法在ActivityThread的handleResumeActivity里进行了调用
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
在handleResumeActivity里,调用了perfromResumeActivity方法
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
}
在perfromResumeActivity方法里,调用了activity的performResume方法
@VisibleForTesting
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
String reason) {
r.activity.performResume(r.startsNotResumed, reason);
}
在performResume方法里,调用了Instrument的callActivityOnResume方法
final void performResume(boolean followedByPause, String reason) {
mInstrumentation.callActivityOnResume(this);
}
callActivityOnResume方法里,调用了onResume
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
activity.onResume();
}
至此,我们就可以明白,ViewRootImpl是在onResume的时候创建的,而上面的示例代码,是在onCreate里修改UI的,这个时候还没有ViewRootImpl,所以不会检查线程。
上面的源码分析是从入口一步一步找上来了,现在顺着逻辑在来做一个总结。
在ActivityThread里的handleResumeActivity方法里,调用了onResume,并且创建了ViewRootImpl,有了ViewRootImpl后,就无法在子线程中直接修改UI了。
这两条线,小伙伴可以自己顺着源码找一找具体的调用关系,调用到的函数在文章里都提到了,希望会对小伙伴有帮助~