Android高階UI系列(2)-DecorView

lerry發表於2017-05-11

上一章我們講解了我們的檢視是如何顯示到介面上的,這一章我們來講講View是如何新增到window上的,換句話說就是我們的DecorView是如何新增到Window上的。

同樣,我們先帶著疑問:
我們的DecorView是怎麼新增到Window上的呢?

這個問題我們要先從Activity的啟動開始講起。我們這裡從Java層面開始,不考慮其他層面。

Activity的啟動時通過ActivityThread來進行發起的。它會呼叫自己的
handleLaunchActivity()方法,

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        if (r.profilerInfo != null) {
            mProfiler.setProfiler(r.profilerInfo);
            mProfiler.startProfiling();
        }

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

        if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        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) {
                // The activity manager actually wants this one to start out paused, because it
                // needs to be visible but isn't in the foreground. We accomplish this by going
                // through the normal startup (because activities expect to go through onResume()
                // the first time they run, before their window is displayed), and then pausing it.
                // However, in this case we do -not- need to do the full pause cycle (of freezing
                // and such) because the activity manager assumes it can just retain the current
                // state it has.
                performPauseActivityIfNeeded(r, reason);

                // We need to keep around the original state, in case we need to be created again.
                // But we only do this for pre-Honeycomb apps, which always save their state when
                // pausing, so we can not have them save their state when restarting from a paused
                // state. For HC and later, we want to (and can) let the state be saved as the
                // normal part of stopping the activity.
                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
            }
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }複製程式碼

在這個方法中它首先會呼叫performLaunchActivity()。好, 我們看這個方法:

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         //省略部分程式碼
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
                    ...
                    } catch (Exception e) {
            ...
        }

        try {
            ...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);

               ...
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
                r.stopped = true;
                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                if (!r.activity.mFinished) {
                    if (r.isPersistable()) {
                        if (r.state != null || r.persistentState != null) {
                            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                        }
                    } else if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
                }
                if (!r.activity.mFinished) {
                    activity.mCalled = false;
                    if (r.isPersistable()) {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state,
                                r.persistentState);
                    } else {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state);
                    }
                    if (!activity.mCalled) {
                        throw new SuperNotCalledException(
                            "Activity " + r.intent.getComponent().toShortString() +
                            " did not call through to super.onPostCreate()");
                    }
                }
            }
            r.paused = true;

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }複製程式碼

首先,它會通過反射獲取Activity,然後他會呼叫我們Activity的attach()方法,而我們的PhoneWindow是在Activity的attach()方法中初始化的,大家可以自己檢視原始碼。因此,初始化的過程都是在Activity中發起呼叫的。初始化完成之後, 它會呼叫callActivityOnCreate(),這裡就是會呼叫activity的,也就是說我們Activity的宣告週期,執行過程,都是在我們ActivityThread中一次執行的,大家可以自己往後看,生命週期次序都有。

看完了performLaunchActivity ()方法,我們回到handleLaunchActivity (),接著往下看,它會執行handleResumeActivity(),我們看方法名,就可以猜到它肯定會去呼叫resume方法吧。

它的流程大概就是handleResumeActivity()->performResumeActivity()->activity.performResume()

  final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
            ...
            //第一步            
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);

        ...
        //第二步
          r.window = r.activity.getWindow();
          View decor = r.window.getDecorView();
          ViewManager wm = a.getWindowManager();
          WindowManager.LayoutParams l = r.window.getAttributes();
          //第三步
          wm.addView(decor, l);  
          ...
            }複製程式碼

好,我們繼續看handleResumeActivity (),他會去拿window,decor,還有WindowManager,然後把我們Decor新增到我們WindowManager裡面去。那這個windowManager是什麼呢,我們點進去一看是個介面,那我們要找它的實現類,它是實現類是WindowManagerImpl。所以我們要看它的addView()方法。

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

@Override
    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) {
                //繪製流程真正發起點
              ViewRootImpl root;
            ...
            root = new ViewRootImpl(view.getContext(), display);

             mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            ...
             root.setView(view, wparams, panelParentView);
            }複製程式碼

它首先建立一個ViewRootImpl,然後把我們的Decor,viewrootImp,和一些引數都儲存到了這個Global類中,也就是說,我們的WindowManagerGlobal是用來管理ViewRoot和DecorView的。接下來他會把我們的DecorView設定給ViewRoot,這就是這篇文章最關鍵的一個方法了。我們點進去看ViewRoot的setView()方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//將我們的DecorView賦值給mView,因此下面的mView就是DecorView
              mView = view;
  ...
   // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
         requestLayout();
         ...
         //發起了跨程式訊息
          res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
          ...
          //給DecorView設定parent,我們view呼叫requsetLayout的時候,其實呼叫的都是viewrootImp的requestLayout
          view.assignParent(this);
}複製程式碼

首先會將我們的DecorView賦值給mView,因此下面的mView就是DecorView,這個mView這裡沒用到,下面的UI遍歷會用到。我們先來看viewRoot的requestLayout()中的scheduleTraversals();

 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }


  void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }複製程式碼

這裡開啟了一個runnable,我們點進去看

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }複製程式碼

而doTraversal()中有一個方法performTraversals(),這個相信只要是看過UI繪製的部落格的人應該都很熟悉這個方法吧,這個方法就是真正執行整個UI繪製的遍歷過程。

private void performTraversals() {
        ...
// cache mView since it is used so much below...
        final View host = mView;
        ...
 // Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
         performLayout(lp, mWidth, mHeight);
        ...
         performDraw();

}複製程式碼

這篇文章不僅僅講了DecorView是如何新增到Window的,還梳理了一遍View的繪製發起點,雖然篇幅較短,但也費了些心思。

下一篇章我們真正開始DecorView的繪製流程講解。下篇文章計劃將結合《Android開發藝術探索》和其他相關部落格,並加上個人的理解,總結出一篇希望能幫助大家理解UI繪製流程的文章,盡請期待!

由於個人水平有限,文章中可能會出現錯誤,如果你覺得哪一部分有錯誤,或者發現了錯別字等內容,歡迎在評論區告訴我。

望共勉。

參考

騰訊課堂

本文為貓舍原創作品,轉載請註明原文出處: imlerry.com/2017/05/11/…

相關文章