• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

Android中的LayoutInflater分析一

android 搞代码 3年前 (2022-03-01) 21次浏览 已收录 0个评论

PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接

ps:本篇文章是基于 Android Api 26 来剖析的

目录

1、LayoutInflater 创立过程

2、LayoutInflater 创立 View 过程

在 Android 中,LayoutInflater 个别叫做为布局解析器或者又叫填充器,LayoutInflater 就是将 XML 布局文件实例化为对应的 View 对象,LayoutInflater 不能间接通过 new 的形式获取,须要通过 Activity.getLayoutInflater() 或 Context.getSystemService() 获取与以后 Context 曾经关联且正确配置的规范 LayoutInflater。

1、LayoutInflater 创立过程

在零碎中,咱们获取 LayoutInflater 对象的形式有2种,一种是通过 LayoutInflater 的 from(Context context) 办法取得;一种是通过 Activity 的 getLayoutInflater办法取得,通过 Activity 的 getLayoutInflater办法取得的 LayoutInflater 最终还是调用 LayoutInflater 的 from(Context context) 办法,如何证实?咱们从 Activity 的 getLayoutInflater办法动手看看;

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

Activity 的 getWindow 办法失去的是 Window 类型的对象,而 Window 的实现类是 PhoneWindow(要想晓得 Window 的实现类是 PhoneWindow 能够看Android中View事件的散发第一篇这篇文章),所以咱们往下看 PhoneWindow 的 getLayoutInflater办法;

@Override
public LayoutInflater getLayoutInflater() {
    return mLayoutInflater;
}

能够看到 PhoneWindow 的 getLayoutInflater 办法间接返回 mLayout-Inflater 字段,那 mLayoutInflater 字段是在哪里赋值的呢?它被赋值的中央只有一次,那就是 PhoneWindow 的构造方法 PhoneWindow(Context context) ;

public PhoneWindow(Context context) {

    super(context);
    mLayoutInflater = LayoutInflater.from(context);

}

从这里能够证实出,通过 Activity 的 getLayoutInflater办法取得的 LayoutInflater 最终还是调用 LayoutInflater 的 from(Context context) 办法。

好了,咱们当初开始进行剖析 LayoutInflater 的创立过程,咱们来看看 LayoutInflater 的 from(Context context) 办法;

public static LayoutInflater from(Context context) {

    
    //1、
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;

}

正文1 这里,用 Context 的 getSystemService(@ServiceName @NonNull String name) 办法来创立 LayoutInflater 对象,然而 Context 的 getSys-temService(@ServiceName @NonNull String name) 办法是一个形象办法,而在于它的实现类上,这里的 context 能够是 Activity、Application 和 Service 中的一个实例,咱们就以 context 是 Activity 实例化的来进行剖析,但在 Activity 中还是没有重写getSystemService(@ServiceName @NonNull String name) 办法,只是在 Activity 的父类 ContextWrapper 进行了重写;

@Override
public Object getSystemService(String name) {
    
    //2、
    return mBase.getSystemService(name);
}

正文2 中的 mBase 是 Context 类型的对象,它的实现类是 ContextImpl(要想晓得为什么mBase 的实现类是 ContextImpl,能够看Android中Application、Activity和Service它们真正干活的Context是什么?这篇文章),所以咱们看 ContextImpl 类的 getSystemService(String name) 办法;

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

看正文3,ContextImpl 类的 getSystemService(String name) 办法调用了 SystemServiceRegistry 的 getSystemService(ContextImpl ctx, String name) 办法;

public static Object getSystemService(ContextImpl ctx, String name) {

    
    //4、
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    
    //5、
    return fetcher != null ? fetcher.getService(ctx) : null;
}

咱们看正文4 的代码,特地是 SYSTEM_SERVICE_FETCHERS 这个常量,它是个 HashMap 类型的,只是看到它通过 key 为 name 而取出 value 为 ServiceFetcher 的操作,那它又是在哪里保留的呢?咱们看一下 SystemServiceRegistry 的动态代码块;

