【Android】Framework笔记——setContentView流程

setContentView

首先先来到了 Activity::setContentView,它其实调用了 getWindow().setContentView, Window 在我们这里的实现类是 PhoneWindow,所以最后来到了 PhoneWindow::setContentView

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        // 1
        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 {
        // 2
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

PhoneWindow::setContentView 中,若 DecorView 未进行初始化,首先会调用 installDecor 对 DecorView 进行初始化。

installDecor 首先会调用 generateDecor 初始化并创建 DecorView,之后调用 generateLayout 通过 findViewById(ID_ANDROID_CONTENT) 找到并返回了 mParentContent。之后就是一些如对 ActionBar 等的初始化。(ID_ANDROID_CONTENT 即为 android.id.content)

installDecor 完成后则会调用 mLayoutInflater.inflate(layoutResID, mContentParent) 这一方法对我们 setContentView 所传入的 layoutId 进行 inflate 操作。

inflate

在 inflate 中,它首先会构造出一个 Xml 解析器,之后便会开始对 xml 文件进行解析。它首先会寻找布局的 xml 的 START_TAG 从而找到整个布局的根节点,之后开始对 xml 中的元素进行处理。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        try {
            // ...
            final String name = parser.getName();
            // ...
            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 {
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                // ...
                rInflateChildren(parser, temp, attrs, true);
                // ...
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                // ...
                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;
    }
}

首先它会判断根节点是否为 merge 节点,若是 merge 节点,则直接调用 rInflate 方法递归解析剩下的节点,若是普通布局则会调用 createViewFromTag 方法构建出一个 View,之后调用 rInflateChildren 对内层进行递归解析(其实内部就是调用了 rInflate 方法),最后将其添加到 parent 上

rInflate

其实 inflate 过程是一个递归的过程,其递归的具体实现就是通过 rInflate 方法,在 rInflate 中,它首先获取了当前这个标签的深度,之后不断向其内部走。

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }
        final String name = parser.getName();
        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException(" cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException(" must be the root element");
        } else {
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }
    // ...
}

若发现了 name 为 requestFocus 的节点,则会调用 consumeChildElements方法

若发现了 name 为 tag 的节点,则会调用 parseViewTag 方法

若发现了 name 为 include 的节点,则会调用 parseInclude 方法

若发现了 name 为 merge 的节点,则会抛异常,因为 merge 只能作为根节点

普通节点则会调用 createViewFromTag 获取该节点对应 View,并对该节点调用 rInflateChildren 从而进行向内层的递归解析,之后将其添加到当前 View 上。

consumeChildElements

final static void consumeChildElements(XmlPullParser parser)
        throws XmlPullParserException, IOException {
    int type;
    final int currentDepth = parser.getDepth();
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
        // Empty
    }
}

consumeChildElements 其实作用就是忽略这个节点剩下的内容,直到下一个新的 START_TAG,也就是不对其作处理。

parseViewTag

private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
        throws XmlPullParserException, IOException {
    final Context context = view.getContext();
    final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
    final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
    final CharSequence value = ta.getText(R.styleable.ViewTag_value);
    view.setTag(key, value);
    ta.recycle();
    consumeChildElements(parser);
}

parseViewTag 方法主要的作用就是对其父 view 设置其属性中所指定的 key 及 value,之后调用 consumeChildElements 方法忽略剩下的内容

parseInclude

private void parseInclude(XmlPullParser parser, Context context, View parent,
        AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;
    if (parent instanceof ViewGroup) {
        // ...
        if (layout == 0) {
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout "
                    + "reference. The layout ID " + value + " is not valid.");
        } else {
            final XmlResourceParser childParser = context.getResources().getLayout(layout);
            try {
                // ...
                final String childName = childParser.getName();
                if (TAG_MERGE.equals(childName)) {
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    final View view = createViewFromTag(parent, childName,
                            context, childAttrs, hasThemeOverride);
                    final ViewGroup group = (ViewGroup) parent;
                    final TypedArray a = context.obtainStyledAttributes(
                            attrs, R.styleable.Include);
                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                    // ...
                    rInflateChildren(childParser, view, childAttrs, true);
                    // ...
                    switch (visibility) {
                        case 0:
                            view.setVisibility(View.VISIBLE);
                            break;
                        case 1:
                            view.setVisibility(View.INVISIBLE);
                            break;
                        case 2:
                            view.setVisibility(View.GONE);
                            break;
                    }
                    group.addView(view);
                }
            } finally {
                childParser.close();
            }
        }
    } else {
        throw new InflateException(" can only be used inside of a ViewGroup");
    }
    LayoutInflater.consumeChildElements(parser);
}

对于 include 节点,它会首先取出 layout 属性所对应的 layout 并为其创建一个 xml 解析器,之后对其进行解析。其解析过程与前面的 inflate 几乎一致: 对 merge 直接调用 rInflate 递归解析剩下的节点,对其他节点先调用 createViewFromTag 构建 View,之后调用 rInflate 递归解析剩下节点。

createViewFromTag

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    // ...
    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        return view;
    } 
    // ...
}

在 createViewFromTag 中首先会检查 mFactory2 是否为 null,不为 null 则直接通过 mFactory2::onCreateView 进行解析。

若为 null,则又会检查 mFactory 是否为 null,不为 null 则通过 mFactory::onCreateView 进行解析。

否则又会检查 mPrivateFactory 是否为 null,不为 null 则通过mPrivateFactory::onCreateView 进行解析。

若前面的条件都不满足则会进入其默认解析步骤,它会判断当前的 name 对应的是一个系统 View 还是一个自定义 View,判断的过程是通过判断 name 中是否含有 「.」,若含有「.」则说明它是一个自定义 View。

若为系统 View,会直接调用 LayoutInflater 的 onCreateView 方法进行 View 对象的创建。

若不为系统 View,则会调用 LayoutInflater 的 createView 方法进行 View 对象的创建

onCreateView

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

其实 onCreateView 的作用只是将系统 View 的 name 前补上 「android.view.」,之后又调用了 createView 进行 View 的创建

createView

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Constructor constructor = sConstructorMap.get(name);
    // ...
    Class clazz = null;
    try {
        // ...
        if (constructor == null) {
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }
        Object lastContext = mConstructorArgs[0];
        // ...
        final View view = constructor.newInstance(args);
        if (view instanceof ViewStub) {
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        mConstructorArgs[0] = lastContext;
        return view;
    } 
    // ...
}

createView 的过程主要是通过反射实现的,它先尝试从 sConstructorMap 中尝试拿出该 name 对应 View 的构造函数的缓存,若没有则会先通过 ClassLoader::loadClass 加载出对应 class 对象,之后通过 getConstructor 获取构造函数并放入缓存。

同时这里还会有一层对 Filter 的判断,若对应的 class 在 mFliter 中不允许被 inflate(onLoadClazz 返回 false),则会在此处抛出异常。

得到构造函数之后,便开始尝试通过构造函数来构建对应的 View 对象,将 context 及 attrs 两个参数填入后调用了 Constructor::newInstance() 方法从而创建了对应的 View。

此时有个对 ViewStub 的特殊处理,若该 View 为 ViewStub,则会调用其 setLayoutInflater 并拷贝一个对应 context 创建的 LayoutInflater 从而使其可以延迟加载。

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注

%d 博主赞过: