我們都知道字線程里更新不能更新UI格仲,否則系統(tǒng)會(huì)報(bào)Only the original thread that created a view hierarchy can touch its views.
錯(cuò)誤吱晒,具體如下:
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í)行的可以朝墩,若休眠了一定時(shí)間后就不可以了。 這是為什么呢伟姐?
為什么會(huì)報(bào)Only the original thread that created a view hierarchy can touch its views.
錯(cuò)誤收苏?
首先我們要搞懂一個(gè)問題就是為什么會(huì)報(bào)Only the original thread that created a view hierarchy can touch its views.
錯(cuò)誤?
從上面錯(cuò)誤信息堆椃弑可以看到是ViewRootImpl.requestLayout()
方法里調(diào)用的checkThread
里爆出了這個(gè)錯(cuò)誤:
@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.");
}
}
這里就可以看到具體檢查報(bào)錯(cuò)的是在ViewRootImpl.requestLayout()
方法里鹿霸,但是這個(gè)ViewRootImpl
是啥?為什么我們更新view
會(huì)到這里秆乳?這里就要說到了requestLayout()
方法了懦鼠。
requestLayout()
(1)如果我們修改了一個(gè) View,如果修改結(jié)果影響了它的尺寸,那么就會(huì)觸發(fā)這個(gè)方法屹堰。(2) 從方法名字可以知道肛冶,“請求布局”,那就是說扯键,如果調(diào)用了這個(gè)方法睦袖,那么對于一個(gè)子View來說,應(yīng)該會(huì)重新進(jìn)行布局流程忧陪。但是扣泊,真實(shí)情況略有不同,如果子View調(diào)用了這個(gè)方法嘶摊,其實(shí)會(huì)從View樹重新進(jìn)行一次測量延蟹、布局、繪制這三個(gè)流程叶堆,最終就會(huì)顯示子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è)置標(biāo)記位 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è)置標(biāo)記位沥匈,該標(biāo)記位的作用就是標(biāo)記了當(dāng)前的View
是需要進(jìn)行重新布局的 - 接著調(diào)用
mParent.requestLayout
方法,這個(gè)十分重要忘渔,因?yàn)檫@里是向父容器請求布局高帖,即調(diào)用父容器的requestLayout
方法,為父容器添加PFLAG_FORCE_LAYOUT標(biāo)記位畦粮,而父容器又會(huì)調(diào)用它的父容器的requestLayout
方法散址,即requestLayout
事件層層向上傳遞乖阵,直到DecorView
,即根View
- 而根
View
又會(huì)傳遞給ViewRootImpl
预麸,也即是說子View
的requestLayout
事件瞪浸,最終會(huì)被ViewRootImpl接收并得到處理
縱觀這個(gè)向上傳遞的流程,其實(shí)是采用了責(zé)任鏈模式吏祸,即不斷向上傳遞該事件对蒲,直到找到能處理該事件的上級(jí),在這里贡翘,只有ViewRootImpl
能夠處理requestLayout
事件蹈矮。到這里我們就明白了為什么當(dāng)更新View
的時(shí)候如果觸發(fā)了requestLayout
方法為什么會(huì)到ViewRootImpl.requestLayout()
處理。
為什么 Activity.onCreate
可以在字線程里更新UI鸣驱?
上面介紹到最終報(bào)錯(cuò)是由ViewRootImpl
處理的含滴,那么這里就涉及到了Activity
的創(chuàng)建過程了。這里貼一個(gè)網(wǎng)上大佬畫的startActivity流程圖
Activity的啟動(dòng)過程丐巫,我們可以從Context的startActivity說起,其實(shí)現(xiàn)是ContextImpl的startActivity勺美,然后內(nèi)部會(huì)通過Instrumentation來嘗試啟動(dòng)Activity递胧,這是一個(gè)跨進(jìn)程過程,它會(huì)調(diào)用ams的startActivity方法赡茸,當(dāng)ams校驗(yàn)完activity的合法性后缎脾,會(huì)通過ApplicationThread回調(diào)到我們的進(jìn)程,這也是一次跨進(jìn)程過程占卧,而applicationThread就是一個(gè)binder遗菠,回調(diào)邏輯是在binder線程池中完成的,所以需要通過Handler H將其切換到ui線程华蜒,第一個(gè)消息是LAUNCH_ACTIVITY辙纬,它對應(yīng)handleLaunchActivity,在這個(gè)方法里完成了Activity的創(chuàng)建和啟動(dò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類實(shí)例
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
類實(shí)例是在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)注兩個(gè)方法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
里實(shí)現(xiàn)子線程里操作UI譬涡,因?yàn)榇藭r(shí)ViewRootImpl
并為創(chuàng)建不會(huì)進(jìn)行線程檢查。