高级UI

Activity 布局加载分析

2019-10-29  本文已影响0人  沐白白白白白

关于Activity需要知道的更多内容

前言

关于Activity的创建以及执行回调onCreate方法我们已经知道了(看这里:https://www.jianshu.com/p/3de730c145be
),然后我们今天想知道我们在onCreate中调用setContentView方法的时候,xml是如何加载的,所以今天就跟着源码来看看。
ps:源码版本为 android-26

一般来说我们写一个XxxActivity都会去继承Activity、FragmentActivity、AppCompatActivity这3个的其中一个,我们今天就分别对这几个父类Activity去分析

一、Activity的xml加载过程

进入我们setContentView方法

  ## Activity
  public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);//1、调用phoneWindow的setContentView方法
        initWindowDecorActionBar();
  }

这边注释的地方getWindow方法获取的一个window对象,它的实现类是PhoneWindow,它在Activity的attach中进行创建出来。

 ## Activity
 final void attach(Context context, ...,
            Window window, ActivityConfigCallback activityConfigCallback) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);//这边进行window对象的初始化
        ...
        mWindow.setColorMode(info.colorMode);
    }

我们跟进PhoneWindow,看看它里面的setContentView方法

##PhoneWindow
@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();//1、加载DecorView
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);//2、布局加载器
    }
    ...
}

注释1是加载DecorView,注释2通过布局加载器将xml解析成view树,并且将view树添加到mContentParent中

1、加载DecorView

### PhoneWindow
/**
 * 加载DecorView
 */
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);//1、创建DecorView
            ...
        } else {
            mDecor.setWindow(this);//2、DecorView绑定到Window
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//3、根据DecorView生成Layout,得到一个mContentParent
            ...
        }
    }

/*
 * 1、创建DecorView
 */
protected DecorView generateDecor(int featureId) {
    ... //这边去创建DecorView
    return new DecorView(context, featureId, this, getAttributes());
}

/*
 *2、调用DecorView的setWindow,传入PhoneWindow对象
 */
 void setWindow(PhoneWindow phoneWindow) {
        mWindow = phoneWindow;
        ...
 }


/*
 *3、根据DecorView生成Layout
 */
protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    //1、获取当前的主题,然后设置相关数据到window上
    TypedArray a = getWindowStyle();
    ...
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);//2、当前Window是否浮动在上面,默认为false
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);//3、传入window的宽、高
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }

    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {//4、window是否有title
        requestFeature(FEATURE_NO_TITLE);//5、请求指定Window的风格,它必须在setContentView之前
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestFeature(FEATURE_ACTION_BAR);
    }

    ...

    //6、这边会很多的设置窗口属性的判断和方法,具体可以大家可以去看源码

    ...

    // Inflate the window decor.
    //7、根据上面设置的窗口的属性 ,设置相应的 layoutResource
    int layoutResource;
    int features = getLocalFeatures();
    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;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        layoutResource = R.layout.screen_progress;
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                  R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
    } 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;
    }

    mDecor.startChanging();//8、开始DecorView的变化
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//9、根据布局文件xml加载得到View树

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//10、根据content id返回一个contentParent,是一个viewGroup
    

    mDecor.finishChanging(); //11、结束DecorView的变化

    return contentParent; //12、返回一个contentParent
}

在上面这段源码中,我们主要是做了几件事:

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

image.png
##screen_simple_overlay_action_mode
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <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" />
    <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>

我们能够发现这几个layout里面都包含了一个id为content的FrameLayout,它是用来添加我们自己的布局。

##DecorView
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ...
   //创建一个DecorCaptionView,它是Decor的标题View, 它包含了标题和窗口控件按钮,它的可见性取决于工作空间和窗口类型
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);//1、用LayoutInflate来加载xml布局文件得到view
    //2、 若不 null 则先添加 mDecorCaptionView, 再向 mDecorCaptionView 中添加 root
    if (mDecorCaptionView != null) {
        if (mDecorCaptionView.getParent() == null) {
            //将 mDecorCaptionView 添加到DecorView
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        //获取的到root add到 mDecorCaptionView 中
        mDecorCaptionView.addView(root,
                new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {
        // 3、若mDecorCaptionView为 null, 则直接添加调用addView将 root 加到 DecorView 中
        // Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    // 4、强转成 ViewGroup, 传递给 mContentRoot
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}


private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
    ...
   //这里将源码稍微变动下,直接调用inflateDecorCaptionView方法
    DecorCaptionView decorCaptionView =  inflateDecorCaptionView(inflater)
    ...
    return decorCaptionView;
}

private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
    final Context context = getContext();
    inflater = inflater.from(context);
    //这边通过LayoutInflater来获到DecorCaptionView
    final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
            null);
    setDecorCaptionShade(context, view);
    return view;
}   

