单例模式-Android源码LayoutInflater.inflater()解析View视图

再说源码之前,我们分析一波单例模式在Android源码中的体现:

类型

恶汉模式:
懒汉模式:
双重锁模式:
枚举单列模式:使用此模式,是为了防止对象在反序列化时重新生成新的对象。所以需要进行序列化操作,并且里面的成员变量如果不是Java的基本类型也需要进行序列化操作。
容器单例模式:通过临时存储空间来存储对象,比如HasMap

UML

在这里插入图片描述

源码中的体现:

LayoutInflater的单例模式

一般我们在ListView的getView以及fragment中新建fragment会使用到。
View view =LayoutInflater.from(mContext).inflate(R.layout.activity_main,null);
通常我们使用 LayoutInflater.from(mContext) 来获取LayoutInflater 服务。

在这里插入图片描述

这个常量的含义是获取名字叫 layout_inflater 的系统服务。
会继续执行 ContextImpl 里面的此方法

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

   public static String getSystemServiceName(Class<?> serviceClass) {
        return SYSTEM_SERVICE_NAMES.get(serviceClass);
    }

我们先来解释下 SystemServiceRegistry 此类
该类在 ConTextImpl 进行初始化

上述的 LAYOUT_INFLATER_SERVICE 对应的服务在 SystemServiceRegistry 类中进行初始化,此类中通过静态代码块注册了一系列的系统服务,我们的 LAYOUT_INFLATER_SERVICE 也是其中的一种。

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

到了这边我们算是清楚了,大部分的系统服务是存放在一个Map里面的,这也就说明了 LayouInflater的使用是单例模式了,比如AccessibilityManager、ActivityManager 等系统服务的创建是种单例模式,采用的容器的方法来实现单例模式。

    private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
            new HashMap<Class<?>, String>();
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
   private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

LayoutInflater 的是如何解析View的

入口依据是 inflater()方法

View  view =LayoutInflater.from(mContext).inflate(R.layout.item_main,null);

在我们 register 服务的时候,PhoneLayoutInflater 很显然就是我们最终要查询的类了,此处6.0与7.0的源码发生了改变,在7.0之后是通过PolicyManager。makeNewLaytouInflater 来代理实现,并创建 PhoneLayoutInflater 的类,在 6.0 上面就没有通过PolicyManager来实现,而是直接 new 一个 PhoneLayoutInflater ,在。我们以6.0为主

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

此处代码比较少,就全部贴出来了,值得注意的是 sClassPrefixList 是干嘛用的,
此处的 onCreateView 不就是给我们系统自带的 View 加上完整包路,例如 TextView,在解析 XMl 的时候会拼上前缀形成 android.widget.TextView ,最后根据类的完整路线来创建具体的对象。

public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    public PhoneLayoutInflater(Context context) {
        super(context);
    }

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

    @Override 
    protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
            }
        }

        return super.onCreateView(name, attrs);
    }

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

接下去我们在说下 LayoutInflater 到底是怎么实现解析 XML 里面的 View,这个还得从Activity的 setContentView(view) 说起
setContentView(R.layout.activity_main);

此处的 getWindow 返回的就是View的容器 PhoneWindow。mWinodw的创建是 Activity 的 attah方法中进行的

  final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }
 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    

那么又是哪里调用Activity 的attach方法的呢?

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
             activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }

            }

        return activity;
    }

到了这边总算把 getWindow() 的逻辑也理清楚了,结论就是 getWindow() 指的是mWindow,而 mWindow 具体指 PhoneWindow ,PhoneWindow 是在Activity 的attach 创建的,而 attach 的调用时机是在 Activity 的 performLaunchActivity 时,比 onCreate 方法还早些。现在终于安心进入 PhoneWindow了

