include 标签

如果给include标签 和 include所加载的布局 都添加id的话,那么id要保持一致,如例子中都是container,否则是在代码中获取不到RelativeLayout容器的。

include布局里元素的id 要和 include所在页面布局里的其他元素id 不同,如例子中的两个textview,如果把id设置相同了,程序运行起来并不会报错,但是textview的赋值只会赋值给其中的一个。

如果需要给include标签设置位置属性的话,如例子中的layoutbelow、layout_marginTop,这时候 必须 同时设置include标签的宽高属性layout_width、layout_height,否则编译器是会报错的。同时include中定义的 layout* 相关熟悉会失效。

merge 标签

merge标签可用于减少视图层级来优化布局,可以配合include使用,如果include标签的父布局 和 include布局的根容器是相同类型的,那么根容器的可以使用merge代替。

ViewStub按需加载

按需加载 顾名思义需要的时候再去加载,不需要的时候可以不用加载,节约内存使用。比如app中页面里某个布局只需要在特定的情况下才显示,其余情况下可以不用加载显示,这时候可以使用ViewStub。

使用 ViewStub.layout 属性设置需要加载的布局,当需要加载视图的时候,调用 inflate 方法,一旦调用后,ViewStub将从视图中移除,被对应的layout布局取代,同时会保留ViewStub上设置的属性效果。

布局加载原理

//AppCompatDelegateImpl
@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); //找到根视图
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//调用 LayoutInflater inflate 直接获取,并attach到根视图
    mOriginalWindowCallback.onContentChanged();
}

//LayoutInflater 
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) + ")");
    }

    //这里 view 总是空
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }

    //读取xml文件
    XmlResourceParser parser = res.getLayout(resource);
    try {
        //解析xml文件 生成相应视图
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

主要流程就是上面:获取根视图,读取xml文件,生产相应的视图并贴到根视图上面。 好的 分开看,先看xml读取

读取xml文件

// ResourceImpl
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
    @NonNull String type)
    throws NotFoundException {

    ...

    final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);

    ...

    return block.newParser();

    ...
}


// AssetsManager
@NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
    Preconditions.checkNotNull(fileName, "fileName");
    synchronized (this) {
        ensureOpenLocked();
        final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
        if (xmlBlock == 0) {
            throw new FileNotFoundException("Asset XML file: " + fileName);
        }
        final XmlBlock block = new XmlBlock(this, xmlBlock);
        incRefsLocked(block.hashCode());
        return block;
    }
}

因此归根到底是使用了 AssetsManager 来读取了本地 xml 文件生产了 XmlBlock,最后生成 xmlParser。

生成视图

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {

        ...
        View result = root;

        try {
            advanceToRootNode(parser);
            final String name = parser.getName();

            ...

            // 对于 merge 标签做额外处理
            if (TAG_MERGE.equals(name)) {
                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
                // 真正创建相应的view createViewFromTag 是关键。
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ...
                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;
                }
            }
            ...
        }
        ...
        return result
    }
    ...
}

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
    ...

    try {
        View view = tryCreateView(parent, name, context, attrs);

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    view = createView(context, name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } 
    ...
}

public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context,
    @NonNull AttributeSet attrs) {
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    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);
    }

    return view;
}

在createViewFromTag方法中,首先会判断mFactory2是否存在,存在就会使用mFactory2的onCreateView方法区创建视图,否则就会调用mFactory的onCreateView方法,接下来,如果此时的tag是一个Fragment,则会调用mPrivateFactory的onCreateView方法,否则的话,最终都会调用LayoutInflater实例的createView方法.

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {

   ...

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            // 加载相应的类
            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 {
            ...
        }

        ...

        // 构造相应的对象 并返回
        final View view = constructor.newInstance(args);
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        mConstructorArgs[0] = lastContext;
        return view;

    } 
   ...
}

整个生成过程,可以看代码中的注释。

最后,我们来总结下Android中的布局加载流程:

  • 在setContentView方法中,会通过LayoutInflater的inflate方法去加载对应的布局。
  • inflate方法中首先会调用Resources的getLayout方法去通过IO的方式去加载对应的Xml布局解析器到内存中。
  • 接着,会通过createViewFromTag根据每一个tag创建具体的View对象。
  • 它内部主要是按优先顺序为Factory2和Factory的onCreatView、createView方法进行View的创建,如果都没有的话则使用 LayoutInflater 实例的onCreatView、createView进行创建,而createView方法内部采用了构造器反射的方式实现。

results matching ""

    No results matching ""