我们都知道字线程里更新不能更新UI,否则系统会报Only the original thread that created a view hierarchy can touch its views.
错误,具体如下:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7313)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1161)
at android.view.View.requestLayout(View.java:21995)
at android.view.View.requestLayout(View.java:21995)
at android.view.View.requestLayout(View.java:21995)
at android.view.View.requestLayout(View.java:21995)
at android.view.View.requestLayout(View.java:21995)
at android.view.View.requestLayout(View.java:21995)
at android.view.View.requestLayout(View.java:21995)
at androidx.recyclerview.widget.RecyclerView.requestLayout(RecyclerView.java:4202)
at androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:5286)
at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:11997)
at androidx.recyclerview.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:7070)
at com.github.jokar.wechat_moments.view.adapter.MomentsAdapter.submitList(MomentsAdapter.java:71)
at com.github.jokar.wechat_moments.view.MainActivity$1.run(MainActivity.java:51)
那么Activity.onCreate
可以在字线程里更新UI么?,答案是可以的。但是不是全部可以,如果子线程是立马执行的可以,若休眠了一定时间后就不可以了。 这是为什么呢?
为什么会报Only the original thread that created a view hierarchy can touch its views.
错误?
首先我们要搞懂一个问题就是为什么会报Only the original thread that created a view hierarchy can touch its views.
错误?
从上面错误信息堆栈可以看到是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.");
}
}
这里就可以看到具体检查报错的是在ViewRootImpl.requestLayout()
方法里,但是这个ViewRootImpl
是啥?为什么我们更新view
会到这里?这里就要说到了requestLayout()
方法了。
requestLayout()
(1)如果我们修改了一个 View,如果修改结果影响了它的尺寸,那么就会触发这个方法。(2) 从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程。但是,真实情况略有不同,如果子View调用了这个方法,其实会从View树重新进行一次测量、布局、绘制这三个流程,最终就会显示子View的最终情况。
源码分析View#requestLayout
:
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//为当前view设置标记位 PFLAG_FORCE_LAYOUT
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
//向父容器请求布局
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
- 在
requestLayout
方法中,首先先判断当前View
树是否正在布局流程,接着为当前子View
设置标记位,该标记位的作用就是标记了当前的View
是需要进行重新布局的 - 接着调用
mParent.requestLayout
方法,这个十分重要,因为这里是向父容器请求布局,即调用父容器的requestLayout
方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout
方法,即requestLayout
事件层层向上传递,直到DecorView
,即根View
- 而根
View
又会传递给ViewRootImpl
,也即是说子View
的requestLayout
事件,最终会被ViewRootImpl接收并得到处理
纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl
能够处理requestLayout
事件。到这里我们就明白了为什么当更新View
的时候如果触发了requestLayout
方法为什么会到ViewRootImpl.requestLayout()
处理。
为什么 Activity.onCreate
可以在字线程里更新UI?
上面介绍到最终报错是由ViewRootImpl
处理的,那么这里就涉及到了Activity
的创建过程了。这里贴一个网上大佬画的startActivity流程图
Activity的启动过程,我们可以从Context的startActivity说起,其实现是ContextImpl的startActivity,然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用ams的startActivity方法,当ams校验完activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而applicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H将其切换到ui线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动。我们在这里主要分析ActivityThread.handleLaunchActiivty
ActivityThread.handleLaunchActiivty
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
//创建Activity类实例
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
//
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
可以看到Activity
类实例是在performLaunchActivity
创建的,然后又调用了handleResumeActivity
方法
ActivityThread.handleResumeActivity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
//调用Activity.onResume
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
....
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
....
//创建添加ViewRootImpl
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);
}
}
}
.....
}
}
这里主要关注两个方法performResumeActivity
和 wm.addView(decor, l);
performResumeActivity
public final ActivityClientRecord performResumeActivity(IBinder token,
boolean clearHide, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (r != null && !r.activity.mFinished) {
if (clearHide) {
r.hideForNow = false;
r.activity.mStartedActivity = false;
}
try {
...
r.activity.performResume();
....
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to resume activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
}
return r;
}
performResumeActivity
里调用了Activity
的performResume()
方法,这里操作了mInstrumentation
的callActivityOnResume()
方法里调用了Activity
生命周期的onResume
方法
#Activity.performResume
final void performResume() {
performRestart();
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
mCalled = false;
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this);
...
onPostResume();
}
#Instrumentation.callActivityOnResume
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
activity.onResume();
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
am.match(activity, activity, activity.getIntent());
}
}
}
}
wm.addView(decor, l)
wm.addView(decor, l)
最终调用了WindowManagerImpl.addView
#WindowManagerImpl.addView
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
#WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
....
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
}
到这里我们终于看到了
ViewRootImpl
的创建,从上面过程可以看到ViewRootImpl
的创建是在Activity.onResume
之后的,这也解释了为什么我们可以在Activity.onCreate
甚至Activity.onResume
里实现子线程里操作UI,因为此时ViewRootImpl
并为创建不会进行线程检查。