Android源码分析——Activity的绘制

分析源码,搞清楚Activity的setContent()背后的逻辑

Activity 的setContent()流程

以前的Activity,都是直接继承Activity.java,而现在的Activity则基本都是继承AppCompatActivity.java,自然setContent()是不一样的,那么先捋一捋旧的

Activity.java

先从Activity.java开始看起。

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

可以看到,getWindow()方法获取了一个自己Activity持有的Window对象的引用,再调用这个对象的setContent(),之后做一个初始化流程。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.
 */

看注释,这大概是一个Activity所呈现界面的顶层Window。他的实现类只有一个,是PhoneWindow。那么就来看看这个PhoneWindow类的setContentView()方法实现:

    @Override
    public void setContentView(int layoutResID) {
        //首先这里有一个ContentParent,如果为空则做一个初始化
        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 {
            /*
            不需要动画,直接开始加载布局,这里是将layoutResID布局加载到了mContentParent上
            而layoutResID是我们交给setContent()的那个布局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;

可以看到,这个mContentParent其实就是一个ViewGroup

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

  • 初始化一个Window持有的ContentParent(即ViewGroup)对象
  • 将布局文件加载到ContentParent上
    那么现在看看这个所谓的初始化过程做了什么,即installDecor():
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {//第一步,发现mDecor没有初始化
            //生成一个mDecor对象,并对其初始化
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            //让mDecor获取一个当前window的引用
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {//第二步,发现mContentParent没有初始化
            //用前面的mDecor生成一个mContentParent对象并初始化
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            //在mDecor中找一下是否有一个DecorContentParent
            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);
            //有?对这个做初始化
            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 {//没有?那么从这里开始
                //获取一个作为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);
                    }
                }
            }
            //对这个mDecor设置背景(回调)
            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(mBackgroundFallbackResource);
            }

            //之后就是一些无关紧要的东西了
    }

再看看这个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就是一个FrameLayout了。
那么这个初始化过程就分为了两步:

  • 初始化mDecor(一个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());
    }

先是用想办法和获取一个context,然后再调用新的构造器,这里的featureId传进来的是-1。然后看构造器:


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();
        //前面不是有一个在发现mDecorView不为Null时要赋予一个当前window引用吗?这里就是在初始化完成后再做的
        setWindow(window);

        updateLogTag(params);

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

至此一个DecorView就初始化完成了,他实际上是一个FrameLayout。接下来看看这个mContentParent是如何通过DecorView来生成的:

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //这里先拿到一些属性
        TypedArray a = getWindowStyle();
        //…………
        //这里开始先是对每一种属性做判断了,比如是否悬浮?是否无标题?等等
        //具体方法和我们写自定义View时是一样的,这里省略了
        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相关的信息,比如targetsdk
        final Context context = getContext();
        final int targetSdk = context.getApplicationInfo().targetSdkVersion;
        //………………

        WindowManager.LayoutParams params = getAttributes();
        //这里是和高端设备相关的设置
        // 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.
        //这里开始,就来真的了
        //这个int值代表了要加载的布局的id
        int layoutResource;
        //所需的属性
        int features = getLocalFeatures();
        //然后,根据属性不同的需求,获取不同的布局文件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.
            //记住这个布局文件id
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        //标识着这个decorview开始改变了
        mDecor.startChanging();
        //将刚才那个布局文件,加载到decor中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //通过findviewbyid()的方式获取这个contentParent,记住这个ID_ANDROID_CONTENT
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        
        //………………
        
        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        //初始化背景和标题等等一些属性
        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            //为decorview设置背景
            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);
        }

        //标识着改变结束
        mDecor.finishChanging();
        //最后,返回这个contentParent 
        return contentParent;
    }

总结一下,就是给这个framelayout————DecorView设置了一种布局,然后通过findviewbyid的方式获取一个contentparent的。那么这两者有什么关系呢?观察到前面提到了两个id,联系就在这里!所以接下来看看具体设置布局的逻辑。

首先看看加载布局:

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指向的那个布局,这里加载后为一个叫做root的View,然后通过调用addView()方法————我们知道DecorView本身是一个FrameLayout————将root加载到自己这个FrameLayout中。

接下来看看layoutResource所引用的布局R.layout.screen_simple,即screen_simple.xml的内容:

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

嗯,一个LinearLayout,包含了一个ViewStub占位和一个FrameLayout。做一个猜测,这就是我们Activity最普通的初始界面,即一个状态栏+一个主界面。然后发现下面那个FrameLayout的id是content,再回到刚才方法中,通过findviewbyid初始化找到contentParent的时候用的id是哪个?

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

根据注释,我们知道了,这个id引用的view就是我们的主布局要加载的地方,也就是在刚才那个xml文件中的FrameLayout!

到此为止,一个installDecor()的过程基本完成了,来捋一捋。

首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到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 {
            //这里可以看到,前面初始化结束后,果然是将我们自己写的布局加载到了mContentParent中!
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

至此,Activity的setContent()流程就是走完了,大致知道了布局是怎么加载进来的。接下来看看新的AppCompatActivity是如何加载布局的

AppCompatActivity

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


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

这里通过getDelegate()方法获取了一个对象的引用,再调用他的setContentView()方法,相当于做了一个代理。
那么现在问题拆分为两步:

  • 代理的对象是如何创建的
  • 代理对象的setContentView()是如何执行的

先看第一个问题:


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

这里用来做代理的,是一个AppCompatDelegate对象,叫mDelegate,他是通过一个静态方法create()创建的,那么先看看这个类是什么:

 <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);
        }
    }

可以看到,这里最终是根据不同的sdk版本来创建不同的AppCompatDelegateImplxxx对象,分别点进去看看后会发现,最终都是到了AppCompatDelegateImplV9.java,然后:

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

所以最终是调用了父类的构造器:

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()是如何执行的。
进入AppCompatDelegateImplV9.java的setContentView():

 @Override
    public void setContentView(int resId) {
        //确保创建一个SubDecor
        ensureSubDecor();
        //通过findviewbyid的方式找到android.R.id.contentd代表的view,作为一个contentParent
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        //清空
        contentParent.removeAllViews();
        //将我们自己的布局文件加载到这个contentParent中
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

再看看这个SubDecor是什么:


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

所以我们自己写的布局文件最终是被加载到了一个id为content的ViewGroup上,而这个ViewGroup是通过subDecor来找到的,而这个SubDecor也是一个ViewGroup。那么重点就是ensureSubDecor()了,他的作用应该就是初始化一个SubDecor了:

private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            //创建一个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();
            //做一个install?
            onSubDecorInstalled(mSubDecor);
            //标识已经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);
            }
        }
    }

现在就分为了两步:

  • mSubDecor是如何被创建的
  • 创建成功之后做了什么

第一个问题,看createSubDecor()方法:


private ViewGroup createSubDecor() {
        //和自定义View时获取属性类似,这儿是从AppCompatTheme获取了属性
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        //这里判断如果没有加这个属性的话会抛出异常
        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.");
        }
        //接下来就是普通的挨个遍历属性了
        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)调用了getDecorView()方法,猜测是获取Decor
        //这里是重点,待会儿分析
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        //创建了一个subDecor引用,还未实例化
        ViewGroup subDecor = null;

        //根据不同需求,让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算是实例化完毕了

        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);
        //这里开始重点来了
        
        //从subDecor中拿到了一个ContentFrameLayout,注意id为R.id.action_bar_activity_content
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        //从window中拿到一个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中那个viewgroup中取出子View
            //然后将他们放入那个从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中的那个ViewGroup把id值为NO_ID
            windowContentView.setId(View.NO_ID);
            //然后偷梁换柱,把那个ContentFrameLayout的id设为了content
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            //把那个背景也去掉了
            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;
    }

再来看看那个重点标记的方法:

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

哦?原来window的getDecorView()方法其实就是前面提到过的installDecor()方法诶!之前说过,installDecor()方法是什么作用来着?

首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到mContentParent中就可以了。

马上接触到真相了,再随便找个刚才所引用到的布局文件看看,比如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>

看看那个id:action_bar_activity_content,而且他还是很个ContentFrameLayout 发现没有?这里的SubDecorView和以前的DecorView逻辑是很像的!都是以自己作为一个大的ViewGroup,里面放另一个小ViewGroup,在这个小ViewGroup中,还有一个ViewGroup作为根布局。

捋一捋刚才的流程:

  • 首先创建了两个DecorView,一个就是以前Activity直接用的那个DecorView,另一个叫做SubDecorView
  • 将旧DecorView的content内容交给SubDecorView的content
  • 将SubDecorView作为一个整体,交给DecorView

总之,就是一个替换的过程。

再回到前面看看:

 @Override
    public void setContentView(int resId) {
        //这里做了刚才所说的一切,现在是两个DecorView嵌套起来了
        ensureSubDecor();
        //id为content的ViewGroup现在的内容其实就是以前的DecorView用的那个
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        //清空
        contentParent.removeAllViews();
        //将我们自己的布局文件加载到这个contentParent中
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

至此,Activity的AppCompatActivity的setContent()的流程都分析完了,总结一下:

  • 一个Activity,有很多功能,其中的“展示东西给别人看”这个功能,是交给自己的一个Window对象来管理的。
  • Window包含一个ViewGroup,作为根ViewGroup
  • 根ViewGroup,根据不同的需求(即主题定义等)会加载不同的布局文件
  • 以最基本的一种布局来讲,他包含一个title和一个content
  • 我们setContent()时传入的布局文件id所指向的那个布局文件,会被加载到这个content中
  • Activity和AppCompatActivity在这里的区别在于,Activity单纯的用一个DecorView,AppCompatActivity则是在原来的基础上,加了一个SubDeocrView,将旧的DecorView的内容放到SubDecorView的content中,然后将SubDecorView作为整体放入旧的DecorView的content中,也就是说,一个DecorView包裹着一个SubDecorView
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容