android之LayoutInflater以及setFactory源码解读

1、概述

     LayoutInflater setFactory是什么,干什么用的,这里不多说,这里推荐鸿洋大神的一篇以及上一篇换肤技术博客,相信解读完之后你就知道它有什么魔力,以及能熟练运用Factory。

      用法:http://blog.csdn.net/lmj623565791/article/details/51503977

      换肤技术:http://blog.csdn.net/zhongwn/article/details/52891902

      LayoutInflater setFactory确实是个好东西,v7包新组建的新特性向下兼容都是通过这玩意来做到的,也很佩服谷歌这帮工程师的设计,因为这个Factory就像是加载view的后门,我相信他们也是想到了后续可能用到而设计的框架。好了不多说了,接下来我们就一探究竟,看看它有什么魅力与魔力。

2、源码解读

     既然我们知道Factory是在LayoutInflater中,那么我们就从LayoutInflater开始解读。
     作为android猿,我们都知道LayoutInflater是用来加载xml布局的,一般来说我们都是通过以下两种来调用
     (1) 
LayoutInflater.from(this).inflate(R.Layout.xml,null);
     (2)
View.inflate(this, android.R.layout.activity_list_item,null);
而第二种我们跟进去看看
public static View inflate(Context context, int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}
发现也是调用第一种方式来加载布局。那么我们就来分析分析第一种方式,而且也是顺着加载布局开始一点点来分析。接下来看看from()这个静态方法
/**
 * Obtains the LayoutInflater from the given context.
 */
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;
}
这里只是通过context从系统服务中获取LayoutInflater一个实例,所以我们重点放在inflate()方法中,进入LayoutInfater中你会发现里边有4个inflate方法,其中有2个分别调用2个,而最后都统一调用一个方法来解析,这里贴着两个方法:
<1>
public View inflate(int resource, 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);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
   这个方法很短,一看就知道它只是简单的获取Resource资源,然后通过我们传进来的R.layout.id获取一个xml解析器,最后还是调用到第二个方法中;其中有两个方法值得关注与思考如下:
getContext().getResources();
res.getLayout(resource);
它是怎么获取Resources实例以及getLayout是怎么创建这个parser解析器实例的,这里我们暂时不做分析,留给读者自行分析。这里我们只要知道它获取的是R.layout.xml布局的解析器,接下来我们有理由相信所有的开始源于第二个方法咯。瞅瞅看便知咯

<2>
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context)mConstructorArgs[0];
        mConstructorArgs[0] = mContext;
        View result = root;

        try {
            // Look for the root node.
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

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

            final String name = parser.getName();
            
            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }

            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, attrs, false, false);
            } else {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, attrs, false);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    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");
                }
                // Inflate all children under temp
                rInflate(parser, temp, attrs, true, 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.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 (XmlPullParserException e) {
            InflateException ex = new InflateException(e.getMessage());
            ex.initCause(e);
            throw ex;
        } catch (IOException e) {
            InflateException ex = new InflateException(
                    parser.getPositionDescription()
                    + ": " + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return result;
    }
}
这个方法有点长,我们挑重点看:
final AttributeSet attrs = Xml.asAttributeSet(parser);
这里通过解析器获取整个xml属性集合attrs即将xml存储在attrs,它相当于xml布局的文档树。
简单说明西夏attrs的由来,由于parser并没有实现AttributeSet接口,所以attrs最终是由XmlPullAttribute组合parser实现的。继续往下走。
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
        type != XmlPullParser.END_DOCUMENT) {
    // Empty
}

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