进入 PhoneWindow 后看 setContentView 方法,会发现本质是调用 LaytouInflater 里面的方法了,而 LayoutInflater 是一个单例对象。

    @Override
    public void setContentView(int layoutResID) {
   
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

     mLayoutInflater.inflate(layoutResID, mContentParent);
 
    

到这边,我们才进入解析 XML 的过程

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        //首先把 XML 转换成 XML 资源
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);
        try {
        // parser 代表 XML 解析器,root 代表父类布局 ,attachToRoot 代表
        //是否添加到 root 视图 
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

具体解析类

 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) {
                    // Empty
                }
                //获取 xml 资源的第一个字符串
                final String name = parser.getName();
                //如果是merge的话,则直接解析root下面的视图树
                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 的 tag 来解析 layout 的根视图。name 就是要 
                    //解析的视图的类名。如 LinearLayout。
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
    
                            temp.setLayoutParams(params);
                        }
                    }
                   rInflate(parser, temp, attrs, true);

                
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

        }

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

            return result;
        }
    }

此方法做了以下事情:
1、根据 id,生成 XmlPullParser
2.如果是 merge ,则直接解析 merge 下的所有子View,这也说明了 为什么 merge 标签为什么会减少一层视图的原因,因为根本没有进入 else 的逻辑。
3.如果不是 merge,则进入 else,调用 createViewFromTag 来解析根视图,并赋值给temp
4.调用 rInflate ,解析 temp 下面的视图,并将这些视图添加到 temp 中。

而 createViewFromTag 方法中有一段很有意思的代码

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

        // Apply a theme wrapper, if allowed and one is specified.
        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)) {
            // Let's party like it's 1995!,闪烁布局,本质还是个容器,闪烁
            //频率500毫秒,不支持修改
            return new BlinkLayout(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;

    }

主要还是看 try 里面的,当 name 没有包含 ”.“ 的时候,我们认为他是内置布局组件,此处是根视图的布局组件。当调用 onCreateView 的时候,PhoneLayoutInflater 类会对此方法重写,所以就会拼接上类的完整路径。反之则是自定义布局.两个方法最后都会调用 createView

    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 {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                //如果 prefix 不为空,则需要填写完整路径
                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);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
            、、、
             Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            

到这里,根据完整包名的路径,通过 newInstance 创建了具体的类。此时就结束了 View 的创建,这是单个 View 的创建逻辑。
最后再看下怎么变量视图树的

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

分为 3 步:
1.获取树的高度,深度优先遍历
2.while 循环,挨个元素进行解析
3.根据元素名进行解析,递归调用进行解析,将解析到的 View 添加到 ViewGroup 中。

#类型
恶汉模式:
懒汉模式:
双重锁模式:
枚举单列模式:使用此模式,是为了防止对象在反序列化时重新生成新的对象。所以需要进行序列化操作,并且里面的成员变量如果不是Java的基本类型也需要进行序列化操作。
容器单例模式:通过临时存储空间来存储对象,比如HasMap
#伪代码

#UML

#源码中的体现:
##LayoutInflater的单例模式
一般我们在ListView的getView以及fragment中新建fragment会使用到。
View view =LayoutInflater.from(mContext).inflate(R.layout.activity_main,null);
通常我们使用 LayoutInflater.from(mContext) 来获取LayoutInflater 服务。

这个常量的含义是获取名字叫 layout_inflater 的系统服务。
会继续执行 ContextImpl 里面的此方法

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

   public static String getSystemServiceName(Class<?> serviceClass) {
        return SYSTEM_SERVICE_NAMES.get(serviceClass);
    }

我们先来解释下 SystemServiceRegistry 此类
该类在 ConTextImpl 进行初始化

上述的 LAYOUT_INFLATER_SERVICE 对应的服务在 SystemServiceRegistry 类中进行初始化,此类中通过静态代码块注册了一系列的系统服务,我们的 LAYOUT_INFLATER_SERVICE 也是其中的一种。

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

到了这边我们算是清楚了,大部分的系统服务是存放在一个Map里面的,这也就说明了 LayouInflater的使用是单例模式了,比如AccessibilityManager、ActivityManager 等系统服务的创建是种单例模式,采用的容器的方法来实现单例模式。

    private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
            new HashMap<Class<?>, String>();
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
   private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

LayoutInflater 的是如何解析View的

入口依据是 inflater()方法

View  view =LayoutInflater.from(mContext).inflate(R.layout.item_main,null);

在我们 register 服务的时候,PhoneLayoutInflater 很显然就是我们最终要查询的类了,此处6.0与7.0的源码发生了改变,在7.0之后是通过PolicyManager。makeNewLaytouInflater 来代理实现,并创建 PhoneLayoutInflater 的类,在 6.0 上面就没有通过PolicyManager来实现,而是直接 new 一个 PhoneLayoutInflater ,在。我们以6.0为主

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

此处代码比较少,就全部贴出来了,值得注意的是 sClassPrefixList 是干嘛用的,
此处的 onCreateView 不就是给我们系统自带的 View 加上完整包路,例如 TextView,在解析 XMl 的时候会拼上前缀形成 android.widget.TextView ,最后根据类的完整路线来创建具体的对象。

public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    public PhoneLayoutInflater(Context context) {
        super(context);
    }

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

    @Override 
    protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
            }
        }

        return super.onCreateView(name, attrs);
    }

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

