We all know that the UI cannot be updated in the word thread, otherwise Only the original thread that created a view hierarchy can touch its views. Error, as follows:
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.MainActivityThe $1.run(MainActivity.java:51)
Copy the code
Can activity.onCreate update the UI in the word thread? The answer is yes. But not all, if the child thread executes immediately, but not if it has been dormant for a certain amount of time. Why is that?
Why would it be reported?Only the original thread that created a view hierarchy can touch its views.
The mistake?
First of all, we need to understand why Only the original thread that created a view hierarchy can touch its views. The mistake?
Can see from the above error message stack is ViewRootImpl requestLayout () method calls out the mistakes in checkThread in:
@Override
public void requestLayout() {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
void checkThread() {
if(mThread ! = Thread.currentThread()) { throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
Here you can see the specific check error is in ViewRootImpl requestLayout () method, but the ViewRootImpl is what? Why are we updating the View here? That’s where the requestLayout() method comes in.
requestLayout()
(1) If we modify a View and the result affects its size, this method is triggered. (2) From the method name, “request layout”, that is, if this method is called, then for a child View, the layout process should be redone. In reality, however, if the child View calls this method, it will measure, lay out, and draw from the View tree again, and finally display the child View’s final state.
View#requestLayout:
public void requestLayout() {
if(mMeasureCache ! = null) mMeasureCache.clear();if(mAttachInfo ! = null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logicif 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; } / / for the current view tag set a PFLAG_FORCE_LAYOUT mPrivateFlags | = PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED;if(mParent ! = null && ! MParent. IsLayoutRequested ()) {/ / ask the parent container layout mParent requestLayout (). }if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
Copy the code
- in
requestLayout
In the method, the current is judged firstView
Whether the tree is laying out the process, followed by the current childView
Sets the flag bit that marks the currentView
It needs to be rearranged - Then call
mParent.requestLayout
Method, which is important because we are asking the parent for the layout, that is, calling the parentrequestLayout
Method to add the PFLAG_FORCE_LAYOUT flag bit to the parent container, which in turn calls its parent’srequestLayout
Methods, i.e.,requestLayout
Events cascade up untilDecorView
, namely the rootView
- The root
View
It’s going to be passed on toViewRootImpl
, that is to say the sonView
therequestLayout
Event, which will eventually be received and processed by ViewRootImpl
Looking at the up-passing process, the chain of responsibility pattern is used, where the event is passed up until a superior can handle the event. In this case, only ViewRootImpl can handle requestLayout events. Here we will why when updating the View if triggered requestLayout method why to ViewRootImpl. RequestLayout ().
whyActivity.onCreate
Can I update the UI in the word thread?
As mentioned above, the final error is handled by ViewRootImpl, so this is where the Activity creation process comes in. Here is a startActivity flow chart drawn by a web guru
To start an Activity, we can start with the startActivity of the Context, which is ContextImpl’s startActivity implementation, and then try to start the Activity internally with Instrumentation, which is a cross-process. It calls AMS’s startActivity method. When AMS validates your activity, it calls back to your process using ApplicationThread, which is a binder. The callback logic is done in the binder thread pool, so it needs to be switched to the UI thread via Handler H. The first message is LAUNCH_ACTIVITY, which corresponds to the handleLaunchActivity, and the Activity is created and started in this method. Here we mainly analyzes 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(); // Create an instance of the Activity class 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(); }}}Copy the code
You can see that the Activity class instance is created in performLaunchActivity and then calls the handleResumeActivity method
ActivityThread.handleResumeActivity
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); . // Call 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) { .... // create add ViewRootImplif (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
// inthis method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); }}}... }}Copy the code
Here we focus on two methods performResumeActivity and 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;
}
Copy the code
The performResumeActivity calls the Activity’s performResume() method, Here the onResume method of the Activity lifecycle is called in the callActivityOnResume() method of the mInstrumentation
#Activity.performResume
final void performResume() {
performRestart();
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
mCalled = false;
// mResumed is setby the instrumentation mInstrumentation.callActivityOnResume(this); . onPostResume(); }Copy the code
#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()); }}}}Copy the code
wm.addView(decor, l)
Wm. AddView (decor, l) finally call 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);
}
Copy the code
#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 watchingforsystem property changes. .... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); }}Copy the code
Here we finally see the creation of the ViewRootImpl. As you can see from the procedure above, the ViewRootImpl is created after the activity.onResume, This also explains why we can implement child threads in activity.onCreate or even activity.onresume to operate the UI, since viewrotimPL doesn’t do thread checking for creation at this point.