从源码解析-Activity的setContentView是怎么加载XML视图的
发布日期:2021-07-19 12:30:05 浏览次数:8 分类:技术文章

本文共 16427 字,大约阅读时间需要 54 分钟。

首先新建一个Activity(如下图所示),我们点击AndroidStudio的顶部菜单栏的Tools菜单,依次点击里面的Android》Layout Inspector;这样就会弹出一个展示Activity窗口视图结构的窗口出来,如图

当前activity的布局里只添加了一个textview,并且当前Activity在setContentView之前没有进行Features设置。

当用鼠标放在左边具体view上面,右边视图上会展示相应的范围

  • 从这里可以看出来Activity最根部的view是DecorView,后面我们会说到DecorView其实是一个FrameLayout
  • DecorView包含两部分,一个装载内容的LinearLayout和一个顶部View
  • LinearLayout包含一个ViewStub和FrameLayout,其实就是一个action_bar,只不过使用ViewStub来修饰,因为开发者是可以设置不要标题栏的;至于FrameLayout就是真正装载我们通过setContentView设置进去的View
  • 最后这个TextView就是我们自己布局的内容

结构了解之后再回来通过setContentView去探索它是怎么形成的:

 

Activity setContentView

通过在onCreate中点击setContentView方法进入到源码中查看

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

这里先调用getWindow()方法

public Window getWindow() {        return mWindow;}

这个mWindow是activity类的全局变量 private Window mWindow;它是在attach方法中实例化的,并且实现了Callback接口,这个后续会用到

mWindow = new PhoneWindow(this, window);mWindow.setCallback(this);

可以看到其实是实例化了一个PhoneWindow对象,Window是个什么东西呢,可以这样说,它是顶级窗口外观和行为策略的抽象基类。 应该将此类的实例用作添加到窗口管理器的顶级视图。 它提供标准的UI策略,例如背景,标题区域,默认密钥处理等。此抽象类的唯一现有实现是android.view.PhoneWindow。

PhoneWindow setContentView

回到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) {            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 {            mLayoutInflater.inflate(layoutResID, mContentParent);        }        mContentParent.requestApplyInsets();        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {              cb.onContentChanged();        }        mContentParentExplicitlySet = true;    }

我们第一次进来的时候,这个mContentParent(这个就是装载我们设置的布局view的容器)肯定是null,那么就进入installDecor方法,这个方法很长,就不全部贴上来了,贴关键部分

