我們都知道字線程里更新不能更新UI,否則系統(tǒng)會報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么?,答案是可以的。但是不是全部可以,如果子線程是立馬執(zhí)行的可以,若休眠了一定時間后就不可以了。 這是為什么呢?
為什么會報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.錯誤?
從上面錯誤信息堆??梢钥吹绞?code>ViewRootImpl.requestLayout()方法里調(diào)用的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,如果修改結(jié)果影響了它的尺寸,那么就會觸發(fā)這個方法。(2) 從方法名字可以知道,“請求布局”,那就是說,如果調(diào)用了這個方法,那么對于一個子View來說,應(yīng)該會重新進行布局流程。但是,真實情況略有不同,如果子View調(diào)用了這個方法,其實會從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;
}
//為當(dāng)前view設(shè)置標記位 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方法中,首先先判斷當(dāng)前View樹是否正在布局流程,接著為當(dāng)前子View設(shè)置標記位,該標記位的作用就是標記了當(dāng)前的View是需要進行重新布局的 - 接著調(diào)用
mParent.requestLayout方法,這個十分重要,因為這里是向父容器請求布局,即調(diào)用父容器的requestLayout方法,為父容器添加PFLAG_FORCE_LAYOUT標記位,而父容器又會調(diào)用它的父容器的requestLayout方法,即requestLayout事件層層向上傳遞,直到DecorView,即根View - 而根
View又會傳遞給ViewRootImpl,也即是說子View的requestLayout事件,最終會被ViewRootImpl接收并得到處理
縱觀這個向上傳遞的流程,其實是采用了責(zé)任鏈模式,即不斷向上傳遞該事件,直到找到能處理該事件的上級,在這里,只有ViewRootImpl能夠處理requestLayout事件。到這里我們就明白了為什么當(dāng)更新View的時候如果觸發(fā)了requestLayout方法為什么會到ViewRootImpl.requestLayout()處理。
為什么 Activity.onCreate可以在字線程里更新UI?
上面介紹到最終報錯是由ViewRootImpl處理的,那么這里就涉及到了Activity的創(chuàng)建過程了。這里貼一個網(wǎng)上大佬畫的startActivity流程圖
Activity的啟動過程,我們可以從Context的startActivity說起,其實現(xiàn)是ContextImpl的startActivity,然后內(nèi)部會通過Instrumentation來嘗試啟動Activity,這是一個跨進程過程,它會調(diào)用ams的startActivity方法,當(dāng)ams校驗完activity的合法性后,會通過ApplicationThread回調(diào)到我們的進程,這也是一次跨進程過程,而applicationThread就是一個binder,回調(diào)邏輯是在binder線程池中完成的,所以需要通過Handler H將其切換到ui線程,第一個消息是LAUNCH_ACTIVITY,它對應(yīng)handleLaunchActivity,在這個方法里完成了Activity的創(chuàng)建和啟動。我們在這里主要分析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();
//創(chuàng)建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創(chuàng)建的,然后又調(diào)用了handleResumeActivity方法
ActivityThread.handleResumeActivity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
//調(diào)用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) {
....
//創(chuàng)建添加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);
}
}
}
.....
}
}
這里主要關(guān)注兩個方法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里調(diào)用了Activity的performResume()方法,這里操作了mInstrumentation的callActivityOnResume()方法里調(diào)用了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)最終調(diào)用了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的創(chuàng)建,從上面過程可以看到ViewRootImpl的創(chuàng)建是在Activity.onResume之后的,這也解釋了為什么我們可以在Activity.onCreate甚至Activity.onResume里實現(xiàn)子線程里操作UI,因為此時ViewRootImpl并為創(chuàng)建不會進行線程檢查。