美文网首页
撸LayoutInflater源码

撸LayoutInflater源码

作者: zhujiaqqq | 来源:发表于2019-11-26 01:08 被阅读0次

    入口:
    Activity.setContentView(@LayoutRes int layoutResID)做了什么?

    每个Activity都要设置一个布局:

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

    这里调用了getWindow().setContentView(layoutResID);,Window 类的实现类PhoneWindow。查看PhoneWindow的setContentView方法:

    public void setContentView(int layoutResID) {
        ...
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
    }
    public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }
    

    这里最终使用LayoutInflater加载布局。


    LayoutInflater的使用只有一句话:

            LayoutInflater.from(mContext).inflate(resId, contentParent);
    

    可以分为两个部分:

    • LayoutInflater.from(mContext)通过mContext获取LayoutInflater对象
    • inflate(resId, contentParent)将布局resId转化层View,加入contentParent中

    获取LayoutInflater对象

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

    这里使用了getSystemService方法去获取LayoutInflater对象,所以进入ContextImpl类的getSystemService()方法:

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

    这里获取的是SystemServiceRegistry中的service,去SystemServiceRegistry中查找:

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

    可以看到,在SystemServiceRegistry类被加载的时候,在static静态块中注册了LAYOUT_INFLATER_SERVICE。这里直接返回了PhoneLayoutInflater类的对象,PhoneLayoutInflater是LayoutInflater的子类,在LayoutInflater的基础上重写了onCreateView方法,为系统自带的View添加前缀:

    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            View view = createView(name, prefix, attrs);
        ...
        }
        return super.onCreateView(name, attrs);
    }
    

    所以,LayoutInflater.from(mContext)最终返回一个LayoutInflater的子类PhoneLayoutInflater的对象。

    加载布局文件

    LayoutInflater加载布局依靠inflate方法:

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

    这里调用了inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法:

    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;
    
            // 找到START_TAG标签,即整个布局的开头
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }
            final String name = parser.getName();
            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");
                }
                // 如果是布局开头是一个merge标签,那就直接递归去解析标签里面的view
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // 这里先创建根布局的View,例如LinearLayout\RelativeLayout等
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ViewGroup.LayoutParams params = null;
                if (root != null) {
                    // 将parser中获取的布局属性转换层LayoutParams,然后放入根布局的view中
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        temp.setLayoutParams(params);
                    }
                }
                // 递归解析根View下的所有布局,转换成View
                rInflateChildren(parser, temp, attrs, true);
                // attachToRoot在入参中设置为false,不会走下面的代码,
                //如果attachToRoot为true的话,就会将解析得到的布局View加入传入的根View
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
            }
            return result;
        }
    }
    

    inflate方法的主体流程就是这些,这里主要看以下几个方法:

    • 创建根布局的View:createViewFromTag(root, name, inflaterContext, attrs);
    • 遍历创建子布局:rInflateChildren(parser, temp, attrs, true);
    • 最终创建View的方法:view = createView(name, prefix, attrs);

    创建根布局的View:

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
    
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        try {
            View view;
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    // 如何标签的名字没有“.”,那么就是系统自带的View,如TextView等
                    // 使用onCreateView(parent, name, attrs)方法
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        // 如果是有“.”,说明是自定义的View,或者扩展库的View
                        // 使用createView(name, null, attrs);方法
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
        }
    

    这里的onCreateView(parent, name, attrs)createView(name, null, attrs)的区别在于onCreateView使用了PhoneLayoutInflater重写的onCreateView方法,将系统View添加了前缀然后再使用createView方法进行创建View。

    遍历创建子布局

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
    
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        // 获取当前深度
        final int depth = parser.getDepth();
        int type;
        // 遍历整个View树
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            final String name = parser.getName();
                // 获取通过标签获取View
                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);
                // 将获取的View插入父View中
                viewGroup.addView(view, params);
        }
    }
    

    这根布局下创建子布局的View通过在While循环下进行递归,一层一层的实现

    最终创建View的方法

    最终创建View的方法在createView中:

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        // 获取当前标签对于的view的构造器
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        // 如果构造器无效,则从缓存中删除
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;
    
        if (constructor == null) {
            // 如果构造器为空,通过类名去获取构造器,并加入缓存中
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
            // 这里限定构造器的入参是:Context.class, AttributeSet.class两个类型的对象
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } 
    
        Object lastContext = mConstructorArgs[0];
        if (mConstructorArgs[0] == null) {
            mConstructorArgs[0] = mContext;
        }
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        // 通过反射创建View对象
        final View view = constructor.newInstance(args);
        if (view instanceof ViewStub) {
            //对ViewStub进行异步加载处理
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        mConstructorArgs[0] = lastContext;
        return view;
    }
    

    从代码中可以看出,最终创建View是通过反射的方式创建,并且构造方法必须是两个入参的那种。


    总结

    • LayoutInflater的创建最终是一个PhoneLayoutInflater对象,只是通过SystemService在类加载的时候创建,并没有跨进程的操作。
    • LayoutInflater在解析布局文件的时候,是通过XmlResourceParser完成的。布局文件是一个树形结构,xml的解析就是一层层的解析,所以View的创建也是一层层创建,创建的时候使用递归的形式完成。
    • 在LayoutInflater中,View的创建是通过反射完成的,效率并不高;View反射创建的时候是通过调用View的两个入参的构造方法,所以在写自定义View的时候如果你的View要在布局文件中插入,就必须要重写View的两个入参的构造方法。

    相关文章

      网友评论

          本文标题:撸LayoutInflater源码

          本文链接:https://www.haomeiwen.com/subject/ssbswctx.html