if (mDecor == null) {            //通过new 创建Decorview            mDecor = generateDecor(-1);            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);            mDecor.setIsRootNamespace(true);            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);            }        } else {            mDecor.setWindow(this);        }        if (mContentParent == null) {            //根据mDecor实例获取mContentParent            mContentParent = generateLayout(mDecor);

第一步:如果DecorView为null,就通过generateDecor来构造一个实例,看看DecorView这个类

public class DecorView extends FrameLayout

说明这就是布局控件,也表明Activity的根布局就是一个FrameLayout

第二步:根布局有了,就需要往里面塞东西了,这时候就判断如果mContentParent 为null,就通过generateLayout来构建,我们看看这个方法,这个方法很长,我把一些类似的代码删了

protected ViewGroup generateLayout(DecorView decor) {        // Apply data from current theme.        //根据Manifest里设置的theme来进行相应设置        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);        }        //比如主题里设置了notitle,这里就会进行设置        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);        }		.		.		.		.        // Inflate the window decor.        //添加布局到DecorView,前面说到,DecorView是继承与FrameLayout,它本身也是一个ViewGroup,        // 而我们前面创建它的时候,只是调用了new DecorView,此时里面并无什么东西。        // 而下面的步骤则是根据用户设置的Feature来创建相应的默认布局主题。举个例子,        // 如果我在setContentView之前调用了requestWindowFeature(Window.FEATURE_NO_TITLE),        // 这里则会通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,        // 此时MainActivity没有进行任何设置,对应的就是R.layout.screen_simple        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;        } 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 if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {            // Special case for a window with only a progress bar (and title).            // XXX Need to have a no-title version of embedded windows.            layoutResource = R.layout.screen_progress;            // System.out.println("Progress!");        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {            // Special case for a window with a custom title.            // If the window is floating, we need a dialog layout            if (mIsFloating) {                TypedValue res = new TypedValue();                getContext().getTheme().resolveAttribute(                        R.attr.dialogCustomTitleDecorLayout, res, true);                layoutResource = res.resourceId;            } else {                layoutResource = R.layout.screen_custom_title;            }            // XXX Remove this once action bar supports these features.            removeFeature(FEATURE_ACTION_BAR);        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {            // If no other features and not embedded, only need a title.            // If the window is floating, we need a dialog layout            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;            }            // System.out.println("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;            // System.out.println("Simple!");        }        mDecor.startChanging();        //将上述确定的布局添加到mDecor中 mDecor其实是一个FrameLayout        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);        //contentParent是mDecor布局中的一个子view FrameLayout id是content        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);        if (contentParent == null) {            throw new RuntimeException("Window couldn't find content container view");        }        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {            ProgressBar progress = getCircularProgressBar(false);            if (progress != null) {                progress.setIndeterminate(true);            }        }        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {            registerSwipeCallbacks();        }        // 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;            }            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();        return contentParent;    }

方法很长,我们从头看,

  • 首先调用getWindowStyle()方法,这个就是获取我们在AndroidManifest里对activity设置的Theme,然后做相应的设置;比如主题里设置了windowNoTitle,那这里就会设置不要标题栏
  • 接下来会调用getLocalFeatures()方法,我们有时候会在activity的setContentView方法前设置requestWindowFeature(Window.FEATURE_NO_TITLE)类似的属性,这个方法就是获取这些设置的并做相应的处理;这也说明了为什么我们要在setContentView之前调用这些设置方法了
  • 我上面的例子没有做相关设置,if-else语句最终会走到最后一个layoutResource = R.layout.screen_simple;这个布局文件就会通过下面的 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)方法被添加到DecorView中,看看这个布局代码
     

这个布局是不是跟我最上面贴的activity的视图结构一样的,这样之前构建的DecorView这个ViewGroup里就有内容了

4. 添加完之后就要获取这个布局里面的view拿来用了,然后就有这句

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

这样就拿到了布局里的FrameLayout并赋值给contentParent,最后把这个contentParent返回

第三步:mContentParent 这样就有值了,installDecor就基本上结束了

最后再回到setContentView方法

通过installDecor方法构建了一个DecorView,这个是作为Activity的根部View,并且添加了一个布局到DecorView里;通过id找到布局里的FrameLayout赋值给mContentParent ,这个ViewGroup就是用来给我们放置要设置的布局的

接下来会执行到mLayoutInflater.inflate(layoutResID, mContentParent);第一个参数就是我们传入的布局id,第二个参数就是上面构建的FrameLayout。

这个inflate方法最终会走到下面

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {        final Resources res = getContext().getResources();        if (DEBUG) {            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("                    + Integer.toHexString(resource) + ")");        }        final XmlResourceParser parser = res.getLayout(resource);        try {            return inflate(parser, root, attachToRoot);        } finally {            parser.close();        }    }public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {        synchronized (mConstructorArgs) {	        //记录解析日志            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");            final Context inflaterContext = mContext;            //通过parser得到layout中的所有view的属性集保存在attrs中            final AttributeSet attrs = Xml.asAttributeSet(parser);            Context lastContext = (Context) mConstructorArgs[0];            mConstructorArgs[0] = inflaterContext;            View result = root;            try {                // Look for the root node.                int type;                while ((type = parser.next()) != XmlPullParser.START_TAG &&                        type != XmlPullParser.END_DOCUMENT) {                    // Empty                }                if (type != XmlPullParser.START_TAG) {                    throw new InflateException(parser.getPositionDescription()                            + ": No start tag found!");                }				//得到layout的节点,例如,view、merge、include等                final String name = parser.getName();                                if (DEBUG) {                    System.out.println("**************************");                    System.out.println("Creating root view: "                            + name);                    System.out.println("**************************");                }                if (TAG_MERGE.equals(name)) {                    if (root == null || !attachToRoot) {                        throw new InflateException("
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 final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return result; } }

这方法逻辑就是通过createViewFromTag方法不断解析并创建出对应的view,然后通过root.addView(temp, params)方法,就将我们编写的布局添加到了mContentParent 这个FrameLayout布局中。

最后就是通过cb.onContentChanged()来通知activity布局内容已经变化了;在最上面我们知道Activity的attach方法中实例化PhoneWindow的时候注册了这个接口,并实现了onContentChanged方法

public void onContentChanged() {}

显然这是一个空的实现,所以我们可以在我们自己的Activity里重写这个方法来监测页面布局的变化。

到这里setContentView方法就分析结束了,做个总结

  1. Activity启动后在attach方法中会实例化一个PhoneWindow对象,说明每个Activity都会拥有一个Window窗口,并且界面展示的特性是由Window控制
  2. 我们的Activity通过setContentView方法设置页面内容,会走到PhoneWindow类的setContentView方法中
  3. 在setContentView方法中,会实例化一个DecorView,这是每个Activity根布局,并根据Theme,Feature往这个布局里添加了一个LinearLayout
  4. LinearLayout布局里有title和FrameLayout,把FrameLayout作为mContentParent来存放我们添加的ViewGroup或者View
  5. 最后通过Callback来通知activity页面布局更新

最终就是这张图,网上借鉴

 

转载地址:https://blog.csdn.net/jdsjlzx/article/details/97895322 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Android 查看并解决重复依赖
下一篇:从源码的角度解析Handler、Looper、Message和MessageQueue

发表评论

最新留言

逛到本站,mark一下
[***.202.152.39]2024年04月07日 20时53分00秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章