final class SystemServiceRegistry {

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

看正文6 的代码,name 为 Context.LAYOUT_INFLATER_SERVICE,这里把 CachedServiceFetcher(并重写 createService(ContextImpl ctx) 办法返回 PhoneLayoutInflater 对象) 对象作为参数之一并调用 SystemServiceRegistry 的 registerService 办法(参数比拟多,我这不不便列举进去,具体看上面的代码);

private static <T> void registerService(String serviceName, Class<T> serviceClass,

                                        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    
    //7、
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

看正文7 的代码,当 serviceName 为 Context.LAYOUT_INFLAT-ER_SERVICE 时,SYSTEM_SERVICE_FETCHERS 保留的是 CachedServiceFetcher 对象;回到正文4 的代码,fetcher 变量不为空,且实质是 CachedServiceFetcher 对象;再看正文5 的代码,也就是 CachedServiceFetcher(该类是 SystemServiceRegistry 的外部类) 的 getService(ContextImpl ctx) 办法;

static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {

    ......
    @Override
    @SuppressWarnings("unchecked")
    public final T getService(ContextImpl ctx) {
        final Object[] cache = ctx.mServiceCache;
        synchronized (cache) {
            // Fetch or create the service.
            Object service = cache[mCacheIndex];
            if (service == null) {
                try {

                    //8、
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                } catch (ServiceNotFoundException e) {
                    onServiceNotFound(e);
                }
            }
            return (T)service;
        }
    }

    public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;

}

CachedServiceFetcher 的 getService(ContextImpl ctx) 办法返回的对象是通过 CachedServiceFetcher 的 createService(ContextImpl ctx) 办法创立的,下面剖析正文6 的代码的时候就曾经说过 CachedServiceFetcher 对象创立后就重写了 createService(ContextImpl ctx) 办法返回 PhoneLayoutIn-flater 对象,所以 PhoneWindow 创立的 LayoutInflater 实质上是创立 PhoneLayoutInflater 对象。

咱们晓得,通过 Activity、Application 和 Service 都能拿到一个 PhoneLayoutInflater 对象,它们拿到的 PhoneLayoutInflater 对象是有区别的吗?答案是有区别的,Application 和 Service 拿到的是同一个 PhoneLayoutInflater 对象;而每一个 Activity 拿到的是不同的 PhoneLayoutInflater 对象,更别说 Activity 和它们(Application 和 Service)的拿到的是同一个对象了。好了,咱们先剖析 Application 和 Service 拿到的 PhoneLayoutInflater 对象,咱们晓得 Application 和 Service都继承于 ContextWrapper,在 ContextWrapper 的 getSystemService(String name) 办法并没有做非凡解决(看正文2 的代码),都是用 ContextImpl 的 getSystemService(String name) 办法调用 SystemServiceRegistry 的静态方法 getSystemService(ContextImpl ctx, String name) 拿到同一个 CachedServiceFetcher 对象,而且 CachedServiceFetcher 重写的 createService(ContextImpl ctx) 办法只会被调用一次,因为只有 service为空的时候才会被调用(看正文8 的代码),创立好 PhoneLayoutInflater 对象后将其保留到 cache 数组中,所以Application 和 Service 拿到的是同一个 PhoneLayoutInflater 对象。

然而 Activity 就不一样了,Activity 继承了 ContextThemeWrapper 并让 ContextThemeWrapper 重写了 getSystemService(String name)办法;

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

看正文9 的代码并通过后面的剖析,LayoutInflater.from(getBaseContext()) 这行代码实质上拿到的是 PhoneLayoutInflater 对象,而后调用 PhoneLayoutInflater 的 cloneInContext(Context newContext)办法;

public LayoutInflater cloneInContext(Context newContext) {

    
    //10、
    return new PhoneLayoutInflater(this, newContext);
}

看见正文10 的代码没有,每个 Activity 拿到的 PhoneLayoutInflater 都是从新创立的,咱们再往下追踪 PhoneLayoutInflater的结构器;

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

看正文11 的代码,这里的 super 示意 LayoutInflater,咱们再跟踪父类 LayoutInflater的结构器;

protected LayoutInflater(LayoutInflater original, Context newContext) {

    mContext = newContext;
    mFactory = original.mFactory;
    mFactory2 = original.mFactory2;
    mPrivateFactory = original.mPrivateFactory;
    setFilter(original.mFilter);
}

参数 original 是利用过程级的 LayoutInflater,即在 SystemServiceRegistry 中保留,并把利用过程级的配置给了Activity 的 LayoutInflater ,这也合乎了 LayoutInflater 的自我介绍 “ 且是正确配置的规范 LayoutInflater ”。

2、LayoutInflater 创立 View 过程

在日常开发中,咱们也会用到 LayoutInflater来创立一个 View ,常写的语句如下所示;

LayoutInflater.from(context).inflate(R.layout.item,root,false);

首先 LayoutInflater.from(context) 拿到的必定是 PhoneLayoutInflater对象,咱们来看一下 PhoneLayoutInflater 的 inflate 办法(该办法在 LayoutInflater 里实现,因为 PhoneLayoutInflater 继承于 LayoutInflater,还有一个是该办法参数太多,我不不便写,具体参数看上面的代码,我又给它另起名 method1);

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {

    ......
    try {
        //12、
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

看正文12 的代码,PhoneLayoutInflater 的 inflate 办法(method1)又调用重载的另一个 inflate 办法(该办法也是在 LayoutInflater 里实现,参数不一样,同时参数太多不不便写,具体参数看上面的代码,我又给它另起名 method2);

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {

    synchronized (mConstructorArgs) {
        ......
        //13、
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;

        //14、
        View result = root;

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

            //15、
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            ......
            //16、
            if (TAG_MERGE.equals(name)) {

                //17、
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                //18、
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                //19、
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                //20、
                if (root != null) {
                    ......
                    // Create layout params that match root, if supplied
                    //21、
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        //22、
                        temp.setLayoutParams(params);
                    }
                }
                ......
                // Inflate all children under temp against its context.
                //23、
                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.
                //24、
                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.
                //25、
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            ......
        } catch (Exception e) {
            ......
        } finally {
            ......
        }

        return result;
    }

}

剖析下面的 PhoneLayoutInflater 的 inflate 办法(method2)之前,咱们先给出2张图;

图片

                                         图1


图片

图2

正文13 的代码示意获取 xml 布局文件的属性;正文14 的代码示意保留根视图,最终会将根视图返回;正文15 的代码示意如果找不到根标签,那么就会抛出异样;正文19 的代码示意创立具体的 View;正文21 的代码示意如果根视图 root 不为空,那么通过 root 创立对应的 LayoutParams 对象;正文22 的代码示意如果创立的 View 不须要增加到根视图 root 中,那么间接设置 View 的 LayoutParams;正文22 的代码示意如果根视图 root 不为空且须要增加到 root 中,那么就将创立好的 View 增加到 root 外面;正文25 的代码示意如果根视图 root 为空且不须要增加根视图外面,那么就用第一次创立的 View 作为根视图。

上面咱们举个例子,再具体解释一下,假如图1 的布局文件叫 layout_1.xml,图2 的布局文件叫 layout_2.xml,root 为 Activity 里的 setContentView 办法外面的布局文件里的一个 ViewGroup,那么我已知的3个货色创立一个 View,看上面代码咯(上面代码的 this,我假如它是 Activity);

    //26、
    View view = LayoutInflater.from(this).inflate(R.layout.layout_1,root,false);
    
    //27、
    View view2 = LayoutInflater.from(this).inflate(R.layout.layout_1,root,true);
    
    //28、
    View view3 = LayoutInflater.from(this).inflate(R.layout.layout_1,null,false);
    
    //29、
    View view4 = LayoutInflater.from(this).inflate(R.layout.layout_2,root,true);

看正文26 的代码中的 inflate 办法(method2),因为 layout_1.xml 文件中根标签不是 merge,而是 RelativeLayout ,所以会执行正文19 的代码,因为 root 不为空,attachToRoot 为 false,所以也会执行正文20、21、22 的代码,正文26 的代码执行完后最终返回 root。

看正文27 的代码中的 inflate 办法(method2),因为 layout_1.xml 文件中根标签不是 merge,而是 RelativeLayout ,所以会执行正文19 的代码,因为 root 不为空,attachToRoot 为 true,所以会执行正文20、21 、24的代码,正文27 的代码执行完后最终返回 root。

看正文28 的代码中的 inflate 办法(method2),因为 layout_1.xml 文件中根标签不是 merge,而是 RelativeLayout ,所以会执行正文19 的代码,因为 root 为空,attachToRoot 为 false,所以会执行正文25的代码,正文28 的代码执行完后最终返回 RelativeLayout 对象。

看正文29 的代码中的 inflate 办法(method2),因为 layout_2.xml 文件中根标签是 merge,所以会执行正文16 的代码,正文16 的代码示意判断是否是 merge 标签,这里的 root 不能为空,且 attachToRoot 要为true,不然会执行正文17 的代码引发报错,29 的代码执行完后最终返回 root。

正文18 的代码和正文23 的代码实质上都是解析子标签,只是参数不一样而已,都是调用同一个办法;本篇文章先写到这里,因为还没有剖析完,正文18、23 的代码剖析,下一篇文章见。

本篇文章写到这里就完结了,因为技术水平无限,文章中难免会出错,欢送大家批评指正。


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android中的LayoutInflater分析一

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址