LayoutInflater是如何“移花接木”-下篇

LayoutInflater“移花接木”的上篇,介绍了LayoutInflater对象的获取方式,更主要的是分析几种方式的原理,发现最终都是通过获取系统服务的方式。那么,本篇算是“移花接木”的重头,主要分析xml是如何转换为view的

获取LayoutInflater对象后,就是使用Inflate方法,从此开始,揭开“移花接木”之神秘面纱...

LayoutInflater.java

两个参数,一个xml对应的资源ID,一个是父级的view,也就是你的xml的view要绑定在谁身上

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
这里除上面的两个参数,多出一个,表示是否绑定在父view上,取决于父view是否为null

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

此处可以看出,涉及到Resources类、LayoutInflater类,执行以下两个方法,分析会围绕这两个方法展开

步骤1:public XmlResourceParser getLayout(int resourceId)

步骤2:public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)


步骤1 getLayout分析:

Resources.java

加载xml资源解析器,这里加载的是layout的,当然,还有anim等资源的解析器,参数type对应的就是“layout”,如果是动画,对应的是“anim”

    public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
        return loadXmlResourceParser(id, "layout");
    }

    XmlResourceParser loadXmlResourceParser(int id, String type)
            throws NotFoundException {
        synchronized (mAccessLock) {
            TypedValue value = mTmpValue;
            if (value == null) {
                mTmpValue = value = new TypedValue();
            }
            getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                return loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
            throw new NotFoundException(
                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
                    + Integer.toHexString(value.type) + " is not valid");
        }
    }

这里涉及getValue,loadXmlResource两个方法,

先看getValue方法内部,是在资源中查找是否存在xml的id,如果有程序继续执行,如果没有,则抛出异常,程序中断

   public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
            throws NotFoundException {
        boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
        if (found) {
            return;
        }
        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
    }

关于异常部分,实际开发中的确遇到过,比如说资源的id写错,会抛出资源找不到或者id无效的异常,就是在此校验

再看loadXmlResource方法,如下:

    XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws NotFoundException {
        if (id != 0) {
            try {
                // These may be compiled...
                synchronized (mCachedXmlBlockIds) {
                    // First see if this block is in our cache.
                    final int num = mCachedXmlBlockIds.length;
                    for (int i=0; i<num; i++) {
                        if (mCachedXmlBlockIds[i] == id) {
                            //System.out.println("**** REUSING XML BLOCK!  id="
                            //                   + id + ", index=" + i);
                            return mCachedXmlBlocks[i].newParser();
                        }
                    }

                    // Not in the cache, create a new block and put it at
                    // the next slot in the cache.
                    XmlBlock block = mAssets.openXmlBlockAsset(
                            assetCookie, file);
                    if (block != null) {
                        int pos = mLastCachedXmlBlockIndex+1;
                        if (pos >= num) pos = 0;
                        mLastCachedXmlBlockIndex = pos;
                        XmlBlock oldBlock = mCachedXmlBlocks[pos];
                        if (oldBlock != null) {
                            oldBlock.close();
                        }
                        mCachedXmlBlockIds[pos] = id;
                        mCachedXmlBlocks[pos] = block;
                        
                        return block.newParser();
                    }
                }
            } 
        }
        throw new NotFoundException(
                "File " + file + " from xml type " + type + " resource ID #0x"
                + Integer.toHexString(id));
    }

乍一看,很多代码,有点蒙圈,仔细一看,return位置,newParser()方法是重点需要关注的,mCachedXmlBlocks是一个XmlBlock类型数组,如下:

    private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];

这样,最终集中到XmlBlock类,如下:

XmlBlock.java

    public XmlResourceParser newParser() {
        synchronized (this) {
            if (mNative != 0) {
                return new Parser(nativeCreateParseState(mNative), this);
            }
            return null;
        }
    }

看到这,Parser是什么,Parser是一个类,实现了XmlResourceParser接口,继承了XmlPullParser接口,

XmlResourceParser虽然是接口,但基本没声明方法

