美文网首页
从源码角度分析 - Activity.onCreate可以在子线

从源码角度分析 - Activity.onCreate可以在子线

作者: JokAr_ | 来源:发表于2019-11-30 16:46 被阅读0次

    我们都知道字线程里更新不能更新UI,否则系统会报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么?,答案是可以的。但是不是全部可以,如果子线程是立马执行的可以,若休眠了一定时间后就不可以了。 这是为什么呢?


    为什么会报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.错误?

    从上面错误信息堆栈可以看到是ViewRootImpl.requestLayout()方法里调用的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,如果修改结果影响了它的尺寸,那么就会触发这个方法。(2) 从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程。但是,真实情况略有不同,如果子View调用了这个方法,其实会从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;
            }
    
            //为当前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,也即是说子ViewrequestLayout事件,最终会被ViewRootImpl接收并得到处理

    纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl能够处理requestLayout事件。到这里我们就明白了为什么当更新View的时候如果触发了requestLayout方法为什么会到ViewRootImpl.requestLayout()处理。

    为什么 Activity.onCreate可以在字线程里更新UI?

    上面介绍到最终报错是由ViewRootImpl处理的,那么这里就涉及到了Activity的创建过程了。这里贴一个网上大佬画的startActivity流程图

    image

    Activity的启动过程,我们可以从Context的startActivity说起,其实现是ContextImpl的startActivity,然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用ams的startActivity方法,当ams校验完activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而applicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H将其切换到ui线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动。我们在这里主要分析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();
            //创建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);
                        }
                    }
    
              
                } 
    
                .....
            } 
        }
    

    这里主要关注两个方法performResumeActivitywm.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里调用了ActivityperformResume()方法,这里操作了mInstrumentationcallActivityOnResume()方法里调用了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并为创建不会进行线程检查。

    相关文章

      网友评论

          本文标题:从源码角度分析 - Activity.onCreate可以在子线

          本文链接:https://www.haomeiwen.com/subject/yywowctx.html