这里是一个空循环,主要目的是获取xml布局最外层的View,也就是xml的root。root也是我们要解析布局的开始标签START_TAG,若是这个布局不是这个跟标签开始的,则认为布局有问题,直接抛异常。按正常流程走,
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, attrs, false, false);
} else {
   // Temp is the root view that was found in the xml
   final View temp = createViewFromTag(root, name, attrs, false);
首先获取节点名(第一次时是根节点,其它都是子节点),假设不是是以merge包含一个布局的,那么就会走else流程,接下来会执行=一个比较重要的方法:createViewFromTag():
View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {
   //省略多余代码…………
   
   if (name.equals(TAG_1995)) {
      // Let's party like it's 1995!
      return new BlinkLayout(viewContext, attrs);
   }

   try {
      View view;
      if (mFactory2 != null) {
         view = mFactory2.onCreateView(parent, name, viewContext, attrs);
      } else if (mFactory != null) {
         view = mFactory.onCreateView(name, viewContext, attrs);
      } else {
         view = null;
      }

      if (view == null && mPrivateFactory != null) {
         view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);
      }

      if (view == null) {
         //省略多余代码…………
            if (-1 == name.indexOf('.')) {
               view = onCreateView(parent, name, attrs);
            } else {
               view = createView(name, null, attrs);
            }
         //省略多余代码…………
      }
      return view;
第一个if判断当前节点(第一次时是根节点,其它都是子节点)是否是TAG_1995,
TAG_1995 = "blink"
这个当然不是,我们的组件中从没见过blink这个节点也没用过,只知道是继承自FrameLayout喜欢探究的自行研究,所以我们已正常的思路走下去。往下看,亮点来了……
呐呐呐~~一系列的Factory,每个Factory的功能基本都是一样的,看看他们的定义
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;
public interface Factory {
   public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
   public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
功能基本一样,都是同一个Factory。
当我们没赋值给任何一个Factory时,所有都是空的,所以我们view是空的
 //省略多余代码…………
            if (-1 == name.indexOf('.')) {
               view = onCreateView(parent, name, attrs);
            } else {
               view = createView(name, null, attrs);
            }
         //省略多余代码…………

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

这里首先会判断当前是否是系统组件,如果是则执行oncreateView,如果不是则执行createView();这两个都是通过反射获取获取view的实例返回;唯一不同的是一个是自定义view一个系统组件,最后onCreateView也是通过调用createView方法来实现,只是将组件包名传过去。。最终都会获取一个view返回。
当Factory为null的时候,这里的节点view(这里是第一个根节点,后续会是一个递归获取子节点)获取就结束了
当Factory不为null的时候,这会执行Factory的回调onCreateView(由开发者自行实现,如换肤功能就是通过这个实现的),若其中Factory返回的View不为null,则这个view就是返回给window粘贴显示在界面上的视图。若为null,则执行上述分析的后续流程。
所以Factory算是一个钩子,专门拦截xml的view节点,而且是每解析xml的一个节点就会执行一次,所以通过它可以实现换肤以及替换view 如将xml的textview替换成imageview,达到偷天换日的效果。
看看赋值的方法分别对应3个,其中有一个已经hide表示只用于系统,不对开发者开放,没关系,因为剩下的两个优先级高于hide方法中privateFactory,从代码可以看出来:三个方法如下:
public void setFactory(Factory factory) {
    if (mFactorySet) {
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = factory;
    } else {
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
    }
}

/**
 * Like {@link #setFactory}, but allows you to set a {@link Factory2}
 * interface.
 */
public void setFactory2(Factory2 factory) {
    if (mFactorySet) {
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}

/**
 * @hide for use by framework
 */
public void setPrivateFactory(Factory2 factory) {
    if (mPrivateFactory == null) {
        mPrivateFactory = factory;
    } else {
        mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
    }
}

你会发现setPrivateFactory这得隐藏了,再看看其它两个基本都是一样的,这里可以解读到的信息如下:
 1、一个InflateLayout只能调用一次setFactory()或setFactory2()方法,否则会直接抛异常
 
 2、若是通过InflateLayout的构造方法获取实例并将Factory的一个实例传进来,那么当调用setFactory时,将会用FactoryMerge将这两个Factory实例保留,详见类的实现。比较简单看看代码应该很容易懂
private static class FactoryMerger implements Factory2 {
    private final Factory mF1, mF2;
    private final Factory2 mF12, mF22;
    
    FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
        mF1 = f1;
        mF2 = f2;
        mF12 = f12;
        mF22 = f22;
    }
    
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        View v = mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF2.onCreateView(name, context, attrs);
    }

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
                : mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
                : mF2.onCreateView(name, context, attrs);
    }
}



接下来看看是怎么递归循环获取它的所有子节点的:思路回到最初的地方,即使inflate()方法中:



            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, attrs, false, false);
            } else {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, attrs, false);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    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");
                }
                // Inflate all children under temp
                rInflate(parser, temp, attrs, true, true);

细心的你会发现不管是if还是else都有会调用如下方法:
     // Inflate all children under temp
      rInflate(parser, temp, attrs, true, true);
其中玄妙,个中原理以及查找所有子节点,都在这个方法里。迫不及待的进去一瞧:
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
        boolean finishInflate, boolean inheritContext) 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, parent, attrs, inheritContext);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            final View view = createViewFromTag(parent, name, attrs, inheritContext);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflate(parser, view, attrs, true, true);
            viewGroup.addView(view, params);
        }
    }

    if (finishInflate) parent.onFinishInflate();
}
分两步走:
 一:这里假设这个布局没有使用merge和include
在这里也开始通过while循环开始读取xml的所有节点,留心的你,这次while循环体里边会执行所有view的操作,那么就会执行到最后一个else,你会发现
相继调用createViewFromTag()创建view,这个过程就是上述Factory,为什么每执行一个节点就会被回调一次的原因。然后继续执行rInflate()方法,由于解析器还是原来的那一个,所以可以递归查找下一个子节点。
二:如果包含了merge或include来引用布局,则会执行如下两个方法中的一个
 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, parent, attrs, inheritContext);
这两个方法:最终也还是会调用rflate()方法进行递归查找,这里就不一一分析了。
到此InflaterLayout也就解读结束了,也应该清楚Factory的面貌,至于怎么用就由开发者自行定义咯。

若有不对的地方,请不吝指正!!!



            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, attrs, false, false);
            } else {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, attrs, false);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    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");
                }
                // Inflate all children under temp
                rInflate(parser, temp, attrs, true, true);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章