Android LayoutInflater源码解读

Posted 瞌睡先生想睡觉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android LayoutInflater源码解读相关的知识,希望对你有一定的参考价值。

这个类主要用途就是将布局文件转化成view,通常情况下的调用方式就是LayoutInflater.from(this).inflate(R.layout.test_1,parentViewGroup,false);,且setContentView方法就是通过这个方式来设置布局的。虽然网络上已经有了很多源码解读的文章,但是我还是写了本篇文章,就是想要加深自己的理解和印象,只有在你真正一行一行的源码读下去,才能发现自己的很多不足,下面我们就根据上面这句代码的调用执行顺序来看一下这个类的相关代码


    protected LayoutInflater(Context context) 
        mContext = context;
    

    /**
     * Create a new LayoutInflater instance that is a copy of an existing
     * LayoutInflater, optionally with its Context changed.  For use in
     * implementing @link #cloneInContext.
     *
     * @param original The original LayoutInflater to copy.
     * @param newContext The new Context to use.
     */
    protected LayoutInflater(LayoutInflater original, Context newContext) 
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
    

    /**
     * Obtains the LayoutInflater from the given context.
	 * 这个类的初始化方法是我们没有办法直接调用的,但LayoutInflater对外提供了 LayoutInflater.from(context)方法来获取LayoutInflater的实例化对象,而这个方法内部是调用
	 * context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)来获取对象的,这个方法实际上初始化的对象是LayoutInflater的子类PhoneLayoutInflater对象,这个暂且不提,继续往下看
     */
    public static LayoutInflater from(Context context) 
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) 
            throw new AssertionError("LayoutInflater not found.");
        
        return LayoutInflater;
    
	

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) 
        return inflate(resource, root, root != null);
    


    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) 
        return inflate(parser, root, root != null);
    


    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);//根据传入的布局的id生成xml解析器
        try 
            return inflate(parser, root, attachToRoot);
         finally 
            parser.close();
        
    

    /**
     * inflate的所有重载的方法都会调用这个最终的方法
	 *
     * @param parser xml解析器
     * @param root 是即将把解析出来的view添加到ViewGroup中的ViewGroup对象,如果attachToRoot是true就会将生成的view添加到root中并且返回root 
     * @param attachToRoot 是否将xml解析出来的view添加到root中去
     * @return 如果attachToRoot是true返回root,为false返回解析出来的root对象
     */
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 
        synchronized (mConstructorArgs) 
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            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) //跳过xml开头
                    // Empty
                

                if (type != XmlPullParser.START_TAG) 
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                

                final String name = parser.getName();//获取顶部View的name

                if (DEBUG) 
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                

                if (TAG_MERGE.equals(name)) //是否是merge标签
                    if (root == null || !attachToRoot) //如果是merge标签,root一定不为空且attachToRoot必须为true否则就会抛出异常
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    
					
                    //循环添加View
                    rInflate(parser, root, inflaterContext, attrs, false);
                 else 
                    //创建根视图
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) //此处如果root不为空程序就会设置根视图的setLayoutParams,否则根视图的LayoutParams就是全部是默认的参数
                        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");
                    

                    //循环解析添加到根视图temp中
                    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中去
                        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)  //如果没有将创建的根视图添加到父布局root中就返回给result重新赋值返回temp
                        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;
        
    

	


    /**
     *  递归添加子控件
     */
    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)) //requestFocus标签用于请求焦点
                pendingRequestFocus = true;
                consumeChildElements(parser);//next
             else if (TAG_TAG.equals(name)) //tag标签,用于给view添加多个tag
                parseViewTag(parser, parent, attrs);
             else if (TAG_INCLUDE.equals(name)) //include标签
                if (parser.getDepth() == 0) 
                    throw new InflateException("<include /> cannot be the root element");
                
                parseInclude(parser, context, parent, attrs);
             else if (TAG_MERGE.equals(name)) //merge标签必须在使用在根布局
                throw new InflateException("<merge /> must be the root element");
             else 
                final View view = createViewFromTag(parent, name, context, attrs);//创建view
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);//递归调用
                viewGroup.addView(view, params);//将创建出来的view添加到viewGroup中
            
        

        if (pendingRequestFocus) 
            parent.restoreDefaultFocus();//前面的requestFocus,请求焦点
        

        if (finishInflate) 
            parent.onFinishInflate();//viewGroup中所有子view都被从xml解析成view之后就会调用
        
    


   /**
	* 处理include标签
    */
    private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException 
        int type;
		
		//父布局只能是viewGroup
        if (parent instanceof ViewGroup) 

		    //include的主题覆盖父控件的主题
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            final boolean hasThemeOverride = themeResId != 0;
            if (hasThemeOverride) 
                context = new ContextThemeWrapper(context, themeResId);
            
            ta.recycle();

            // 提取layout
            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
            if (layout == 0) 
                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
                if (value == null || value.length() <= 0) 
                    throw new InflateException("You must specify a layout in the"
                            + " include tag: <include layout=\\"@layout/layoutID\\" />");
                

                // Attempt to resolve the "?attr/name" string to an attribute
                // within the default (e.g. application) package.
                layout = context.getResources().getIdentifier(
                        value.substring(1), "attr", context.getPackageName());

            

            // The layout might be referencing a theme attribute.
            if (mTempValue == null) 
                mTempValue = new TypedValue();
            
            if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) 
                layout = mTempValue.resourceId;
            

            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 
				//下面的内容基本同inflate方法类似,所以不多解释
                final XmlResourceParser childParser = context.getResources().getLayout(layout);

                try 
                    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

                    while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                            type != XmlPullParser.END_DOCUMENT) 
                        // Empty.
                    

                    if (type != XmlPullParser.START_TAG) 
                        throw new InflateException(childParser.getPositionDescription() +
                                ": No start tag found!");
                    

                    final String childName = childParser.getName();

                    if (TAG_MERGE.equals(childName)) 
                        // The <merge> tag doesn't support android:theme, so
                        // nothing special to do here.
                        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);
                        a.recycle();

                        // We try to load the layout params set in the <include /> tag.
                        // If the parent can't generate layout params (ex. missing width
                        // or height for the framework ViewGroups, though this is not
                        // necessarily true of all ViewGroups) then we expect it to throw
                        // a runtime exception.
                        // We catch this exception and set localParams accordingly: true
                        // means we successfully loaded layout params from the <include>
                        // tag, false means we need to rely on the included layout params.
                        ViewGroup.LayoutParams params = null;
                        try 
                            params = group.generateLayoutParams(attrs);
                         catch (RuntimeException e) 
                            // Ignore, just fail over to child attrs.
                        
                        if (params == null) 
                            params = group.generateLayoutParams(childAttrs);
                        
                        view.setLayoutParams(params);

                        // Inflate all children.
                        rInflateChildren(childParser, view, childAttrs, true);

                        if (id != View.NO_ID) 
                            view.setId(id);
                        

                        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("<include /> can only be used inside of a ViewGroup");
        

        LayoutInflater.consumeChildElements(parser);//next
    


    /**
     * Creates a view from a tag name using the supplied attribute set.
     * 根据传入的参数生成view
     *
     * @param parent 
     * @param name the name of the XML tag used to define the view
     * @param context the inflation context for the view, typically the
     *                @code parent or base layout inflater context
     * @param attrs the attribute set for the XML tag used to define the view
     * @param ignoreThemeAttr @code true to ignore the @code android:theme
     *                        attribute (if set) for the view being inflated,
     *                        @code false otherwise
     */
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) 
	    //v

以上是关于Android LayoutInflater源码解读的主要内容,如果未能解决你的问题,请参考以下文章

Android LayoutInflater&LayoutInflaterCompat源码解析

[Android FrameWork 6.0源码学习] LayoutInflater 类分析

Android LayoutInflater.inflate的使用及源码分析

Android LayoutInflater

Android源码梳理:setContentView(...)与LayoutInflater的加载机制分析

Android源码梳理:setContentView(...)与LayoutInflater的加载机制分析

(c)2006-2024 SYSTEM All Rights Reserved IT常识