setContentView()及LayoutInflater布

2021-07-29  本文已影响0人  碧云天EthanLee
概述

这回我们来分析一下 Activity布局加载的setContentView()的整个流程。这一流程将细化为3个部分来分析:setContentView系统布局加载流程、LayoutInflater初始化分析、LayoutInflater布局加载流程

一、setContentView系统布局加载流程

我们来看看 Activity的 setContentView()方法:

    // Activity.java
    // mWindow 实例
    mWindow =new PhoneWindow(this,window, activityConfigCallback);
   // ......
    public Window getWindow() {
        return mWindow;
    }
    public void setContentView(@LayoutRes int layoutResID) {
        //注释 1 mWindow是 PhoneWindow。我们到 PhoneWindow去看 setContentView() 方法
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

   // PhoneWindow.java
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            //注释 2, 如果 mContentParent等于空,创建一个 DecorView(mContentParent 就是 ContentView)
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        // 注释 3, 将我们页面的布局记载道 id为 R.id.content(mContentParent) 的布局里
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }

上面我们看到,Activity的 setContentView方法调用了变量 mWindow的方法,而mWindow的实现类是PhoneWindow,看上面注释。

我们看下上面注释2、3,PhoneWindow的 setContentView方法先会判断布局变量mContentParent 是否等于空。如果等于空,在上面注释 2的地方初始化 DecorView。然后在注释 3的地方将我们在activity_main_layout自己定义的布局加载到 mContentParent布局里,而这个 mContentParent布局就是这个 id为 R.id.content 的Layout,这个下面会看到。现在我们来看看注释 2处 DecorView创建的过程:

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //注释 4, 如果 mDecor等于null,创建一个 DecorView
            mDecor = generateDecor(-1);
            ......
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //注释 5 创建一个id为 R.id.content 的布局(也就是contentView),这就是我们 activity_main_layout的父布局
            mContentParent = generateLayout(mDecor);
        }
}

 protected DecorView generateDecor ( int featureId){
            // 创建一个 DecorView
            return new DecorView(context, featureId, this, getAttributes());
        }

上面注释 4处创建了一个 DecorView 对象赋给了变量 mDecor。然后注释 5是 mContentParent 的初始化过程,下面看一下:

     protected ViewGroup generateLayout (DecorView decor){
            //注释 6, int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
            // 获取到 id为 R.id.content的Layout
            ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
            // .......
            if (
                //注释 7, 一些列 if else判断
            ) {......} else {
                // Embedded, so no decoration is needed.
                layoutResource = R.layout.screen_simple;
                // System.out.println("Simple!");
            }
            //注释 8, 在 DecorView里再添加一层 id为layoutResource 的系统布局
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
            //注释 9, 返回 content的Layout,赋值给上面的 mContentParent变量
            return contentParent;
        }

 //  DecorView.java
        void onResourcesLoaded (LayoutInflater inflater,int layoutResource){
            ......
            // 创建系统布局
            final View root = inflater.inflate(layoutResource, null);
            ......
            // Put it below the color views.
            // 在 DecorView里再添加一层 id为layoutResource 的系统布局
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            ......
        }

上面注释 6的地方开始创建这个 id为R.id.content的布局,并最终返回给上面的mContentParent 变量。然后注释 7这个地方有一系列判断,最终会选择一个系统布局,在注释 8的地方把这个系统布局加载到我们的 DecorView里。而且需要注意的是这个系统布局里会有一个叫 R.id.content的 View。

总结一下,Activity的setContentView是通过 其持有的 PhoneWindow来加载布局的。PhoneWindow会先创建一个 DecorView加载进来,而DecorView在创建时又会加载一个系统布局添加给 DecorView,而这个系统布局又包含了一个叫做 R.id.content的布局,此布局最终会加载并赋值给 mContentParent 变量,最后我们自己在 activity里定义的activity_main_layout会加载进 mContentParent。

下面是一个简单的层级图:


setContentView.png
二、LayoutInflater初始化分析

下面到 LayoutInflater布局加载分析。分析布局加载之前我们先看一下 LayoutInflater的初始化过程。

1.View view = View.inflate(this,id_view, viewParent);
2.View view = LayoutInflater.from(this).inflate(id_view, viewParent);
3.View view = LayoutInflater.from(this).inflate(id_view, viewParent, false);

我们常用的布局加载有上面几种用法,其实最终都会走到第3种方式。第3种方式最后一个 boolean型参数表示是否将创建的布局添加到父布局。下面我们先看看 LayoutInflater.from(this)这个方法,LayoutInflater是怎么初始化的:

//LayoutInflater .java
 public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
            // 这里点进去是抽象方法,找到 context的实现类 ContextImpl的 getSystemService方法
           (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                // ......
                return LayoutInflater;
            }

上面是调用了 context的 getSystemService方法,我们找到实现类 ContextImpl看一下:

 // ContextImpl.java
            public Object getSystemService (String name){
                // ......
                // 在 SystemServiceRegistry.java里拿
                return SystemServiceRegistry.getSystemService(this, name);
            }
// SystemServiceRegistry.java
            public final class SystemServiceRegistry {
                // 单例,用于缓存系统服务
                private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
                        new ArrayMap<String, ServiceFetcher<?>>();
                static {
                    // 注册系统布局加载 Service
                    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                            new CachedServiceFetcher<LayoutInflater>() {
                                @Override
                                public LayoutInflater createService(ContextImpl ctx) {
                                    return new PhoneLayoutInflater(ctx.getOuterContext());
                                }
                            });
                    // 这个静态块里注册了 N多个系统服务
                    // 省略......
                }

                public static Object getSystemService(ContextImpl ctx, String name) {
                    // 从缓存内中获取系统服务
                    final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
                    final Object ret = fetcher.getService(ctx);
                    // ......
                    return ret;
                }
            }

