Android源碼分析——Activity的繪制

分析源碼鸳劳,搞清楚Activity的setContent()背后的邏輯

Activity 的setContent()流程

以前的Activity测僵,都是直接繼承Activity.java,而現(xiàn)在的Activity則基本都是繼承AppCompatActivity.java钮孵,自然setContent()是不一樣的,那么先捋一捋舊的

Activity.java

先從Activity.java開始看起。

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

可以看到款违,getWindow()方法獲取了一個(gè)自己Activity持有的Window對象的引用,再調(diào)用這個(gè)對象的setContent()群凶,之后做一個(gè)初始化流程插爹。Window類是一個(gè)抽象類:

/**
 * 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.
 */

看注釋,這大概是一個(gè)Activity所呈現(xiàn)界面的頂層Window请梢。他的實(shí)現(xiàn)類只有一個(gè)赠尾,是PhoneWindow。那么就來看看這個(gè)PhoneWindow類的setContentView()方法實(shí)現(xiàn):

    @Override
    public void setContentView(int layoutResID) {
        //首先這里有一個(gè)ContentParent毅弧,如果為空則做一個(gè)初始化
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        //根據(jù)是否需要?jiǎng)赢媮碜鲆恍┦?        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            /*
            不需要?jiǎng)赢嬈蓿苯娱_始加載布局,這里是將layoutResID布局加載到了mContentParent上
            而layoutResID是我們交給setContent()的那個(gè)布局id
            因此我們的Activity最終顯示的頁面就是加載   到了mContent上
            */
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

再看看mContentParent的定義:


    // 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.
    ViewGroup mContentParent;

可以看到够坐,這個(gè)mContentParent其實(shí)就是一個(gè)ViewGroup

所以在setContent()中主要做了兩件事:

  • 初始化一個(gè)Window持有的ContentParent(即ViewGroup)對象
  • 將布局文件加載到ContentParent上
    那么現(xiàn)在看看這個(gè)所謂的初始化過程做了什么寸宵,即installDecor():
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {//第一步,發(fā)現(xiàn)mDecor沒有初始化
            //生成一個(gè)mDecor對象元咙,并對其初始化
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            //讓mDecor獲取一個(gè)當(dāng)前window的引用
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {//第二步梯影,發(fā)現(xiàn)mContentParent沒有初始化
            //用前面的mDecor生成一個(gè)mContentParent對象并初始化
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            //在mDecor中找一下是否有一個(gè)DecorContentParent
            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);
            //有?對這個(gè)做初始化
            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                final int localFeatures = getLocalFeatures();
                for (int i = 0; i < FEATURE_MAX; i++) {
                    if ((localFeatures & (1 << i)) != 0) {
                        mDecorContentParent.initFeature(i);
                    }
                }

                mDecorContentParent.setUiOptions(mUiOptions);
            //…………
            
            } else {//沒有庶香?那么從這里開始
                //獲取一個(gè)作為title的view并初始化
                mTitleView = findViewById(R.id.title);
                if (mTitleView != null) {
                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                        final View titleContainer = findViewById(R.id.title_container);
                        if (titleContainer != null) {
                            titleContainer.setVisibility(View.GONE);
                        } else {
                            mTitleView.setVisibility(View.GONE);
                        }
                        mContentParent.setForeground(null);
                    } else {
                        mTitleView.setText(mTitle);
                    }
                }
            }
            //對這個(gè)mDecor設(shè)置背景(回調(diào))
            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(mBackgroundFallbackResource);
            }

            //之后就是一些無關(guān)緊要的東西了
    }

再看看這個(gè)mDecor是何方神圣:

 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    //…………
}

原來mDecor就是一個(gè)FrameLayout了甲棍。
那么這個(gè)初始化過程就分為了兩步:

  • 初始化mDecor(一個(gè)FrameLayout)
  • 借助mDecor初始化mContentParent

再來分別看看兩者是如何初始化的,顯示mDecor:

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().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

先是用想辦法和獲取一個(gè)context赶掖,然后再調(diào)用新的構(gòu)造器七扰,這里的featureId傳進(jìn)來的是-1颈走。然后看構(gòu)造器:


