Viewペイントシステム知識整理(2)-setContentViewソース解析

11706 ワード

一、概説

Activityでは、レイアウトを初期化するためにsetContentViewメソッドを呼び出すのが一般的です.

二、ContentViewに関する方法

Activityでは、ContentViewに関連する関数は次のとおりです.まず、それらの注釈の説明を見てみましょう.
    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    /**
     * Set the activity content to an explicit view.  This view is placed
     * directly into the activity's view hierarchy.  It can itself be a complex
     * view hierarchy.  When calling this method, the layout parameters of the
     * specified view are ignored.  Both the width and the height of the view are
     * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
     * your own layout parameters, invoke
     * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
     * instead.
     *
     * @param view The desired content to display.
     *
     * @see #setContentView(int)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

    /**
     * Set the activity content to an explicit view.  This view is placed
     * directly into the activity's view hierarchy.  It can itself be a complex
     * view hierarchy.
     *
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(int)
     */
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }

    /**
     * Add an additional content view to the activity.  Added after any existing
     * ones in the activity -- existing views are NOT removed.
     *
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     */
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().addContentView(view, params);
        initWindowDecorActionBar();
    }

上記の注記から、この4つの方法の用途がわかります.
  • 1つ目:対応するレイアウトをレンダリングlayouResIdのトップレベルactivityに追加します.
  • 第2:ViewViewのレイアウトに追加します.デフォルトの幅はactivityです.
  • 第3種:上記と同じですがViewGroup.LayoutParams#MATCH_PARENTが指定されています.
  • 第4種類:コンテンツを追加し、LayoutParamsを指定する必要があります.既存のLayoutParamsは削除されません.

  • これらの4つの方法はいずれもViewを呼び出した方法であり、ソースコードによってPhoneWindow.javasetContentView(View view, ViewGroup.LayoutParams params)のステップは基本的に同じであることが分かるが、レイアウトに追加する際、前者はsetContentView(@LayoutRes int layoutResID)の例を得たため、Viewの方法を用い、後者は先にaddViewを用いる必要があるため、inflateを用いる.

    三、LayoutInflater方法


    次に、setContentViewを例に、具体的な実装手順を見てみましょう.

    3.1 setContentView(@LayoutRes int layoutResID)

        @Override
        public void setContentView(int layoutResID) {
            // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
            // decor, when theme attributes and the like are crystalized. Do not check the feature
            // before this happens.
            if (mContentParent == null) {
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    まず、setContentViewが空であるかどうかを判断します.追加されたコードから、このmContentParentは実はmContentParentが最後にレンダリングされたレイアウトに対応する親コンテナであり、このlayoutResIdが空である場合、ContentParentが呼び出され、installDecorが中で初期化されていることがわかります.

    3.2 mContentParent

        private void installDecor() {
            // DecorView , , FrameLayout。
            if (mDecor == null) {
                mDecor = generateDecor();
            }
            // `ContentParent` , , `DecorView`
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
                final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent);
                if (decorContentParent != null) {
                    mDecorContentParent = decorContentParent;
                }
            }
        }
    
    installDecor()mDecorであり、FrameLayoutとの関係はmContentParentによって生成されることが分かる.

    3.3 mContentParent = generateLayout(mDecor)

        protected ViewGroup generateLayout(DecorView decor) {
            //... , `layoutResource` .
            View in = mLayoutInflater.inflate(layoutResource, null);
            decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            mContentRoot = (ViewGroup) in;
            ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
            //...
            return contentParent;
        }
    

    上記の値を割り当てる過程で、私たちは主に以下のいくつかの変数に注目します.generateLayout(DecorView decor):
  • mContentRoot/mContentParent/mDecorContentは必ずmContentRootの次のサブコンテナです.
  • mDecormContentParentのうちmDecoridR.id.contentであるが、ViewGroupとの具体的な階層関係は不確定であり、mDecorがどのmContentRootでレンダリングされるかに依存する.
  • xmlは必ず入ってきたmContentParentが行ってlayoutResIdが完成した後の親容器で、それはきっと空ではありません.そうしないと異常を投げ出します.私たちinflate方法が入ってきたレイアウトは、その子setContentView(xxx)です.
  •     // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;
    
        // This is the view in which the window contents are placed. It is either
        // mDecor itself, or a child of mDecor where the contents go.
        private ViewGroup mContentParent;
    
  • ViewmDecorContentのうちmDecoriddecor_content_parentであるが、ViewGroupの中にこのmDecoridがない可能性もあり、これは私たちのViewとどのmContentRootを使用してxmlを使用したのかに依存する必要がある.

  • さらに前のinflateの場所に戻って、下を見続け、setContentViewが空でない場合、その下のすべてのサブmContentParentが除去されます.その後、Viewメソッドが呼び出され、受信したmLayoutInflater.inflate(layoutResID, mContentParent);Viewに追加され、最後にリスニングがコールバックされます.
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    

    3.4 mContentParentフラグビット

    mContentParentExplicitlySetの最後に、setContentViewという変数をmContentParentExplicitlySetに設定します.この変数は実はtrueに使用されます.つまり、requestFeatureを呼び出す前にsetContentViewを呼び出さなければなりません.そうしないと、次の異常が放出されます.
        @Override
        public boolean requestFeature(int featureId) {
            if (mContentParentExplicitlySet) {
                throw new AndroidRuntimeException("requestFeature() must be called before adding content");
            }
            return super.requestFeature(featureId);
        }
    

    したがって、requestFeatureは、requestFeature(xxx)を呼び出す前に呼び出さなければならない.

    三、setContentView(xxx)


    次に、addContentView(View view, ViewGroup.LayoutParams params)メソッドを見てみましょう.
        @Override
        public void addContentView(View view, ViewGroup.LayoutParams params) {
            if (mContentParent == null) {
                installDecor();
            }
            mContentParent.addView(view, params);
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
        }
    

    これとaddContentViewメソッドの違いは、setに追加する前に、mContentParentのすべてのサブmContentParentを削除するのではなく、それを直接追加することであり、レイアウト分析ソフトウェアを通じて、ViewのタイプはmContentParentであり、実際にはContentFrameLayoutであるため、FrameLayoutの既存のサブmContentParentに上書きされることがわかります.

    四、追加したレイアウトとViewのActivityを関連付ける


    上記の解析では、Windowを初期化し、設定されたDecorView属性に基づいて、入力されたStyleに基づいてサブレイアウトを初期化しただけであるが、この場合にはContentViewActivityと本当に関連付けられ、関連付けられた場所はWindowにある.
    final void handleResumeActivity(IBinder token,
                boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
            r = performResumeActivity(token, clearHide, reason);
            if (r != null) {
                if (r.window == null && !a.mFinished && willBeVisible) {
                    r.window = r.activity.getWindow();
                    View decor = r.window.getDecorView();
                    decor.setVisibility(View.INVISIBLE);
                    ViewManager wm = a.getWindowManager();
                    WindowManager.LayoutParams l = r.window.getAttributes();
                    a.mDecor = decor;
                    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                    l.softInputMode |= forwardBit;
                    if (r.mPreserveWindow) {
                        a.mWindowAdded = true;
                        r.mPreserveWindow = false;
                        // Normally the ViewRoot sets up callbacks with the Activity
                        // in addView->ViewRootImpl#setView. If we are instead reusing
                        // the decor view we have to notify the view root that the
                        // callbacks may have changed.
                        ViewRootImpl impl = decor.getViewRootImpl();
                        if (impl != null) {
                            impl.notifyChildRebuilt();
                        }
                    }
                    if (a.mVisibleFromClient && !a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    }
                } else if (!willBeVisible) {
                    if (localLOGV) Slog.v(
                        TAG, "Launch " + r + " mStartedActivity set");
                    r.hideForNow = true;
                }
            } else {
    
            }
        }
    

    ソースコードから分かるように、ActivityThread.javaが実行されたときに、以前handleResumeActivityDecorViewに追加されなかった場合、その最初の追加はWindowManagerメソッドが実行された後に追加される.