可以看到在onResourcesLoaded这里是我们做了4件事:

##Window
 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content

2、布局加载器加载xml生成View树

通过LayoutInflater布局加载器去生成View树

##LayoutInflater
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    ...
    final XmlResourceParser parser = res.getLayout(resource);//1、根据xml id 得到一个xml解析器资源
    try {
        return inflate(parser, root, attachToRoot);//2、传入xml解析器资源和mContentParent
    } finally {
        parser.close();
    }
}

##LayoutInflater
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ...
        View result = root;//1、它就是我们最终返回的view对象

        try {
            // Look for the root node.
            int type;

            ...
            
            final String name = parser.getName();//XmlPullParser解析xml,获取里面的标签name


            if (TAG_MERGE.equals(name)) {//这里获取的name如果是merge,则root不能为null,否则会抛出异常
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                //2、这边创建得到一个临时的view对象 temp
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                ...
                if (root != null && attachToRoot) {//3、这边判断如果root不为null,则将临时的view对象temp添加到root中
                    root.addView(temp, params);
                }

                if (root == null || !attachToRoot) {//4、如果root为null,将temp传递给我们的最终view
                    result = temp;
                }
            }

        }
        ...
        return result;
    }
}

这里通过一系列的判断和传递最终会得到一个View对象,它就是我们根据xml去解析加载出来的View树。

附加一张window和contentView的层次结构图

image.png

二、AppCompatActivity的xml加载

跟进AppCompatActivity的setContentView方法

##AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);//调用getDelegate的setContentView
}

这边的getDelegate返回的是一个AppCompatDelegate,它是一个抽象类,这边我们跟踪发现最后是在AppCompatDelegateImplV9中调用了setContentView方法

##AppCompatDelegateImplV9
@Override
public void setContentView(int resId) {
    ensureSubDecor();//1、创建DecorView
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//2、这边根据content id去返回一个contentParent
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//3、通过LayoutInflater布局加载器将xml解析得到View树,添加到contentParent中
    mOriginalWindowCallback.onContentChanged();
}

1、注释1创建DecorView

##AppCompatDelegateImplV9
private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();//调用createSubDecor方法
        ...
    }
}

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

    //获取当前的主题,然后设置相关数据到window上
    ...

    mWindow.getDecorView();//1、调用PhoneWindow的getDecorView方法

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;//定义一个viewgroup


    //2、根据window的样式不同去加载不同的layout
    if (!mWindowNoTitle) {
        if (mIsFloating) {
            ...
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_dialog_title_material, null);
            ...
        } else if (mHasActionBar) {
            ...
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                    .inflate(R.layout.abc_screen_toolbar, null);
            ...
        }
    } 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);
        }

        ...
    }

    
    ...

    //3、下面具体分析
    // Make the decor optionally fit system windows, like the window's decor
    ViewUtils.makeOptionalFitsSystemWindows(subDecor);

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        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.
        windowContentView.setId(View.NO_ID);
        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);
        }
    }

    //4、调用PhoneWindow的setContentView方法
    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);
    ...
    return subDecor;
}

在ensureSubDecor调用了createSubDecor方法:

##PhoneWindow
@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}
源码位置:/frameworks/support/v7/appcompat/res/layout/abc_dialog_title_material.xml

##abc_screen_simple_overlay_action_mode
<android.support.v7.widget.FitWindowsFrameLayout
        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:fitsSystemWindows="true">

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

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

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

##abc_screen_content_include
<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>
##PhoneWindow
@Override
public void setContentView(View view) {
    setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

一张层次结构图带你理解:

image.png

三、FragmentActivity的xml加载

关于FragmentActivity,我们可以发现它自己是没有实现setContentView的,它是直接调用了父类Activity的setContentView方法,所以我们可以看上面的Activity的xml加载过程就可以了。
但是为什么会有FragmentActivity以及它的作用呢,这就要从Fragment说起来,在Android3.0以前是没有Fragment的,为了能让3.0以前能使用Fragment我们需要引入了support包,然后去继承FragmentActivity就可以使用Fragment,在3.0以后我们可以选择直接继承Activity,就可以正常使用Fragment了。而且3.0以前获取FragmentManager的方法是getSupportFragmentManager,3.0以后直接用getFragmentManager就可以了。
关于Fragment和FragmentManager我们会在另外的文字里进行分析,这里就不多讲。

ps:关于FragmentActivity的api原文

FragmentActivity is a special activity provided in the Support Library to handle fragments on system versions older than API level 11. If the lowest system version you support is API level 11 or higher, then you can use a regular Activity.

沿着别人走过的路,跟上去发现不一样的风景

参考链接:

上一篇 下一篇

猜你喜欢

热点阅读