DecorView(Context context, int featureId, PhoneWindow window,
            WindowManager.LayoutParams params) {
        super(context);
        mFeatureId = featureId;

        mShowInterpolator = AnimationUtils.loadInterpolator(context,
                android.R.interpolator.linear_out_slow_in);
        mHideInterpolator = AnimationUtils.loadInterpolator(context,
                android.R.interpolator.fast_out_linear_in);

        mBarEnterExitDuration = context.getResources().getInteger(
                R.integer.dock_enter_exit_duration);
        mForceWindowDrawsStatusBarBackground = context.getResources().getBoolean(
                R.bool.config_forceWindowDrawsStatusBarBackground)
                && context.getApplicationInfo().targetSdkVersion >= N;
        mSemiTransparentStatusBarColor = context.getResources().getColor(
                R.color.system_bar_background_semi_transparent, null /* theme */);

        updateAvailableWidth();
        //前面不是有一個(gè)在發(fā)現(xiàn)mDecorView不為Null時(shí)要賦予一個(gè)當(dāng)前window引用嗎司致?這里就是在初始化完成后再做的
        setWindow(window);

        updateLogTag(params);

        mResizeShadowSize = context.getResources().getDimensionPixelSize(
                R.dimen.resize_shadow_size);
        initResizingPaints();
    }

至此一個(gè)DecorView就初始化完成了捞奕,他實(shí)際上是一個(gè)FrameLayout。接下來看看這個(gè)mContentParent是如何通過DecorView來生成的:

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //這里先拿到一些屬性
        TypedArray a = getWindowStyle();
        //…………
        //這里開始先是對每一種屬性做判斷了弄抬,比如是否懸浮孔飒?是否無標(biāo)題逾柿?等等
        //具體方法和我們寫自定義View時(shí)是一樣的,這里省略了
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        //………………
        //這里開始取出部分app相關(guān)的信息,比如targetsdk
        final Context context = getContext();
        final int targetSdk = context.getApplicationInfo().targetSdkVersion;
        //………………

        WindowManager.LayoutParams params = getAttributes();
        //這里是和高端設(shè)備相關(guān)的設(shè)置
        // Non-floating windows on high end devices must put up decor beneath the system bars and
        // therefore must know about visibility changes of those.
        if (!mIsFloating && ActivityManager.isHighEndGfx()) {
            if (!targetPreL && a.getBoolean(
                    R.styleable.Window_windowDrawsSystemBarBackgrounds,
                    false)) {
                setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                        FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
            }
            if (mDecor.mForceWindowDrawsStatusBarBackground) {
                params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
            }
        }
        
        //………………
        
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    R.styleable.Window_windowAnimationStyle, 0);
        }

        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                }
                mBackgroundFallbackResource = a.getResourceId(
                        R.styleable.Window_windowBackgroundFallback, 0);
                if (false) {
                    System.out.println("Background: "
                            + Integer.toHexString(mBackgroundResource) + " Frame: "
                            + Integer.toHexString(mFrameResource));
                }
            }
            if (mLoadElevation) {
                mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
            }
            mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
            mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
        }

        // Inflate the window decor.
        //這里開始汪疮,就來真的了
        //這個(gè)int值代表了要加載的布局的id
        int layoutResource;
        //所需的屬性
        int features = getLocalFeatures();
        //然后,根據(jù)屬性不同的需求,獲取不同的布局文件id
        // 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 {
            // Embedded, so no decoration is needed.
            //記住這個(gè)布局文件id
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        //標(biāo)識著這個(gè)decorview開始改變了
        mDecor.startChanging();
        //將剛才那個(gè)布局文件猾漫,加載到decor中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //通過findviewbyid()的方式獲取這個(gè)contentParent庸蔼,記住這個(gè)ID_ANDROID_CONTENT
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        
        //………………
        
        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        //初始化背景和標(biāo)題等等一些屬性
        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            //為decorview設(shè)置背景
            mDecor.setWindowBackground(background);

            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);

            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }

        //標(biāo)識著改變結(jié)束
        mDecor.finishChanging();
        //最后解总,返回這個(gè)contentParent 
        return contentParent;
    }

