Android源码阅读分析:从资源文件到控件布局——LayoutInflater分析

从资源文件到控件——LayoutInflater分析


(注:源代码为android-8.1)

0. 前言

  我在我的文章《Android源码阅读分析:从Activity开始(二)——加载布局》中简单介绍了Activity的如何加载布局的。在文章末尾提到,资源文件通过inflater方法转换为View。那么本篇文章就来分析一下LayoutInflater是如何工作的。

1. 创建LayoutInflater

  LayoutInflater的构造方法被protected修饰,也就是说,我们在创建LayoutInflater时,不能直接使用构造方法来创建新对象。官方的推荐获取LayoutInflater对象方式有两个:
  - 通过Activity.getLayoutInflater方法获取
  - 通过Context.getSystemService方式获取
这样能够保证获取到的LayoutInflater对象是已经与当前context关联上。那么,我就分别从这两个方法来看一下是如何获取对象的。

1.1 通过Activity.getLayoutInflater获取LayoutInflater对象
(frameworks/base/core/java/android/app/Activity.java)

public LayoutInflater getLayoutInflater() {
    return getWindow().getLayoutInflater();
}

  从上一篇文章中,我们知道了,Activity.getWindow方法所得到的是一个PhoneWindow对象,那么跟踪PhoneWindowgetLayoutInflater方法。

(framework/base/core/java/com/android/internal/policy/PhoneWindow.java)

public LayoutInflater getLayoutInflater() {
    return mLayoutInflater;
}

  哦?这里就是简简单单的返回了mLayoutInflater。那么,我们就要看看这个变量是在哪里创建或者赋值的。
  经过查找,发现在PhoneWindow类里,只有一个地方是mLayoutInflater变量被赋值了。

(frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java)

public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

  这里调用了LayoutInflaterfrom方法。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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.getSystemService方法,这与官方推荐的第二种方法相同。

1.2 通过Context.getSystemService方式获取LayoutInflater对象

  getSystemServiceContext的一个方法,通过传入NAME来获取对应的Object,然后转换为相应的服务对象。这里传入的参数值为Context.LAYOUT_INFLATER_SERVICE。通过查找,在ContextThemeWrapper类中找到处理Context.LAYOUT_INFLATER_SERVICE的方法。

(frameworks/base/core/java/androidview/ContextThemeWrapper.java)

@Override
public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

  这里又调用了from(getBaseContext),而baseContext通常情况下就是ContextImpl。那么跟踪到ContextImplgetSystemService方法。

(frameworks/base/core/java/android/app/ContextImpl.java)

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

  在这里我们发现,是从SystemServiceRegistry里获取的系统服务,再继续跟踪。

(frameworks/base/core/java/android/app/SystemServiceRegistry.java)

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

  从这里可以看出,LayoutInflater是从SYSTEM_SERVICE_FETCHERS里获取的,而SYSTEM_SERVICE_FETCHERS是一个HashMap,继续跟踪代码,发现Context.LAYOUT_INFLATER_SERVICE是在静态代码块中调用静态方法registerService内存入SYSTEM_SERVICE_FETCHERS的。

(frameworks/base/core/java/android/app/SystemServiceRegistry.java)

static {
    ...
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            return new PhoneLayoutInflater(ctx.getOuterContext());
        }});
    ...
}

  到这里,我们终于发现了真正的LayoutInflater是怎么被创造出来的了。原来我们真正使用的LayoutInflater其实是它的子类PhoneLayoutInflater
  那么,我们再看一下PhoneLayoutInflater类的cloneInContext方法。这个方法在LayoutInflater中是抽象方法。

(frameworks/base/core/java/com/android/internal/policy/PhoneLayoutInflater.java)

public LayoutInflater cloneInContext(Context newContext) {
    return new PhoneLayoutInflater(this, newContext);
}

  通过这个方法,可以获取一个在新的Context下的PhoneLayoutInflater对象。
  

2. 构建View

  通常情况下,我们使用LayoutInflater构建View时,会调用inflate方法,下面我们来看一下其实现。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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

  继续跟踪。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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

  首先根据resourceIdContext中获取到Resource对象,然后调用getLayout得到一个XML解析器,之后调用重载方法inflate

(frameworks/base/core/java/android/view/LayoutInflater.java)

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    sychornized(mConstructorArgs) {
        ...
        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        ...
        mConstructorArgs[0] = inflaterContext;
        View result = root;
        try {
            // 获取根节点
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }
            ...
            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 {
                // XML资源文件中的根节点View
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ...
                // 解析子节点
                rInflateChildren(parser, temp, attrs, true);
                ...
                if (root != null && attachToRoot) {
                    // 将解析出的根节点View添加到
                    root.addView(temp, params);
                }
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
            ...
        }
        return result;
    }
}

  这个方法的主要功能是根据参数和布局资源文件解析根节点View并返回,其内部调用了rInflate方法和rInflateChildren方法。其中,rInflateChildren方法内部也调用了rInflate方法。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } 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 (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }

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

  这个方法通过递归方法解析出整个资源文件的布局树。其中核心的实例化View的方法为createViewFromTag。那么下面就重点看一下createViewFromTag方法是如何实现的。

(frameworks/base/core/java/android/view/LayoutInflater.java)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
    ...
    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) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                // 如果name不含".",表示该控件为系统自带控件
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            }
            ...
        }
        return view;
    }
    ...
}

  在创建控件实例时,首先判断该控件是否为系统自带控件,如果是系统自带控件,则调用onCreateView方法,否则直接调用createView方法。我们先看一下onCreateView方法,该方法在LayoutInflater中其实就是在控件名称前加上android.view.前缀,之后再调用createView方法。PhoneLayoutInflater复写了onCreateView方法,其实只是增加了三个前缀android.widget.android.webkit.android.app.,最终仍然会调用createView方法。那么下面就看一下createView方法的实现。

(frameworks/base/core/java/android/view/LayoutInflater.java)

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) {
            // 通过反射得到name对应的控件类
            clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
            ...
            // 获取构造器
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            if (mFilter != null) {
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    ...
                }
                ...
            }
        }
        ...
        Object[] args = mConstructorArgs;
        ...
        final View view = constructor.newInstance(args);
        ...
        return view;
    }
    ...
}

  这个方法主要做了两件事,一个是获取或创建控件的构造器,另一个是通过构造器实例化控件。在创建控件的构造器时,用到了mConstructorSignature

(frameworks/base/core/java/android/view/LayoutInflater.java)

static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};

  可以看到这里的得到的构造器是包含ContextAttributeSet类型的两个参数。也就是说,如果我们需要做自定义控件的话,那么必须要有这两个类型参数的构造方法,即View(Context context, AttributeSet attrs)方法。
  至此,我们完成了从布局XML资源文件到控件的转换过程。

3. 总结

  本篇文章主要分析了源码中LayoutInflater的创建过程以及其如何将布局XML资源文件转换为控件布局的过程。
  总的来说,就是通过获取系统服务的方式,得到LayoutInflater对象。LayoutInflater则会对布局资源文件进行解析,根据解析得到的控件名称,获取控件构造器。再利用控件构造器创建控件实例。

发布了21 篇原创文章 · 获赞 63 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/dongzhong1990/article/details/80243794