咱們都知道字線程裏更新不能更新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
併爲建立不會進行線程檢查。