總結(jié)一下,就是給這個(gè)framelayout————DecorView設(shè)置了一種布局掏膏,然后通過findviewbyid的方式獲取一個(gè)contentparent的劳翰。那么這兩者有什么關(guān)系呢?觀察到前面提到了兩個(gè)id馒疹,聯(lián)系就在這里佳簸!所以接下來看看具體設(shè)置布局的邏輯。

首先看看加載布局:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        mStackId = getStackId();

        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

看到layoutInflater就知道了颖变,這里果然是加載layoutResource指向的那個(gè)布局生均,這里加載后為一個(gè)叫做root的View,然后通過調(diào)用addView()方法————我們知道DecorView本身是一個(gè)FrameLayout————將root加載到自己這個(gè)FrameLayout中腥刹。

接下來看看layoutResource所引用的布局R.layout.screen_simple马胧,即screen_simple.xml的內(nèi)容:

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

嗯,一個(gè)LinearLayout衔峰,包含了一個(gè)ViewStub占位和一個(gè)FrameLayout佩脊。做一個(gè)猜測蛙粘,這就是我們Activity最普通的初始界面,即一個(gè)狀態(tài)欄+一個(gè)主界面威彰。然后發(fā)現(xiàn)下面那個(gè)FrameLayout的id是content出牧,再回到剛才方法中,通過findviewbyid初始化找到contentParent的時(shí)候用的id是哪個(gè)抱冷?

/**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

根據(jù)注釋崔列,我們知道了梢褐,這個(gè)id引用的view就是我們的主布局要加載的地方旺遮,也就是在剛才那個(gè)xml文件中的FrameLayout!

到此為止盈咳,一個(gè)installDecor()的過程基本完成了耿眉,來捋一捋。

首先要初始化一個(gè)叫做DecorView的FrameLayout鱼响,他是和當(dāng)前Window息息相關(guān)的鸣剪。我們知道一個(gè)Activity,他的顯示界面這個(gè)模塊是交給了Window管理丈积,而在Window中則是交給了DeocrView筐骇。這個(gè)DecorView會(huì)根據(jù)不同的需求(主題)來給自己填上一個(gè)不同的布局。然后在加載進(jìn)來的這個(gè)布局中江滨,有一個(gè)ViewGroup是專門用來顯示我們編寫的界面的铛纬,這個(gè)ViewGroup會(huì)通過findViewById()的形式在DecorView中找到,然后交給mContentParent唬滑,這樣我們要將自己寫的布局加載進(jìn)來的時(shí)候告唆,就是直接加載到mContentParent中就可以了。

回過頭來看看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 {
            //這里可以看到晶密,前面初始化結(jié)束后擒悬,果然是將我們自己寫的布局加載到了mContentParent中!
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

至此稻艰,Activity的setContent()流程就是走完了懂牧,大致知道了布局是怎么加載進(jìn)來的。接下來看看新的AppCompatActivity是如何加載布局的

AppCompatActivity

接下來再看看AppCompatActivity是如何加載布局的
先看AppCompatActivity.java的setContentView()方法:


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

這里通過getDelegate()方法獲取了一個(gè)對象的引用尊勿,再調(diào)用他的setContentView()方法归苍,相當(dāng)于做了一個(gè)代理。
那么現(xiàn)在問題拆分為兩步:

  • 代理的對象是如何創(chuàng)建的
  • 代理對象的setContentView()是如何執(zhí)行的

先看第一個(gè)問題:


/**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
    

這里用來做代理的运怖,是一個(gè)AppCompatDelegate對象拼弃,叫mDelegate,他是通過一個(gè)靜態(tài)方法create()創(chuàng)建的摇展,那么先看看這個(gè)類是什么:

 <p>An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance,
 * therefore the instance returned from {@link #create(Activity, AppCompatCallback)} should be
 * retained until the Activity is destroyed.</p>

再來看看他的create()方法:

/**
     * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
     *
     * @param callback An optional callback for AppCompat specific events
     */
    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }
private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

可以看到吻氧,這里最終是根據(jù)不同的sdk版本來創(chuàng)建不同的AppCompatDelegateImplxxx對象,分別點(diǎn)進(jìn)去看看后會(huì)發(fā)現(xiàn),最終都是到了AppCompatDelegateImplV9.java盯孙,然后:

