咱们都知道字线程里更新不能更新UI,不然系统会报Only the original thread that created a view hierarchy can touch its views.
错误,具体以下:java
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么?,答案是能够的。可是不是所有能够,若是子线程是立马执行的能够,若休眠了必定时间后就不能够了。 这是为何呢?android
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.
错误?git
从上面错误信息堆栈能够看到是ViewRootImpl.requestLayout()
方法里调用的checkThread
里爆出了这个错误:github
@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()
方法了。bash
requestLayout()
(1)若是咱们修改了一个 View,若是修改结果影响了它的尺寸,那么就会触发这个方法。(2) 从方法名字能够知道,“请求布局”,那就是说,若是调用了这个方法,那么对于一个子View来讲,应该会从新进行布局流程。可是,真实状况略有不一样,若是子View调用了这个方法,其实会从View树从新进行一次测量、布局、绘制这三个流程,最终就会显示子View的最终状况。app
源码分析View#requestLayout
:ide
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
ui
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
并为建立不会进行线程检查。