LayoutInflater源码深度分析笔记

LayoutInflater是一个抽象类,具体代码如下:

package android.view;

public abstract class LayoutInflater {
    //代码省略
}

既然是抽象不是具体的,那么我们就找找具体的实现吧,首先我们从LayoutInflater的起源开始。《Android源码中的单例模式》中我们对LayoutInflater进行了分析,从中我们知道,在加载ContextImpl时会通过如下代码将LayoutInflater的ServiceFetcher注册到容器中,具体代码如下:

registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});

这里调用了PolicyManager.makeNewLayoutInflater方法,其源码如下:

public final class PolicyManager {
    // Policy实现类
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {
        // Pull in the actual implementation of the policy at run-time
        // 通过反射构造Policy对象
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        }
    }

    // Cannot instantiate this class
    private PolicyManager() {}

    // The static methods to spawn new policy-specific objects
    // 这里就是创建PhoneWindow对象的地方发
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }

    // 通过sPolicy创建LayoutInflater
    public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context);
    }

    public static WindowManagerPolicy makeNewWindowManager() {
        return sPolicy.makeNewWindowManager();
    }

    public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
        return sPolicy.makeNewFallbackEventHandler(context);
    }
}

PolicyManager中通过反射构造了Policy实现类,这个类实现了IPolicy接口,通过这种形式将具体的Policy类对外进行隐藏实现。PolicyManager实际上就是一个代理类,具体的功能通过sPolicy对象进行实现,我们看看sPolicy对应的Policy类,也就是com.android.internal.policy.impl.Policy:

public class Policy implements IPolicy {
    private static final String TAG = "PhonePolicy";

    private static final String[] preload_classes = {
        "com.android.internal.policy.impl.PhoneLayoutInflater",
        "com.android.internal.policy.impl.PhoneWindow",
        "com.android.internal.policy.impl.PhoneWindow$1",
        "com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
        "com.android.internal.policy.impl.PhoneWindow$DecorView",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
    };

    static {
        // For performance reasons, preload some policy specific classes when
        // the policy gets loaded.
        for (String s : preload_classes) {
            try {
                Class.forName(s);
            } catch (ClassNotFoundException ex) {
                Log.e(TAG, "Could not preload class for phone policy: " + s);
            }
        }
    }

    // 创建PhoneWindow,这就是Activity中Window的具体实现类
    public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }

    // 创建LayoutInflater, 具体类为PhoneLayoutInflater,这才是我们需要关注的地方
    public LayoutInflater makeNewLayoutInflater(Context context) {
        return new PhoneLayoutInflater(context);
    }

    public WindowManagerPolicy makeNewWindowManager() {
        return new PhoneWindowManager();
    }

    public FallbackEventHandler makeNewFallbackEventHandler(Context context) {
        return new PhoneFallbackEventHandler(context);
    }
}

此时,已经很清楚了,真正LayoutInflater的实现类就是PhoneLayoutInflater。我们继续深入看看PhoneLayoutInflater的源代码:

public class PhoneLayoutInflater extends LayoutInflater {
    // 内置View类型的前缀,如TextView的完整路径是android.widget.TextView
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    /** * Instead of instantiating directly, you should retrieve an instance * through {@link Context#getSystemService} * * @param context The Context in which in which to find resources and other * application-specific things. * * @see Context#getSystemService */
    public PhoneLayoutInflater(Context context) {
        super(context);
    }

    protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
    }

    /** Override onCreateView to instantiate names that correspond to the widgets known to the Widget factory. If we don't find a match, call through to our super class. */
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        // 在View名字的前面添加前缀来构造View的完整路径,例如,类名为TextView,
        // 那么TextView的完整路径是android.widget.TextView
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }

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

代码不多,核心的程序语句就是覆写LayoutInflater的onCreateView方法,该方法就是在传递进来的View名字前面加上“android.widget.”或者“android.webkit.”前缀用以得到该内置View类(如TextView、Button等都在android.widget包下)的完整路径。最后,根据类的完整路径来构造对应的View对象。

具体是一个怎样的过程呢?以Activity的setContentView为例,先来看看这个函数的实现:

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}

Activity的setContentView方法实际上调用的是Window的setContentView,而Window是一个抽象类,上文提到Window的具体实现类是PhoneWindow,我们看看PhoneWindow中对应的方法:

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        // 1.当mContentParent为空时先构造DecorView
        // 并将DecorView包裹到mContentParent中
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 2.解析layoutResID
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

分析之前,我们来看看一个Window的View层级图,如下所示:
LayoutInflater源码深度分析笔记_第1张图片

我们看到mDecor中会加载一个系统定义好的布局,这个布局中有包裹了mContentParent,而这个mContentParent就是我们设置的布局,并将添加到parent区域。在PhoneWindow的setContentView方法中也验证了这点,首先会构造mContentParent这个对象,然后通过LayoutInflater的inflater函数将制定布局的视图添加到mContentParent中。那么就先来看看inflate方法:

public View inflate(int resource, ViewGroup root) {
    // root 不为空,则会从resource布局解析到View,并添加到root中
    return inflate(resource, root, root != null);
}

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) + ")");
        }
        // 获取xml资源解析器
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
}

