setContentView 加載過程分析

想必你們都知道在 Activity 的 onCreate 經過 setContentView(R.layout.xxx) ,而後就能本身編寫界面被加載顯示啦! 卻對它源碼是如何實現的不得而知吧! 這篇文章就是對 setContentView 的分析。android

人狠話很少,直接進入主題。來看咱們的 Activity 的 onCreate 方法,windows

public class MainActivity extends Activity {

   @SuppressLint("ClickableViewAccessibility")
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

   }
}
複製代碼

請注意,我這裏是繼承自 Activity 而沒有繼承 AppCompatActivity。這是谷歌在後續增長新功能引入的,在這裏爲了先分析簡化的版本,分析完了再去分析 AppCompatActivity 額外作了那些工做。bash

點擊 setContentView 進入到 Activity 的 setContentView。app

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
複製代碼

能夠看到第一行代碼 getWindow(), 這個方法返回的什麼呢?跟進去看看。ide

public Window getWindow() {
        return mWindow;
    }

複製代碼

返回的是一個 mWindow, 這個 mWindow 又是什麼呢?能夠看到它是 Window 對象。Window 又是又是一個抽象類以下:佈局

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window { 
.....
}

複製代碼

從註釋中能夠看到,它有惟一的實現類 PhoneWindow,說明 setContentView 是在 PhoneWindow 實現的。post

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

        // 當一個 Activity 的 onCreate 方法被調用時,mContentParent 爲空。
        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 {

          // 這個方法也是很是重要,它是將咱們佈局中的寫的組件,解析並加載到  mContentParent 中, 會分單獨的文章來說,見佈局中的 xml 文件中的加載過程
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
複製代碼

來看 installDecor 作了什麼?ui

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
           //  這個方法用來建立 mDecor
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
  
        if (mContentParent == null) {
            // 生成 mContentParent
            mContentParent = generateLayout(mDecor);

            ..... 省略了部分代碼
        }
    }

複製代碼

先看 mDecor 的建立,mDecor 是 DecorView 的實例。generateDecor 方法就建立出 mDecor 而已。this

protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }

  // 建立 DecorView 返回
        return new DecorView(context, featureId, this, getAttributes());
    }

複製代碼

mDecor 以及被實例化了,接下來就是看 generateLayout 方法。前方高能,代碼多到懷疑人生,不過咱們只找咱們關係的部分。分析源碼就是這個過程,若是要每行都看懂。呵呵噠,不存在的,若是追求每一行都看懂,最終就會入門到放棄。spa

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();
          
        .....  省略部分代碼, 這部分代碼主要是 window 屬性的設置,好比 FEATURE_NO_TITLE,
FEATURE_ACTION_BAR 咱們比較熟悉的

       // 看到這個註釋了麼,告訴咱們開始填充 decor
        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

       
        mDecor.startChanging();
        // 看到這個方法,或許就該慶賀了,由於要結束啦。關鍵的是  layoutResource 這是啥?
       // 其實從上面一段代碼中能夠看到賦值的地方有好幾處, 這裏咱們以 layoutResource = R.layout.screen_simple; 爲例。其實說白了就是將這個佈局做爲 DecorView 的根視圖。
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

  //  在 R.layout.screen_simple 中有一個 ID 名爲 ID_ANDROID_CONTENT, 其實值爲:  com.android.internal.R.id.content, 這個時候就獲得了 contentParent。
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        ...... 省略部分代碼
        mDecor.finishChanging();

      // 將建立好的 contentParent 返回
        return contentParent;
    }
複製代碼

咱們能夠舉個例子來看看系統提供的佈局,在源碼中 res/layout/ 找到,是否是發現前面的 content 的 ID, 它是一個 FrameLayout 容器。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

複製代碼

最終返回到 PhoneWindow 的 setContentView 的方法,再看一遍:

@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 {

           // 到了這兒,又將咱們在 Activity 調用 setContentView 的佈局填充到 mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

複製代碼

好像很簡單吧!確實不難。經過 mLayoutInflater.inflate(layoutResID, mContentParent) 將咱們的佈局添加進去。給出它的層次圖1-1

1-1

經過繼承 Activity 設置 setContentView 分析完畢。接下來就是來看繼承 AppCompatActivity 又作了什麼?首先將以前繼承自 Activity 的改成 AppCompatActivity, 點擊 setContentView 進入源碼,會看到以下的方法:

public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
    }

複製代碼

這裏的 getDelegate 是引入代理類來實現 setContentView 邏輯,最終到 AppCompatDelegateImpl 的 setContentView 方法。

public void setContentView(int resId) {
        this.ensureSubDecor();
        // 因爲下載這部分源碼,若是隻能看到 16908290 這樣的數字,不過沒關係,問題不大 
        // 只需知道 mSubDecor 幹了什麼便可推理出來。
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }

複製代碼

留意到第一方代碼了嗎? this.ensureSubDecor(); 進去看看。