class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
AppCompatDelegateImplV9(Context context, Window window, AppCompatCallback callback) {
        super(context, window, callback);
    }

所以最終是調(diào)用了父類的構(gòu)造器:

AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) {
        mContext = context;
        mWindow = window;
        mAppCompatCallback = callback;

        mOriginalWindowCallback = mWindow.getCallback();
        if (mOriginalWindowCallback instanceof AppCompatWindowCallbackBase) {
            throw new IllegalStateException(
                    "AppCompat has already installed itself into the Window");
        }
        mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback);
        // Now install the new callback
        mWindow.setCallback(mAppCompatWindowCallback);

        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
                context, null, sWindowBackgroundStyleable);
        final Drawable winBg = a.getDrawableIfKnown(0);
        if (winBg != null) {
            mWindow.setBackgroundDrawable(winBg);
        }
        a.recycle();
    }

這樣就完成了鲁森。接下來看看setContentView()是如何執(zhí)行的。
進(jìn)入AppCompatDelegateImplV9.java的setContentView():

 @Override
    public void setContentView(int resId) {
        //確保創(chuàng)建一個(gè)SubDecor
        ensureSubDecor();
        //通過findviewbyid的方式找到android.R.id.contentd代表的view振惰,作為一個(gè)contentParent
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        //清空
        contentParent.removeAllViews();
        //將我們自己的布局文件加載到這個(gè)contentParent中
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

再看看這個(gè)SubDecor是什么:


// true if we have installed a window sub-decor layout.
    private boolean mSubDecorInstalled;
    private ViewGroup mSubDecor;
    

所以我們自己寫的布局文件最終是被加載到了一個(gè)id為content的ViewGroup上歌溉,而這個(gè)ViewGroup是通過subDecor來找到的,而這個(gè)SubDecor也是一個(gè)ViewGroup骑晶。那么重點(diǎn)就是ensureSubDecor()了痛垛,他的作用應(yīng)該就是初始化一個(gè)SubDecor了:

private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            //創(chuàng)建一個(gè)SubDecor
            mSubDecor = createSubDecor();

            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                onTitleChanged(title);
            }

            applyFixedSizeWindow();
            //做一個(gè)install?
            onSubDecorInstalled(mSubDecor);
            //標(biāo)識已經(jīng)installed
            mSubDecorInstalled = true;

            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!isDestroyed() && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }

現(xiàn)在就分為了兩步:

  • mSubDecor是如何被創(chuàng)建的
  • 創(chuàng)建成功之后做了什么

第一個(gè)問題桶蛔,看createSubDecor()方法:


