Activity加载xml布局流程源码分析(一)

目录

一、从Activity的setContentView方法入手

二、PhoneWindow的setContentView方法

三、回到PhoneWindow的setContentView方法


首先明确的是,该文章只分析了xml加载到view布局中的流程,至于xml文件如何解析,并不是本文关注重点。我们更关注的是,通过SDK的API加载xml的流程,从中我们可以看到系统都做了哪些事,我们开发这么久,有没有认真思考过?

Android开发者都知道,Activity通过setContentView加载xml布局文件,那就可以通过分析Activity的setContentView顺藤摸瓜,查看源码,来了解一个布局文件是如何被加载出来的。

一、从Activity的setContentView方法入手

源码路径:

/frameworks/base/core/java/android/app/Activity.java

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

可以看出,是通过getWindow方法获得Window对象,获得是mWindow,mWindow被初始化是在attach中,很明显,根据Activity的生命周期,attach方法先于setContentView方法被调用。接下来,看下attach方法中都做了什么?

 final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);
​
        mFragments.attachHost(null /*parent*/);
​
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();
​
        mMainThread = aThread;

从上面代码看出那么,mWindow是具通过phoneWindow的构造方法完成了初始化,phoneWindow是Window的具体实现类,下面我们里面做了什么?

 /**
     * Constructor for main window of an activity.
     */
    public PhoneWindow(Context context, Window preservedWindow,
            ActivityConfigCallback activityConfigCallback) {
        this(context);
        // Only main activity windows use decor context, all the other windows depend on whatever
        // context that was given to them.
        mUseDecorContext = true;
        if (preservedWindow != null) {
            //第1步:获得DecorView
            mDecor = (DecorView) preservedWindow.getDecorView();
            mElevation = preservedWindow.getElevation();
            mLoadElevation = false;
            mForceDecorInstall = true;
            // If we're preserving window, carry over the app token from the preserved
            // window, as we'll be skipping the addView in handleResumeActivity(), and
            // the token will not be updated as for a new window.
            getAttributes().token = preservedWindow.getAttributes().token;
        }
        // Even though the device doesn't support picture-in-picture mode,
        // an user can force using it through developer options.
        //第2步 画中画模式,用户可以自己修改
        boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
                DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
        mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_PICTURE_IN_PICTURE);
        mActivityConfigCallback = activityConfigCallback;
    }

关于DecorView,

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

mDecor初始化,

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

从注释中,可以看出DecorView是Window的顶级视图,包含了window的装饰内容,即DecorView是Window的根节点。后面我们还会提到这个DecorView,这里是获取它。记住installDecor这个方法。

至此,第一步已经完成了,Activity启动,初始化了PhoneWindow对象,通过调用PhoneWindow中的setContentView方法,来加载布局,接下来进入第二步。

二、PhoneWindow的setContentView方法

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //1、初始化DecorView和mContentParent
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
​
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            //2、设置Activity切换动画
            transitionTo(newScene);
        } else {
            //3、将Id为layoutResID资源文件的布局加载到mContentParent中去
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

上面代码,我们核心关注注释1和3的部分,进入setContentView首先判断的是mContentParent,什么是mContentParent,是一个ViewGroup,注意,ViewGroup是Android系统的ViewTree的根类,或者我们做过动态加载布局的时候,是不是也用到过ViewGroup。

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

从注释上看,它是window中内容用来替换的view,它不是mDecor自己,就是mDecor的子view。读了这段注释我们也许不知道mContentParent具体能干什么,或者到底是什么,我们记住are placed就够了。根据代码经验,一般这个时候,mContentParent肯定是null的,那么installDecor(第一步前面提到过的)就是为了初始化它,继续向下看

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //1、初始化DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            //2、已经初始化后,第二次走这里,this指PhoneWindow
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //3、初始化mContentParent
            mContentParent = generateLayout(mDecor);
​
            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
​
            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);
​
                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                        (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                    mDecorContentParent.setIcon(mIconRes);
                } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                        mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                    mDecorContentParent.setIcon(
                            getContext().getPackageManager().getDefaultActivityIcon());
                    mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                }
                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                        (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                    mDecorContentParent.setLogo(mLogoRes);
                }
                //...省略
           }
        }
}

上面核心代码已经注释,先来看,generateDecor是如何初始化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());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        //this 指的是phoneWindow对象
        return new DecorView(context, featureId, this, getAttributes());
    }

通过构造方法,返回DecorView对象,我们去DecorView类中看下,什么是DecorView

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
}

到这里,DecorView继承了FrameLayout,DecorView的实例对象为PhoneWindow所持有。再来看DecorView的构造方法

 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();
​
        setWindow(window);
​
        updateLogTag(params);
​
        mResizeShadowSize = context.getResources().getDimensionPixelSize(
                R.dimen.resize_shadow_size);
        initResizingPaints();
    }

DecorView 构造方法设置依赖一些动画相关的特性,并没有其他特别,我们到这里只要知道DecorView 的父类是FrameLayout布局即可,并且已经和PhoneWindow做了关联,这样就可以了,现在看mContentParent是如何初始化的

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //  1、获取了Window的属性,和我们自定义控件属性类似,获取后要进行解析
        TypedArray a = getWindowStyle();
​
        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }
        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);
        }
​
        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }
​
        if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
​
        if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
            requestFeature(FEATURE_SWIPE_TO_DISMISS);
        }
​
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }
​
        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }
​
        if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
                false)) {
            setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                    & (~getForcedWindowFlags()));
        }
​
        if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
            setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
        }
     //........若干代码
     int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            } 
            //省略....很多代码
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
            } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
         }
      mDecor.startChanging();
     //2、加载布局文件作为根布局
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      //3、获得id为ID_ANDROID_CONTENT的ViewGroup,来初始化contentParent
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
 }
     
​

onResourcesLoaded源码如下:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        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();
    }

从onResourcesLoaded可以看出,加载布局资源layoutResource为DecorView的子view,而资源文件的路径:frameworks/base/core/res/res/layout,一般是一个LinearLayout布局文件。contentParent的初始化是通过找到id为content的framlayout赋给mContentParent,mContentParent同样为DecorView的子view,mContentParent也就是root视图。

三、回到PhoneWindow的setContentView方法

mContentParent初始化完成后,通过

mLayoutInflater.inflate(layoutResID, mContentParent);

将我们的布局加载到mContentParent中,也就是将我们的布局文件的view添加到了DecorView中,整个加载流程到此就结束了。用一个图来表达这个层次关系,就是下面这张图

                                                                     

总结:Acivity的启动后,通过PhoneWindow来启动这个加载流程,而一个xml布局文件的加载进来,其实是一个替换过程,就是先通过加载一个线性布局资源文件,来替换其中id为conten的FrameLayout布局部分。xml加载流程只是其中一部内容,还有视图是如何显示出来,xml是如何解析出来,这些问题,都值得我们探索研究。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ezview_uniview

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值