也就是说,XmlBlock的内部类Parser,具体实现了XmlPullParser的方法,源码如下:

    final class Parser implements XmlResourceParser{
        //...
        public String getText() {
       int id = nativeGetText(mParseState);
       return id >= 0 ? mStrings.get(id).toString() : null;
     }
     public int getLineNumber() {
       return nativeGetLineNumber(mParseState);
     }
        //...
        public int next() throws XmlPullParserException,IOException {
            if (!mStarted) {
                mStarted = true;
                return START_DOCUMENT;
            }
            if (mParseState == 0) {
                return END_DOCUMENT;
            }
            int ev = nativeNext(mParseState);
            if (mDecNextDepth) {
                mDepth--;
                mDecNextDepth = false;
            }
            switch (ev) {
            case START_TAG:
                mDepth++;
                break;
            case END_TAG:
                mDecNextDepth = true;
                break;
            }
            mEventType = ev;
            if (ev == END_DOCUMENT) {
                // Automatically close the parse when we reach the end of
                // a document, since the standard XmlPullParser interface
                // doesn't have such an API so most clients will leave us
                // dangling.
                close();
            }
            return ev;

}


Parser的代码比较繁杂,这里摘录了一部分,具体可参见XmlBlock.java源码

在next()方法中,很清晰看到对于xml步步解析也是使用EventType的进行的,在XmlPullParser.java中TYPES数组定义了xml所有的EventType类型

可以看出,对xml的解析,用到很多native方法,也就是说,xml是靠c代码解析的,这点避免了java类的编译过程,尤其是xml解析这种对xml层层解析的过程,是比较耗时的操作,c代码的执行效率远高于java代码

native方法部分具体实现暂时没有研究,后续慢慢会补上

至此,getLayout分析完

步骤2 Inflate分析开始,

LayoutInflater.java,以下源码在该类中

第一个参数,步骤1 获取到的资源解析器

第二个参数,父级的view

第三个参数,是否绑定到父view上

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            //...
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
                // Look for the root node.
                int type;
                //...
                final String name = parser.getName();
               //...
                // 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) {
                        // 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);
                        }
                    }
                    //...
                    // 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;
                    }
                    //...
            return result;
        }
    }

上面代码大致过程:

首先,执行通过解析器,获取到要inflate的view名称,

然后,调用createViewFromTag,参数依次,父view,view名称,上下文,view对应的AttributeSet,最终得到temp,也就是我们inflate xml布局后的view,

值得注意的,这个temp是xml布局中最顶的,比如,xml布局中最顶级是Framlayout,那么,temp对应的就是Framlayout,如果是LinearLayout,temp对应的就是LinearLayout,这也是为什么我们finViewById时,需要指定view.findViewById的原因,从顶级父view开始搜寻指定id的子view

接着,通过父view得到LayoutParams,设置view的LayoutParams,将view绑定到父view上

如果没有父view,直接返回view

接下来,具体看createViewFromTag,代码执行如下:

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }
        //...
        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;
        } 
    }

看完这段代码,是不是很爽?基本不大明白,别怕,这里我们只关注需要的,那就是最终返回的view,

上面的关键执行过程:mFactory2,mFactory,mPrivateFactory的判断逻辑,不为null,分别回调各自方法赋值给view,全部为null,调用类方法createView,onCreateView

涉及到对应接口mFactory2,mFactory,mPrivateFactory对应的方法,有类方法createView,onCreateView,也有回调方法onCreateView

先看接口mFactory2,mFactory,mPrivateFactory,是在LayoutInfalter的构造中初始化的,如下:

    protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
    }
关于上面LayoutInfalter的构造方法,没有找到具体在哪被实例化,不过,这并不影响我们分析,这里认定mFactory2,mFactory,mPrivateFactory全部为null,那么,按照上面

代码逻辑,就会调用LayoutInflater的类方法createView,onCreateView,代码如下:

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

onCreateView内部执行的是createView方法,如下:

第一个参数,view的名称,第二个参数view对应的类名前缀,第三个参数view对应的AttributeSet

public final View createView(String name, String prefix, AttributeSet attrs){
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;
        //...
            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 {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        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[] args = mConstructorArgs;
            args[1] = attrs;

            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]));
            }
            return view;
            //...
    }

代码很长,一点点分析,首先是从sConstructorMap中取对应name的view构造,也就是从缓存中取构造,

如果存在构造,则执行else分支,如果没有构造,则执行if,获取字节码对象,获取构造,添加到sConstructorMap缓存中,方便再次使用

获取view类的构造方法后,创建view的实例对象,返回实例对象

这个过程是反射的运用

以上是假定,mFactory2,mFactory,mPrivateFactory全部为null情况

另外,关于mFactory2,mFactory,mPrivateFactory全部为null,在LayoutInflater"移花接木"-上篇中,提到,三种获取LayoutInflater方式,最终获取到的是SystemServiceRegistry类中注册的PhoneLayoutInflater的实例,构造函数以及父类构造函数,如下:

    public PhoneLayoutInflater(Context context) {
        super(context);
    }
    protected LayoutInflater(Context context) {
        mContext = context;
    }

这也说明,创建LayoutInfalter并未涉及到,protected LayoutInflater(LayoutInflater original, Context newContext),这种构造方法,那么,mFactory2,mFactory,mPrivateFactory自然也就全部为null

Inflate分析完成

步骤1 步骤2,整个Inflater.inflate方法执行过程分析到此结束


总结:xml布局,资源文件的解析,底层是用c代码编写的,执行效率高

            xml布局转化为view最终是采用反射的方式,获取实例对象的





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值