美文网首页
Android源码分析——Activity的绘制

Android源码分析——Activity的绘制

作者: _惊蛰 | 来源:发表于2020-11-27 14:55 被阅读0次

    分析源码,搞清楚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

    相关文章

      网友评论

          本文标题:Android源码分析——Activity的绘制

          本文链接:https://www.haomeiwen.com/subject/tluxwktx.html