上面一直找,找到了 SystemServiceRegistry类里面。原来 LayoutInflater在SystemServiceRegistry 初始化块里面会被注册成一个系统服务,而且以单例的形式保存在 ArrayMap列表里。

三、LayoutInflater布局加载流程

下面我们来分析LayoutInflater加载布局的流程,看它的inflate方法:

public View inflate ( @LayoutRes int resource, @Nullable ViewGroup root){
            return inflate(resource, root, root != null);
        }

        public View inflate ( @LayoutRes int resource, @Nullable ViewGroup root,
        boolean attachToRoot){
            // 获取用于资源加载的 Resources对象
            final Resources res = getContext().getResources();

            //注释 10, 用来根据布局文件的xml预编译生成dex,然后通过反射来生成对应的View,
           //从而减少XmlPullParser解析Xml的时间。
            View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
            if (view != null) {
                return view;
            }

            // 获取 XmlResourceParser解析器,用于解析布局
            XmlResourceParser parser = res.getLayout(resource);
            try {
                //注释 11, 开始加载布局并返回
                return inflate(parser, root, attachToRoot);
            } finally {
                parser.close();
            }
        }

上面注释 10,首先根据布局文件的xml预编译生成dex,然后通过反射来生成对应的View。如果没有,则往下用 XmlResourceParser 解析加载。这个预编译的开关默认是false,只有在内部测试时打开,这里不再讲。我们继续往下看注释 11处inflate的重载方法:

public View inflate (XmlPullParser parser, @Nullable ViewGroup root,boolean attachToRoot){
            synchronized (mConstructorArgs) {
                // 获取属性集
                final AttributeSet attrs = Xml.asAttributeSet(parser);
                View result = root;
                try {
                    // parser 移动到布局根节点处,获取根标签名称
                    advanceToRootNode(parser);
                    final String name = parser.getName();
                    //注释12, 判断 根标签是不是 “merge”
                    if (TAG_MERGE.equals(name)) {
                        // 是 merge标签,那就遍历布局并创建。通过递归解析加载布局
                        rInflate(parser, root, inflaterContext, attrs, false);
                    } else {
                        //注释 13, 不是 merge,则创建根标签 View
                        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                        //省略......

                        //注释 14, 遍历布局文件,生成所有自View。通过递归解析加载布局
                        rInflateChildren(parser, temp, attrs, true);
                        //注释 15, 如果需要的话,把布局添加到父布局
                        if (root != null && attachToRoot) {
                            root.addView(temp, params);
                        }
                        // 返回 View
                        if (root == null || !attachToRoot) {
                            result = temp;
                        }
                    }
                    // 省略 ......
                    return result;
                }
            }

上面在创建布局的时候,会现在注释 12处判断布局的根节点标签是不是“merge”,如果是就会调用rInflate方法遍历标签下的所有布局,并递归地解析和加载布局。

如果根标签不是“merge”,就会到注释13处开始创建布局,并在注释 14处同样采用递归的方式解析和加载子布局。然后在注释15的地方判断创建的布局是否加载到父布局中。也就是我们在调用布局加载参数时可以传入一个boolean参数,决定是否加入父布局。

下面再继续看一下上面注释 13处,View是怎么创建的:

   View createViewFromTag (View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr){
                // ......
                try {
                    // 注释16,尝试从 Factory中获取View
                    View view = tryCreateView(parent, name, context, attrs);
                    if (view == null) {

                        try {
                            //注释17 如果类名里存在'.',则说明布局里用的是全类名,说明这是自定义 View
                            // 否则 不带'.',说明用的不是全类名,是系统提供的View(默认前缀 android.widget)
                            if (-1 == name.indexOf('.')) {
                                view = onCreateView(context, parent, name, attrs);
                            } else {
                                view = createView(context, name, null, attrs);
                            }
                        } finally {
                            mConstructorArgs[0] = lastContext;
                        }
                    }
                    return view;
                    // ......
                }

 public final View createView (@NonNull Context viewContext, @NonNull String name,
                        @Nullable String prefix, @Nullable AttributeSet attrs){
                    // ......
                    // 先从缓存中拿构造函数
                    Constructor<? extends View> constructor = sConstructorMap.get(name);
                    Class<? extends View> clazz = null;
                    try {
                        if (constructor == null) {
                            //注释 18 缓存中没有,那就通过反射创建构造函数,并把构造函数加入缓存
                            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                    mContext.getClassLoader()).asSubclass(View.class);
                            // ......
                            constructor = clazz.getConstructor(mConstructorSignature);
                            constructor.setAccessible(true);
                            sConstructorMap.put(name, constructor);
                        } else {
                            // If we have a filter, apply it to cached constructor
                        }
                            // .......
                        try {
                            // 用构造函数通过反射创建 View并返回
                            final View view = constructor.newInstance(args);
                            // ......
                            return view;
                        } finally {
                        }
                    }
                }

上面注释 16的地方,会首先尝试从Factory中获取View的对象,这个Factory可以人为地传入,这样我们就可以拦截到 View的创建过程。再往下看注释 18 的地方,到了View最终创建的地方。View的创建是通过反射的方式,并将构造器保存在缓存中,下次创建时直接在缓存中拿。

我们再看看上面注释 16的地方 Factory的传入,我们在 activity的onCreate方法里可以以下面的方式传入Factory,这样就可以拦截到 View的创建:

        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
            @Nullable
            @Override
            public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
                // 可以创建View并返回
                return null;
            }

            @Nullable
            @Override
            public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
                // 可以创建View并返回
                return null;
            }
        });

好了布局的加载流程基本就这样了。稍微总结一下吧:

上一篇下一篇

猜你喜欢

热点阅读