接下去我们在说下 LayoutInflater 到底是怎么实现解析 XML 里面的 View,这个还得从Activity的 setContentView(view) 说起
setContentView(R.layout.activity_main);

此处的 getWindow 返回的就是View的容器 PhoneWindow。mWinodw的创建是 Activity 的 attah方法中进行的

  final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }
 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    

那么又是哪里调用Activity 的attach方法的呢?

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
             activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }

            }

        return activity;
    }

到了这边总算把 getWindow() 的逻辑也理清楚了,结论就是 getWindow() 指的是mWindow,而 mWindow 具体指 PhoneWindow ,PhoneWindow 是在Activity 的attach 创建的,而 attach 的调用时机是在 Activity 的 performLaunchActivity 时,比 onCreate 方法还早些。现在终于安心进入 PhoneWindow了

进入 PhoneWindow 后看 setContentView 方法,会发现本质是调用 LaytouInflater 里面的方法了,而 LayoutInflater 是一个单例对象。

    @Override
    public void setContentView(int layoutResID) {
   
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

     mLayoutInflater.inflate(layoutResID, mContentParent);
 
    

到这边,我们才进入解析 XML 的过程

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        //首先把 XML 转换成 XML 资源
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);
        try {
        // parser 代表 XML 解析器,root 代表父类布局 ,attachToRoot 代表
        //是否添加到 root 视图 
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

具体解析类

 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) {
                    // Empty
                }
                //获取 xml 资源的第一个字符串
                final String name = parser.getName();
                //如果是merge的话,则直接解析root下面的视图树
                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 的 tag 来解析 layout 的根视图。name 就是要 
                    //解析的视图的类名。如 LinearLayout。
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
    
                            temp.setLayoutParams(params);
                        }
                    }
                   rInflate(parser, temp, attrs, true);

                
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

        }

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

            return result;
        }
    }

此方法做了以下事情:
1、根据 id,生成 XmlPullParser
2.如果是 merge ,则直接解析 merge 下的所有子View,这也说明了 为什么 merge 标签为什么会减少一层视图的原因,因为根本没有进入 else 的逻辑。
3.如果不是 merge,则进入 else,调用 createViewFromTag 来解析根视图,并赋值给temp
4.调用 rInflate ,解析 temp 下面的视图,并将这些视图添加到 temp 中。

而 createViewFromTag 方法中有一段很有意思的代码

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

        // Apply a theme wrapper, if allowed and one is specified.
        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)) {
            // Let's party like it's 1995!,闪烁布局,本质还是个容器,闪烁
            //频率500毫秒,不支持修改
            return new BlinkLayout(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;

    }

主要还是看 try 里面的,当 name 没有包含 ”.“ 的时候,我们认为他是内置布局组件,此处是根视图的布局组件。当调用 onCreateView 的时候,PhoneLayoutInflater 类会对此方法重写,所以就会拼接上类的完整路径。反之则是自定义布局.两个方法最后都会调用 createView

    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 {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                //如果 prefix 不为空,则需要填写完整路径
                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);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
            、、、
             Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            

到这里,根据完整包名的路径,通过 newInstance 创建了具体的类。此时就结束了 View 的创建,这是单个 View 的创建逻辑。
最后再看下怎么变量视图树的

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

分为 3 步:
1.获取树的高度,深度优先遍历
2.while 循环,挨个元素进行解析
3.根据元素名进行解析,递归调用进行解析,将解析到的 View 添加到 ViewGroup 中。

好啦,今天就到这边了,有问题或者不足之处的可以给我留言,我会一一解答的,大家加油!

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页