private ViewGroup createSubDecor() {
        //和自定義View時(shí)獲取屬性類似匙头,這兒是從AppCompatTheme獲取了屬性
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        //這里判斷如果沒有加這個(gè)屬性的話會(huì)拋出異常
        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }
        //接下來就是普通的挨個(gè)遍歷屬性了
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();
        
        // Now let's make sure that the Window has installed its decor by retrieving it
        //這里通過Window對象(即PhoneWindow)調(diào)用了getDecorView()方法,猜測是獲取Decor
        //這里是重點(diǎn)仔雷,待會(huì)兒分析
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        //創(chuàng)建了一個(gè)subDecor引用蹂析,還未實(shí)例化
        ViewGroup subDecor = null;

        //根據(jù)不同需求,讓subDecor 裝載不同的布局
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);

                // Floating windows can never have an action bar, reset the flags
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
                /**
                 * This needs some explanation. As we can not use the android:theme attribute
                 * pre-L, we emulate it by manually creating a LayoutInflater using a
                 * ContextThemeWrapper pointing to actionBarTheme.
                 */
                TypedValue outValue = new TypedValue();
                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

                Context themedContext;
                if (outValue.resourceId != 0) {
                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
                } else {
                    themedContext = mContext;
                }

                // Now inflate the view using the themed context and set it as the content view
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);

                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());

                /**
                 * Propagate features to DecorContentParent
                 */
                if (mOverlayActionBar) {
                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
                }
                if (mFeatureProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
                }
                if (mFeatureIndeterminateProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
                }
            }
        } else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }

            if (Build.VERSION.SDK_INT >= 21) {
                // If we're running on L or above, we can rely on ViewCompat's
                // setOnApplyWindowInsetsListener
                ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                        new OnApplyWindowInsetsListener() {
                            @Override
                            public WindowInsetsCompat onApplyWindowInsets(View v,
                                    WindowInsetsCompat insets) {
                                final int top = insets.getSystemWindowInsetTop();
                                final int newTop = updateStatusGuard(top);

                                if (top != newTop) {
                                    insets = insets.replaceSystemWindowInsets(
                                            insets.getSystemWindowInsetLeft(),
                                            newTop,
                                            insets.getSystemWindowInsetRight(),
                                            insets.getSystemWindowInsetBottom());
                                }

                                // Now apply the insets on our view
                                return ViewCompat.onApplyWindowInsets(v, insets);
                            }
                        });
            } else {
                // Else, we need to use our own FitWindowsViewGroup handling
                ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                        new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                            @Override
                            public void onFitSystemWindows(Rect insets) {
                                insets.top = updateStatusGuard(insets.top);
                            }
                        });
            }
        }
        
        //到此為止碟婆,subDecor算是實(shí)例化完畢了

        if (subDecor == null) {
            throw new IllegalArgumentException(
                    "AppCompat does not support the current theme features: { "
                            + "windowActionBar: " + mHasActionBar
                            + ", windowActionBarOverlay: "+ mOverlayActionBar
                            + ", android:windowIsFloating: " + mIsFloating
                            + ", windowActionModeOverlay: " + mOverlayActionMode
                            + ", windowNoTitle: " + mWindowNoTitle
                            + " }");
        }

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }

        // Make the decor optionally fit system windows, like the window's decor
        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
        //這里開始重點(diǎn)來了
        
        //從subDecor中拿到了一個(gè)ContentFrameLayout,注意id為R.id.action_bar_activity_content
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        //從window中拿到一個(gè)id為content的ViewGroup
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            // 這里电抚,依次從window中那個(gè)viewgroup中取出子View
            //然后將他們放入那個(gè)從subDecor中拿到的Content中
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            // Change our content FrameLayout to use the android.R.id.content id.
            // Useful for fragments.
            //全部挪完之后,給原來window中的那個(gè)ViewGroup把id值為NO_ID
            windowContentView.setId(View.NO_ID);
            //然后偷梁換柱竖共,把那個(gè)ContentFrameLayout的id設(shè)為了content
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            //把那個(gè)背景也去掉了
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }

        // Now set the Window's content view with the decor
        //貍貓換太子蝙叛,直接把subDecor給了Window
        mWindow.setContentView(subDecor);

        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });

        return subDecor;
    }

再來看看那個(gè)重點(diǎn)標(biāo)記的方法:

@Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

哦?原來window的getDecorView()方法其實(shí)就是前面提到過的installDecor()方法誒肘迎!之前說過甥温,installDecor()方法是什么作用來著?

首先要初始化一個(gè)叫做DecorView的FrameLayout妓布,他是和當(dāng)前Window息息相關(guān)的姻蚓。我們知道一個(gè)Activity,他的顯示界面這個(gè)模塊是交給了Window管理匣沼,而在Window中則是交給了DeocrView狰挡。這個(gè)DecorView會(huì)根據(jù)不同的需求(主題)來給自己填上一個(gè)不同的布局。然后在加載進(jìn)來的這個(gè)布局中释涛,有一個(gè)ViewGroup是專門用來顯示我們編寫的界面的加叁,這個(gè)ViewGroup會(huì)通過findViewById()的形式在DecorView中找到,然后交給mContentParent唇撬,這樣我們要將自己寫的布局加載進(jìn)來的時(shí)候它匕,就是直接加載到mContentParent中就可以了。

馬上接觸到真相了窖认,再隨便找個(gè)剛才所引用到的布局文件看看豫柬,比如R.layout.abc_screen_simple:

<android.support.v7.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <android.support.v7.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <include layout="@layout/abc_screen_content_include" />

</android.support.v7.widget.FitWindowsLinearLayout>

還有abc_screen_content_include.xml:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v7.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>

