View的创建过程

2020-02-07  本文已影响0人  羽寂

--视图布局的加载
--setContentView()
--LayoutInflater.inflate()是如何解析xml的?
--createViewFromTag() 创建View
------自定义View的创建
------系统View的创建

要分析View的创建过程,应该从视图布局的加载开始分析。

视图布局的加载

在开发中我们一般通过setContentView()加载Activity的布局,通过LayoutInflater.inflate()方法加载fragment、recyclerview里adapter加载item布局等等。

而setContentView()实际上使用的就是LayoutInflater.inflate()进行的布局加载,所以我们从setContentView()开始分析最好不过。

setContentView()

获取一个window实例,实际上是调用了PhoneWindow的setContentView()方法

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID); //重点
        initWindowDecorActionBar();
}
public Window getWindow() {
    return mWindow;
}

PhoneWindow的setContentView源码:

其实下边就可以看出实际上setContentView()是通过LayoutInflater.inflate()进行的布局加载。 也就是说,实际上加载xml布局的是LayoutInflater.inflate()方法。

@Override
    public void setContentView(int layoutResID) {
            //···忽略
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //···忽略
        } else {
         //重点
         // 调用LayoutInflater的inflate方法解析布局文件,并生成View树,
            mLayoutInflater.inflate(layoutResID, mContentParent); 
        }
        mContentParent.requestApplyInsets(); //mContentParent是View树的根节点
        
        //回调Activity的onContentChanged方法通知视图发生改变
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

上面的mLayoutInflater是在PhoneWindow的构造方法中被实例的。

public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

LayoutInflater.inflate()是如何解析xml的?

inflate()一共有三个重载方法,其中前两个实际上都是调用的第三个方法,在第三个方法中,通过上下文获取到Resource实例,再通过getLayout()方法传入layout的布局id获取到XmlResourceParser对象。 接着又调用了一个inflate()方法。

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }
    
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();
        }
    }

LayoutInflater的实例实际上是通过getSystemService()创建的

深入到下一个inflate()方法中,首先遍历整个XML寻找merge标签,如果查到进行merge标签里的创建,如果没有则调用createViewFromTag()进行view的创建。 这段代码比较长,详细的信息写在注释里。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
             //···省略
            try {
                // 循环查找根节点
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                }
                final String name = parser.getName();

                //如果查找到是merge标签
                //private static final String TAG_MERGE = "merge";
                if (TAG_MERGE.equals(name)) {
                    //如果是merge标签,根节点不能为空attachToRoot不能为false,否则抛出异常
                    //因为merge标签需要依附在父布局里才能使用
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //然后调用rInflate加载布局
                    rInflate(parser, root, inflaterContext, attrs, false);
                                 
                //如果不是merge标签
                 } else {
                    //重点 
                    //View是通过createViewFromTag()方法创建出来的
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                
                  }
                 //···省略  
            } catch (XmlPullParserException e) {
                //···省略
            } finally {
                //···省略
            }
            return result;
        }
    }

createViewFromTag() 创建View

我们知道了View是createViewFromTag() 创建的,那么看里边的实现,具体写在注释里。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
    

        //首先会使用mFactory2,mFactory,mPrivateFactory这三个对象按先后顺序创建view。
        //如果这三个对象都为空的话,则会默认流程来创建View,最后返回View。
        //通常来讲这三个Factory都为空,如果我们想要控制View的创建过程就可以利用这一机制来定制自己的factory。  
        try {
            View view;
            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。
            //因为系统View是直接使用类名不用写全包名的,而自定义View在使用的时候一定要写全包名
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    //如果是自定义View则调用createView来创建View,否则调用onCreateView方法。
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } 
}

从上面可以看出,mFactory2,mFactory其实都是可以hook的点,通过这里进行拦截,添加我们想要进行的操作。 而view的实际创建,系统进行判断,是自定义view还是系统自带view,通过是否有包名去判断。 我们再深入看 createView()方法。

自定义View的创建

从下面代码可以看出每个View都是通过反射进行创建的。

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(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
                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 lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            //view的创建
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {   
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

        } catch (NoSuchMethodException e) {
            ···
        } finally {
            ···
        }
    }

系统View的创建

在LayoutInflater中我们找到了onCreateView()方法

protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
}

onCreateView方法创建系统View最终的实现也是交给了createView方法,只是传入了一个字符串android.view.,这样在创建构造器时就会与View的名字拼接到一起获取对应的Class对象,使最终能够成功创建对应的View。

在PhoneLayoutInflater里找到了createView()方法。

主要完成的是遍历一个存放了三个包名字符串的数组,然后调用createView方法创建View,只要这三次创建View有一次成功,那么就返回创建的View,否则最终返回的还是父类传入"android.view."时创建的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) {
            try {
                //重点
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) { 
            }
        }

        return super.onCreateView(name, attrs);
    }

createView的具体实现:

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
            //从缓存器中获取构造器
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

        try {
         
            //没有缓存的构造器
            if (constructor == null) {
                //通过传入的prefix构造出完整的类名 并加载该类
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                //···省略
                
                //从class对象中获取构造器
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                //存入缓存器中
                sConstructorMap.put(name, constructor);
            } else {
                //···省略
            }
           //···省略
          
            //这里可以看到,系统应用也是通过反射创建View
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;
        } 

    }

结合前面的分析,我们可以清楚的看到view的创建过程。

经历了从 加载布局 — 到遍历xml各个节点 — 判断是否系统view — view的创建 的过程,这对于我们以后的开发大有帮助。

上一篇下一篇

猜你喜欢

热点阅读