從源碼角度分析 - Activity.onCreate能夠在子線程裏更新UI麼?

咱們都知道字線程裏更新不能更新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#requestLayoutide

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.handleLaunchActiivtyui

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併爲建立不會進行線程檢查。

相關文章
相關標籤/搜索