看看那個(gè)id:action_bar_activity_content告希,而且他還是很個(gè)ContentFrameLayout 發(fā)現(xiàn)沒有?這里的SubDecorView和以前的DecorView邏輯是很像的烧给!都是以自己作為一個(gè)大的ViewGroup燕偶,里面放另一個(gè)小ViewGroup,在這個(gè)小ViewGroup中础嫡,還有一個(gè)ViewGroup作為根布局指么。

捋一捋剛才的流程:

  • 首先創(chuàng)建了兩個(gè)DecorView,一個(gè)就是以前Activity直接用的那個(gè)DecorView榴鼎,另一個(gè)叫做SubDecorView
  • 將舊DecorView的content內(nèi)容交給SubDecorView的content
  • 將SubDecorView作為一個(gè)整體伯诬,交給DecorView

總之,就是一個(gè)替換的過程檬贰。

再回到前面看看:

 @Override
    public void setContentView(int resId) {
        //這里做了剛才所說的一切姑廉,現(xiàn)在是兩個(gè)DecorView嵌套起來了
        ensureSubDecor();
        //id為content的ViewGroup現(xiàn)在的內(nèi)容其實(shí)就是以前的DecorView用的那個(gè)
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        //清空
        contentParent.removeAllViews();
        //將我們自己的布局文件加載到這個(gè)contentParent中
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

至此缺亮,Activity的AppCompatActivity的setContent()的流程都分析完了翁涤,總結(jié)一下:

  • 一個(gè)Activity,有很多功能萌踱,其中的“展示東西給別人看”這個(gè)功能葵礼,是交給自己的一個(gè)Window對象來管理的。
  • Window包含一個(gè)ViewGroup并鸵,作為根ViewGroup
  • 根ViewGroup鸳粉,根據(jù)不同的需求(即主題定義等)會(huì)加載不同的布局文件
  • 以最基本的一種布局來講,他包含一個(gè)title和一個(gè)content
  • 我們setContent()時(shí)傳入的布局文件id所指向的那個(gè)布局文件园担,會(huì)被加載到這個(gè)content中
  • Activity和AppCompatActivity在這里的區(qū)別在于届谈,Activity單純的用一個(gè)DecorView,AppCompatActivity則是在原來的基礎(chǔ)上弯汰,加了一個(gè)SubDeocrView艰山,將舊的DecorView的內(nèi)容放到SubDecorView的content中,然后將SubDecorView作為整體放入舊的DecorView的content中咏闪,也就是說曙搬,一個(gè)DecorView包裹著一個(gè)SubDecorView
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸽嫂,隨后出現(xiàn)的幾起案子纵装,更是在濱河造成了極大的恐慌,老刑警劉巖据某,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橡娄,死亡現(xiàn)場離奇詭異,居然都是意外死亡癣籽,警方通過查閱死者的電腦和手機(jī)挽唉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門扳还,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橱夭,你說我怎么就攤上這事氨距。” “怎么了棘劣?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵俏让,是天一觀的道長。 經(jīng)常有香客問我茬暇,道長首昔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任糙俗,我火速辦了婚禮勒奇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巧骚。我一直安慰自己赊颠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布劈彪。 她就那樣靜靜地躺著竣蹦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沧奴。 梳的紋絲不亂的頭發(fā)上痘括,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音滔吠,去河邊找鬼纲菌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疮绷,可吹牛的內(nèi)容都是我干的翰舌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼矗愧,長吁一口氣:“原來是場噩夢啊……” “哼灶芝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唉韭,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤夜涕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后属愤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體女器,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年住诸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驾胆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涣澡。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丧诺,靈堂內(nèi)的尸體忽然破棺而出入桂,到底是詐尸還是另有隱情,我是刑警寧澤驳阎,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布抗愁,位于F島的核電站,受9級特大地震影響呵晚,放射性物質(zhì)發(fā)生泄漏蜘腌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一饵隙、第九天 我趴在偏房一處隱蔽的房頂上張望撮珠。 院中可真熱鬧,春花似錦金矛、人聲如沸芯急。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽志于。三九已至涮因,卻和暖如春废睦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背养泡。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工嗜湃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人澜掩。 一個(gè)月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓购披,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肩榕。 傳聞我的和親對象是個(gè)殘疾皇子刚陡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容