private void ensureSubDecor() {
        if (!this.mSubDecorInstalled) {
          // 來看看怎麼建立的 mSubDecor, 這個就是咱們想要知道的。
            this.mSubDecor = this.createSubDecor();
        
            CharSequence title = this.getTitle();
            if (!TextUtils.isEmpty(title)) {
                if (this.mDecorContentParent != null) {
                    this.mDecorContentParent.setWindowTitle(title);
                } else if (this.peekSupportActionBar() != null) {
                    this.peekSupportActionBar().setWindowTitle(title);
                } else if (this.mTitleView != null) {
                    this.mTitleView.setText(title);
                }
            }

            this.applyFixedSizeWindow();
            this.onSubDecorInstalled(this.mSubDecor);
            this.mSubDecorInstalled = true;
            AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
            if (!this.mIsDestroyed && (st == null || st.menu == null)) {
                this.invalidatePanelMenu(108);
            }
        }
    }

複製代碼

createSubDecor 這個方也很長啊,怎麼感受咱們在前面是否是看到過,其實和前面看到的原理很像。

private ViewGroup createSubDecor() {
        TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
        if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        } else {
            if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {
                this.requestWindowFeature(1);
            } else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, false)) {
                this.requestWindowFeature(108);
            }

            if (a.getBoolean(styleable.AppCompatTheme_windowActionBarOverlay, false)) {
                this.requestWindowFeature(109);
            }

            if (a.getBoolean(styleable.AppCompatTheme_windowActionModeOverlay, false)) {
                this.requestWindowFeature(10);
            }

            this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
            a.recycle();
            this.mWindow.getDecorView();
            LayoutInflater inflater = LayoutInflater.from(this.mContext);

          // subDecor 等着被賦值.
            ViewGroup subDecor = null;

  //  選擇沒有標題的主題
            if (!this.mWindowNoTitle) {
                if (this.mIsFloating) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
                    this.mHasActionBar = this.mOverlayActionBar = false;
                } else if (this.mHasActionBar) {
                    TypedValue outValue = new TypedValue();
                    this.mContext.getTheme().resolveAttribute(attr.actionBarTheme, outValue, true);
                    Object themedContext;
                    if (outValue.resourceId != 0) {
                        themedContext = new ContextThemeWrapper(this.mContext, outValue.resourceId);
                    } else {
                        themedContext = this.mContext;
                    }

                    subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
                    this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent);
                    this.mDecorContentParent.setWindowCallback(this.getWindowCallback());
                    if (this.mOverlayActionBar) {
                        this.mDecorContentParent.initFeature(109);
                    }

                    if (this.mFeatureProgress) {
                        this.mDecorContentParent.initFeature(2);
                    }

                    if (this.mFeatureIndeterminateProgress) {
                        this.mDecorContentParent.initFeature(5);
                    }
                }
            } else {  // 有標題的主題
                if (this.mOverlayActionMode) {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
                } else {
                    subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
                }

                if (VERSION.SDK_INT >= 21) {
                    ViewCompat.setOnApplyWindowInsetsListener(subDecor, new OnApplyWindowInsetsListener() {
                        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                            int top = insets.getSystemWindowInsetTop();
                            int newTop = AppCompatDelegateImpl.this.updateStatusGuard(top);
                            if (top != newTop) {
                                insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
                            }

                            return ViewCompat.onApplyWindowInsets(v, insets);
                        }
                    });
                } else {
                    ((FitWindowsViewGroup)subDecor).setOnFitSystemWindowsListener(new OnFitSystemWindowsListener() {
                        public void onFitSystemWindows(Rect insets) {
                            insets.top = AppCompatDelegateImpl.this.updateStatusGuard(insets.top);
                        }
                    });
                }
            }


      // 到這兒 subDecor 就應該不爲空,不然 Activity 就會啓動失敗,由於沒有找到相關主題。
            if (subDecor == null) {
                throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
            } else {
                if (this.mDecorContentParent == null) {
                    this.mTitleView = (TextView)subDecor.findViewById(id.title);
                }

                ViewUtils.makeOptionalFitsSystemWindows(subDecor);

            //  找到 subDecor 的內容 contentView
                ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);

               // 各位,看到這兒明白什麼了嗎? 這個方法找到的是咱們前面的 DecorView, 而後將它添加到 contentView 中,就是在外面套了一層 AppCompatActivity 的主題。
                ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
                if (windowContentView != null) {
                    while(windowContentView.getChildCount() > 0) {
                        View child = windowContentView.getChildAt(0);
                        windowContentView.removeViewAt(0);
                        contentView.addView(child);
                    }

                    windowContentView.setId(-1);
                    contentView.setId(16908290);
                    if (windowContentView instanceof FrameLayout) {
                        ((FrameLayout)windowContentView).setForeground((Drawable)null);
                    }
                }

              // 偷樑換柱手法,將本身注入到原來的體系中,即多了一層 subDecor.
                this.mWindow.setContentView(subDecor);
                contentView.setAttachListener(new OnAttachListener() {
                    public void onAttachedFromWindow() {
                    }

                    public void onDetachedFromWindow() {
                        AppCompatDelegateImpl.this.dismissPopups();
                    }
                });
                return subDecor;
            }
        }
    }

複製代碼

然咱們再回到 AppDelegateImpl 的 setContentView 中 contentParent 是不該該知道是什麼了吧! 接下來的就是將咱們在 Activity 設置的佈局文件加載到 contentParent 中。

public void setContentView(int resId) {
        this.ensureSubDecor();
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
    }


複製代碼

總結一下, 其實 AppCompatActivity 只是在 Activity 外面套了直接的一層主題。來個最終層次圖吧!

1-2
相關文章
相關標籤/搜索