安卓 - 源码 - LayoutInflater(二)

`上一节:安卓 - 源码分析 - LayoutInflater(一)

inflate方法的重载:

inflate方法有很多重载,而最终将会进入:

/*
 * 3个参数分别为:
 * parser       : 已经包含xml布局的xml解析器
 * root         : 可选参数,生成的布局的根视图,作用取决于attachToRoot参数
 * attachToRoot : 当为true时,生成的View作为子控件添加到根视图root;
                  当为false时,root仅为生成的View提供外部的LayoutParams;
 */
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

当调用参数为xml资源id的重载方法时:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

则会使用getResources().getLayout(resource)将xml布局资源转换为XmlResourceParser:

final XmlResourceParser parser = res.getLayout(resource);
try {
    return inflate(parser, root, attachToRoot);
} finally {
    parser.close();
}

默认的attachToRoot由root参数是否null决定:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root){
     return inflate(parser, root, root != null);
}
inflate的过程:

解析过程,只需要关注最终使用的inflate方法。去掉Debug和Trace,取出关键的源码先大概了解:

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;

        try {
            int type;

            //Look for the root node.
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
            }
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

            final String name = parser.getName();

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

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);

                // 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 (Exception e) {
            ...
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }
        return result;
    }
}

应该已经很明显了,然后我们一步步分析:

/*
 * 这里主要做了一些关键对象的配置和临时保存。
 */
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
/*
 * 这里主要检索xml是否能够被解析。
 * 查找第一个标签,直到文件结束。
 * 一个标签都找不到时,抛出异常,结束解析。
 */
while ((type = parser.next()) != XmlPullParser.START_TAG &&
    type != XmlPullParser.END_DOCUMENT) {
}
if (type != XmlPullParser.START_TAG) {
    throw new InflateException(parser.getPositionDescription()
            + ": No start tag found!");
}
/*
 * 这里主要是为xml布局文件创建一个根节点,
 */
if (TAG_MERGE.equals(name)) {

    /*
     * 当标签是merge时,必须有父布局及需要添加到父布局,否则异常
     */
    if (root == null || !attachToRoot) {
        throw new InflateException("<merge /> can be used only with a valid "
            + "ViewGroup root and attachToRoot=true");
    }

    /*
     * 当第一个标签是<merge/>时,调用rInflate方法解析xml并填充到参数给出的root
     * rInflate方法后面介绍
     */
    rInflate(parser, root, inflaterContext, attrs, false);
} else {

    /*
     * 当第一个标签不是merge时,使用这个标签创建一个根节点temp,
     * 调用createViewFromTag方法创建View,这个方法后面介绍
     */
    final String name = parser.getName();
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

    /* 这里的流程下面介绍 */
    ...
}
/*
 * 根据root和attachToRoot,为根节点temp配置合适的LayoutParams。
 */
ViewGroup.LayoutParams params = null;
if (root != null) {
    params = root.generateLayoutParams(attrs);
    if (!attachToRoot) {
        temp.setLayoutParams(params);
    }
}
/*
 * 通过rInflateChildren方法,将剩余的子节点解析并填充到根节点temp
 * rInflateChildren方法后面介绍
 */
 rInflateChildren(parser, temp, attrs, true);
/* 
 * 这时根节点temp已经解析完毕,并已正确添设置LayoutParams,
 * 如果参数root非空,且参数attachToRoot为true,则将temp添加到root上
 */
if (root != null && attachToRoot) {
    root.addView(temp, params);
}

/* 
 * 在开始的时候,result被设置为root,
 * 当root为空,或者attachToRoot为false,即temp没有被添加到root时
 * 应该将temp作为结果返回,即把result设置为temp
 */
if (root == null || !attachToRoot) {
    result = temp;
}

/*  清空context引用 */
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;

/* 返回结果 */
return result;
inflate过程调用的几个重要方法:

rInflate方法:
例行先源码,删减部分

void rInflate(XmlPullParser parser, View parent, Context context,
                AttributeSet attrs, boolean finishInflate) 
                throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;

    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)) {
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> 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);
        }
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}

进一步分析:

/*
 * 这里获取了xml的当前层,用于后面的层次校验,
 * 保证while循环于当前节点内,例如:
 * 当前层为1,当循环到层1时,表示当前节点的子节点全部解析完成
 */
final int depth = parser.getDepth();
int type;

/*
 * 循环条件为:
 * 层次深于当前层的节点的结尾(即不为当前节点的结尾)并且不为文件结尾
 * 这样确保了解析所有子节点,直到文件结尾,或子节点解析完成
 */
while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
       && type != XmlPullParser.END_DOCUMENT) {

    /*
     * 确保从节点开始解析节点
     */
    if (type != XmlPullParser.START_TAG) {
        continue;
    }

    /* 这里的流程下面介绍 */
    ...
}
/* 
 * 根据不同的标签进行不同的处理,主要针对几个特殊标签:
 * <requestFocus/>:使用在非容器View内,能使View获取焦点
 * <tag/>         :能在xml进行Tag设置,相当于setTag(id, value)
 * <include/>     :include只能作为子节点使用
 * <merge/>       :merge只能作为根节点使用
 * 具体的parse相对简单,可以自行了解
 * 而不是这几个特殊标签时,进入正常解析流程
 */
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
    parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
    parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
    if (parser.getDepth() == 0) {
        throw new InflateException("<include /> cannot be the root element");
    }
    parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
    throw new InflateException("<merge /> must be the root element");
} else {

    /*
     * 正常解析流程,看得出这个流程使用了递归算法
     * 调用createViewFromTag,生成当前标签对应的View对象,
     * 然后通过其父容器得到LayoutParams,
     * 然后调用rInflateChildren对该节点下的子节点进行解析填充,
     * 最后,将这个View添加到父容器中。
     */
    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);
}

rInflateChildren方法:

/*
 * 不做过多解析,看得出,rInflateChildren只是对rInflate的简单封装
 */
final void rInflateChildren(XmlPullParser parser, View parent, 
                    AttributeSet attrs,boolean finishInflate)
                    throws XmlPullParserException, IOException {

    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

createViewFromTag方法:

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

    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

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

    if (name.equals(TAG_1995)) {
        return new BlinkLayout(context, attrs);
    }

    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;
    } catch (Exception e) {
        ...
    }
}

这里就涉及到分析一提及到的Factory了,继续慢慢分析:

/*
 * 对于<view/>标签,取出其class属性替代标签名称
 */
if (name.equals("view")) {
    name = attrs.getAttributeValue(null, "class");
}

// 这里会产生一个问题,什么是view标签,class属性又是什么
// 其实view标签是为内部类View使用的,例如:
//   在com.example.A.class里,有一个自定义View:ViewB.class,
//   在编译后,ViewB类名则变成了com.example.A.class$ViewB,
//   而<com.example.A$ViewB/>标签是不能定义的,所以就要使用到<view/>标签
//   即<view class="com.example.A$ViewB"/>
// 当然你也可以对普通的View使用同样的定义方法如<view class="Button"/>
// 注意,使用<view/>标签必须定义class,否则会抛空指针异常
/*
 * 处理主题相关
 */
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();
}
/*
 * 这个TAG_1995其实就是<blink/>标签
 * 会为这个标签创建一个BlinkLayout直接返回
 * 这个BlinkLayout,会对内部的控件提供一种闪烁的功能
 * 然而是没什么卵用的,其实就是内部维护了一个handler,
 * 每0.5秒进行一次更新,根据mBlinkState去选择性绘制子控件
 * 这个0.5秒是个final值,不能改变
 */
if (name.equals(TAG_1995)) {
    return new BlinkLayout(context, attrs);
}
/*
 * 这里就是View的创建位置,调用优先级为:
 * 自定义Factory > 系统Factory > LayoutInflater默认
 */
try {
    View view;

    if (mFactory2 != null) {

        /*
         * 优先使用Factory2
         */
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
        
        /*
         * 然后再是Factory
         */
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }

    /* 这里的流程下面介绍 */
    ...
}
/*
 * 没有设置自定义Factory时,尝试使用系统添加的Factory进行创建
 */
if (view == null && mPrivateFactory != null) {
    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
/*
 * 没有设置任何工厂时,使用默认的createView方法创建View
 */
if (view == null) {

   /*
    * 这里也是临时记录context
    */
   final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;

    try {
        
        /*
         * 这里其实就是看你的view有没有完整的类名
         * 类似Button,那么就是不完整,使用onCreateView
         * 类似com.example.ViewA这样的,就可以直接使用createView
         */
        if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
        } else {
            view = createView(name, null, attrs);
        }
    } finally {

        /*
         * 还原临时记录
         */
        mConstructorArgs[0] = lastContext;
    }
}

// 返回View
return view;

onCreateView方法:

/*
 * 这个方法就是对系统提供的控件补完类名"android.view."
 * 实际上工作的还是createView方法
 */
protected View onCreateView(String name, AttributeSet attrs)
                throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

createView方法:
还是先贴源码,在这个createView里面,涉及到我们之前说的Filter了,直接解析流程

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
  
    /*
     * 尝试从构造器缓存取出构造器
     */
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;

    try {
        if (constructor == null) {

            /*
             * onCreateView方法的"android.view."在这里拼接
             * prefix + name
             */
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
            
            /*
             * 这里就是Filter作出过滤的地方了
             * 不通过过滤就会通过failNotAllowed排除异常
             */
            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 {

            /*
             * 有构造器缓存的话,直接使用Filter做一次过滤
             * 这里做了个优化,缓存了Filter的过滤结果
             */
            if (mFilter != null) {
                // 查找Filter缓存
                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);
                    // 插入Filter缓存
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }

        /*
         * 到了这里就知道mConstructorArgs对象里面存放的是什么
         * 分别是context和attrs,老铁,没毛病
         */
        Object[] args = mConstructorArgs;
        args[1] = attrs;

        /*
         * newInstance了,反射,没毛病
         */
        final View view = constructor.newInstance(args);

        /*
         * 像是ViewStub这样的情况
         * 则将自己(LayoutInflater)复制一份给ViewStub
         * 延迟加载
         */
        if (view instanceof ViewStub) {
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        
        /*
         * 到这里终于结束这个创建流程了
         */
        return view;
    } catch (NoSuchMethodException e) {
        ...
    }
}

上面整个流程涉及到的几个点需要说一下:

Filter和Factory:
因为Filter的调用是在LayoutInflater的默认创建过程才会调用的,如果拦截的View在Factory中被返回,则Filter是不会起效的,你也可以在Factory中自行调用Filter进行拦截判断。

创建子节点和本节点:
所有的创建本节点,都是使用createViewFromTag方法,
某一个节点的子节点创建,都是使用了rInflateChildren方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容