美文网首页
View的创建过程

View的创建过程

作者: 羽寂 | 来源:发表于2020-02-07 13:56 被阅读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的创建 的过程,这对于我们以后的开发大有帮助。

    相关文章

      网友评论

          本文标题:View的创建过程

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