View的创建过程

--视图布局的加载
--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的创建 的过程,这对于我们以后的开发大有帮助。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容