/** * parser : xml解析器 * root : 要解析布局的父视图 * attachToRoot :是否将要解析的视图添加到父视图中 */
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];
            // Context对象
            mConstructorArgs[0] = mContext;
            // 存储父视图
            View result = root;

            try {
                // Look for the root node.
                int type;
                // 找到root元素
                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("**************************");
                }
                // 1.解析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, attrs, false, false);
                } else {
                    // Temp is the root view that was found in the xml
                    // 2. 不是merge标签那么直接解析布局中的视图
                    // 3. 这里就是通过xml的tag来解析layout跟视图
                    // name就是要解析的视图类名,如RelativeLayout
                    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);
                        // 如果attachToRoot为false,那么将给temp设置布局参数
                        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
                    // 解析temp视图下的所有子View
                    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.
                    // 如果Root不为空,且attachToRoot为true,那么将temp添加到父视图中
                    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.
                    // 如果root为空或者attachToRoot为false,那么返回的结果就是temp
                    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;
        }
    }

上述inflate方法中,主要有下面几步:
1. 解析xml中的根标签(第一个元素)
2. 如果跟标签是merge,那么调用rInflate进行解析,rInflate会将merge标签下的所有子View直接添加到根标签中;
3. 如果标签是普通元素,那么运行到代码3,调用createViewFromTag对该元素进行解析;
4. 调用rInflate解析temp根元素下的所有子View,并且将这些子View添加到temp下;
5. 返回解析到的跟视图;

我们先从简单的地方理解,即解析单个元素的createViewFromTag,看看如下代码:

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

        Context viewContext;
        if (parent != null && inheritContext) {
            viewContext = parent.getContext();
        } else {
            viewContext = mContext;
        }

        // Apply a theme wrapper, if requested.
        final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            viewContext = new ContextThemeWrapper(viewContext, themeResId);
        }
        ta.recycle();

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(viewContext, attrs);
        }

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

        try {
            View view;
            // 1. 用户可以通过设置LayoutInflater的factory来自行解析View,默认这些Factory都为空,可以忽略这段
            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);
            }
            // 2.没有Factory的情况下通过onCreateView或者createView创建View
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = viewContext;
                try {
                    // 3.内置View控件的解析
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        // 4.自定义控件的解析
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            if (DEBUG) System.out.println("Created view is: " + view);
            return view;

        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;

        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        }
    }

上面代码重点在代码2及以后的代码,createViewFromTag会将该元素的parent及名字传递过来。当这个tag的名字中没有包含“.”(在名字中查找“.”返回-1)时,LayoutInflater会认为这是一个内置的View,例如,我们在xml中声明一个内置View时大概是这样的:

<TextView  android:id="@+id/textView" android:layout_width="60dp" android:layout_height="60dp"/>

这里的TextView就是xml元素的名字,因此,在执行inflate时就会调用代码3处的onCreateView来解析这个TextView标签。当我们自定义View时,在xml中必须写View的完整路径,例如:

<com.custom.MyView
    android:id="@+id/textView"
    android:layout_width="60dp"
    android:layout_height="60dp"/>

此时,就会调用代码注释4的createView来解析该View。为什么要这么处理,它们之间有什么不同呢?

在上文的PhoneLayoutInflater中我们知道,PhoneLayoutInflater覆写了onCreateView方法,也就是代码3处的onCreateView,该方法就是在View标签名的前面设置一个“android.widget.”前缀,然后在传递给createView进行解析的。也就是说内置View和自定义View最终调用了createView进行解析,只是Google为了让开发者在xml中更方便定义View,只写View名称而不需要写完整的路径。在LayoutInflater解析时如果遇到致谢类名的View,那么认为是内置的View控件,在onCreateView方法中会将”android.widget.”前缀传递给createView方法,最后,在createView中构造View的完整路径来进行解析。如果是自定义控件,那么必须写完整的路径,此时调用createView且前缀为null进行解析。

关于createView已经解释很多了,我们看看他的具体实现吧:

// 通过完整路径的类名通过反射机制构造View对象
public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        // 1. 从缓存中获取构造函数 
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null; try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
            // 2. 如果没有缓存构造函数
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                // 如果prefix不为空,那么构造完整View路径,并且加载该类
                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);
                    }
                }
                // 3. 从Class对象中获取构造函数
                constructor = clazz.getConstructor(mConstructorSignature);
                // 4. 将构造函数存入缓存中
                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;

            constructor.setAccessible(true);
            // 5. 通过反射构造View
            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;

        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;

        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

createView相对比较简单,如果有前缀,那么构造View的完整路径,并且将该类加载到虚拟机中,然后获取该类的构造函数并且缓存起来,在通过构造函数来创建该View的对象,最后将View对象返回,这就是解析单个View的过程。而我们的窗口中是一颗视图树,LayoutInflater需要解析完这棵树,这个功能就交给了rInflate方法,具体代码如下:

void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
            boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
            IOException {
        // 1. 获取树的深度,深度优先遍历
        final int depth = parser.getDepth();
        int type;
        // 2. 挨个元素解析
        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)) {// 解析include标签
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs, inheritContext);
            } else if (TAG_MERGE.equals(name)) {// 解析merge标签,抛出异常,因为merge标签必须为跟视图
                throw new InflateException("<merge /> must be the root element");
            } else {
                // 3. 根据元素名称尽心解析
                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);
                // 将解析到的View添加到viewGroup中,也就是它的parent
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) parent.onFinishInflate();
    }

rInflate通过深度优先遍历来构造视图树,每解析到一个View元素就会递归调用rInflate,直到这条路径下的最后一个元素,然后在回溯过来将每个View元素添加到它们的parent中。通过rInflate的解析之后,整棵视图树就构建完毕。当调用了Activity的onResume之后,我们通过setContentView设置的内容就会出现在我们的视野中。

关于LayoutInflater的分析过程就到这里了。

参考书籍《Android源码设计模式解析与实战》

你可能感兴趣的:(源码,layout,分析,inflater)