LayoutInflater--布局文件的加载

858 阅读7分钟

今天这篇文章,分析探究布局文件如何加载;既LayoutInflater如何加载布局文件的过程。在分析此过程中,也解决自定义View中构造方法的调用,以及单例模式的应用。

单例模式获取layoutInflater

获取layoutInflater

LayoutInflater.from(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的getSystemService去获取的。接下里我们看一下context里面相关的实现。

context获取服务

context之前将activity添加view时,其的创建以及分析过,我们知道其实现是ContextImpl,接下来,我们直接查看给类。

  1. 服务获取
@Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

    @Override
    public String getSystemServiceName(Class<?> serviceClass) {
        return SystemServiceRegistry.getSystemServiceName(serviceClass);
    }

最终是通过SystemserviceRegistry去获取的,那SystemserviceRegistry是什么,其何时创建的? 2. SystemserviceRegistr

  • 其内部有两个静态的hashMap进行服务名何fetch的存放
  • 内部的静态代码块会注册服务
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<?>>();

static {
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});
            ...
}

  • 注册服务时,分别加入两个集合中
  • 获取服务时,首先获取ServiceFetch,然后再利用Servicefetch获取具体的服务对象
public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

public final T getService(ContextImpl ctx) {
            synchronized (StaticServiceFetcher.this) {
                if (mCachedInstance == null) {
                    try {
                        mCachedInstance = createService();
                    } catch (ServiceNotFoundException e) {
                        onServiceNotFound(e);
                    }
                }
                return mCachedInstance;
            }
        }

layoutInflater的创建

 public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});

这样我们知道其实现类为PhoneLayoutInflater

  • 其只是对onCreateView方法进行了重写,进行前缀的添加
private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
  • 这就是我们android中ui的所在包

总结

  • 所以我们后期获取系统提供的服务都是通过从这个Map中去查询,保证了其唯一行,
  • 而静态代码块,只会在第一次加载类是调用,保证后面不会重复创建
  • 获取时,通过DCL,去获取对象,空的化,根据注册时,不同fetch对cratefetch的重写逻辑去创建,不为空就直接返回
  • 给实现是容器单例模式的稍微变化了一下,容器里面去获取响应的类,在给类的方法在利用懒汉模式实现单例。我们时间开发中需要根据不同情况创建不同的对象,但还须保证唯一,可使用此中设计方法

LayoutInflater加载布局

这里我们直接从加载一个布局进行分析,既inflate(resource,root,boolean). 因为setContentView通过逐级调用,其内部也是调用的该方法.

//Phonewindow
public void setContentView(int layoutResID) {
       
mLayoutInflater.inflate(layoutResID, mContentParent);
    }

inlfate加载布局

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // 查找根节点
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                ...
                //判断如果是Merge标签,将其里面的view,添加到根view中
                if (TAG_MERGE.equals(name)) {
                    //对root进行校验
                    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 {
                    // 依次查找root下的标签创建view
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        
                        //如果设置了布局参数,创建布局参数,匹配rootview, 
                        params = root.generateLayoutParams(attrs);
                        //根据是否要将布局中的view添加到根view的等级中,如果false的或,不添加上去,只是用来创建view,然后返回
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }

                    //加载tempView下的子view
                    rInflateChildren(parser, temp, attrs, true);

                     将tempview添加到root上  
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // attachToRoot false,不添加到根上,返回view
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } 
            return result;
        }
    }

根据上面的代码总结为下面几个步骤:

  • 解析xml中的根标签
  • 处理merge标签,如果是,将其里面的view,解析到根view。通过rInflate方法
  • 普通标签,调用createViewFromTag方法创建view,
  • 判断是否要添加到根view,设置布局参数
  • 通过rInflateChildren,加载其下面的子view。
  • 最后根据是否attachRoot,返回root,或自己。

创建View(createViewFromTag发起)

这里我们可以找到view构造方法的调用,在布局中具体调用那个构造方法

createViewFromTag(...){
    ...
    try {
            View view
            //我们知道创建的是PhoneLayout,而起调用的构造方法为super(context)
            //所以默认factory都为空,我们可以通过setfactory方法设置
            //这段代码可以忽略
            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 && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            //根据是否有.,传递不同的参数,这里区分了自定义view和系统包下的view
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    //系统包下的view
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        //自定义view
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
}

通过分析该方法,我们知道了自定义view时,为什么要写完整的路径,,这是因为系统的View,其会自动添加,在三个包下去查找。

createView

这里最终会创建类,即会调用不同的构造方法

  • 获取缓存中的构造方法
 Constructor<? extends View> constructor = sConstructorMap.get(name);
  • 如果有,且可用,
  • 判断是否设置了过滤规则,如有进行响应的过滤,发现禁止加载,抛出异常。
if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
}

if (mFilter != null) {

    Boolean allowedState = mFilterMap.get(name);
    if (allowedState == null) {

       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);
     }
}     
                                                 
  • 用缓存中的构造方法创建实例
 final View view = constructor.newInstance(args);

接下来分析没有缓存,既第一次加载,

  • 首先加载给类
  • 然后也是判断过滤规则,这里不分析了
  • 然后其调用getConstructor方法创建实例,该方法就是反射创建类,其会根据参数调用不同的方法,因此我们只要知道其参数是什么,就知道布局中的view实例调用那个构造方法了
if (constructor == null) {
     // Class not found in the cache, see if it's real, and try to add it
            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);               
}

//构造传递的参数
static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};
总结

通过分析布局View的创建,我们知道其默认调用的是两个参数的方法。因此我们在自定义的view构造方法,需要重写,让其最终调用第四个方法。最全的那个方法。我们看系统源码也是。例如TextView

public TextView(Context context) {
        this(context, null);
    }

    public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.textViewStyle);
    }

    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }


reinlfate加载视图树

无论是merge标签下,还是加载子view最终都是调用该方法,因此我们可以分析该方法得到如何加载出视图树的。

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

            //处理设置了requestfocuse的标签,这个没用过
            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
               
            } else if (TAG_TAG.equals(name)) { //处理tag标签
                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, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) { //merge标签只能在根布局下使用
                throw new InflateException("<merge /> must be the root element");
            } else {
                //更加特定的标签创建view
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //递归调用加载子view
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

总结

到这里,我们知道merge只能在根视图下使用,视图树的加载是深度优先递归调用

结束语

layoutInflater加载就分析完了,下一篇研究一下AsyncLayoutInflater,看一下这个如何使用,可以优化我们的应用启动。