Activity的setContentView源码分析

来源:互联网 发布:2016年珠宝消费数据 编辑:程序博客网 时间:2024/06/04 18:48

Activity的setContentView分析


1.setContentView的简单的介绍

我们知道Activity的setContentView方法实际上就是调用的是Window的setContentView方法,而Window是一个抽象类,Window的具体实现类时PhoneWindow。在PhoneWindow的setContentView方法中具体操作如下:

public void setContentView(int layoutResID) {    //1.当mContentParent为空时先构建DecorView并将DecorView包裹到mContentParent 中    if (mContentParent == null) {        installDecor();    } else {        mContentParent.removeAllViews();    }    //解析layoutresID    mLayoutInflater.inflate(layoutResID, mContentParent);    //省略不必要的代码}

2.Window的View层级图:

Window的View层级图如下:

这里写图片描述

从上图中我们可以看到mDecor中会加载一个系统定义好的布局,这个布局中又包裹了mContentParent,mContentParent就是我们设置的布局,并将其添加到Parent区域。这个思路在PhoneWindow的setContentView方法中也可以得知,首先会构建mContentParent这个对象,然后通过LayoutInflater的inflate方法将制定布局的视图添加到mContentParent中。

installDecor()方法的源码如下:

private void installDecor() {    if (mDecor == null) {        //创建DecorView        mDecor = generateDecor();        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);        mDecor.setIsRootNamespace(true);        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);        }    }    if (mContentParent == null) {        //根据features生成Activity的layout资源的父节点        mContentParent = generateLayout(mDecor);        ...    }}

3.inflate方法详解

由LayoutInflater的inflate方法加载布局到mContentParent,所以我们来看看inflate方法的实现。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {  synchronized (mConstructorArgs) {    // 获取上下文对象,即LayoutInflater.from传入的Context.    final Context inflaterContext = mContext;    // 根据parser构建XmlPullAttributes.    final AttributeSet attrs = Xml.asAttributeSet(parser);    // 保存之前的Context对象.    Context lastContext = (Context) mConstructorArgs[0];    // 赋值为传入的Context对象.    mConstructorArgs[0] = inflaterContext;    // 注意,默认返回的是父布局root.    View result = root;    try {      // 查找xml的开始标签.      int type;      // 找到root元素      while ((type = parser.next()) != XmlPullParser.START_TAG &&          type != XmlPullParser.END_DOCUMENT) {        // Empty      }      // 如果没有找到有效的开始标签,则抛出InflateException异常.      if (type != XmlPullParser.START_TAG) {        throw new InflateException(parser.getPositionDescription()            + ": No start tag found!");      }      // 获取控件名称.      final String name = parser.getName();      // 特殊处理merge标签      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");        }        rInflate(parser, root, inflaterContext, attrs, false);      } else {        // 不是merge标签实那么直接解析布局中的视图        final View temp = createViewFromTag(root, name, inflaterContext, attrs);        ViewGroup.LayoutParams params = null;        // 如果传入的parent不为空.        if (root != null) {          if (DEBUG) {            System.out.println("Creating params from root: " +                root);          }          // 创建父类型的LayoutParams参数.          params = root.generateLayoutParams(attrs);          if (!attachToRoot) {            // 如果实例化的View不需要添加到父布局上,则直接将根据父布局生成的params参数设置            // 给它即可.            temp.setLayoutParams(params);          }        }        // 递归的创建当前布局的所有控件        rInflateChildren(parser, temp, attrs, true);        // 如果传入的父布局不为null,且attachToRoot为true,则将实例化的View加入到父布局root中        if (root != null && attachToRoot) {          root.addView(temp, params);        }        // 如果父布局为null或者attachToRoot为false,则将返回值设置成我们实例化的View        if (root == null || !attachToRoot) {          result = temp;        }      }    } catch (XmlPullParserException e) {      InflateException ex = new InflateException(e.getMessage());      ex.initCause(e);      throw ex;    } catch (Exception e) {      InflateException ex = new InflateException(          parser.getPositionDescription()              + ": " + e.getMessage());      ex.initCause(e);      throw ex;    } finally {      // Don't retain static reference on context.      mConstructorArgs[0] = lastContext;      mConstructorArgs[1] = null;    }    Trace.traceEnd(Trace.TRACE_TAG_VIEW);    return result;  }}

在inflate方法中的源码中,主要有以下几个步骤

  • 1.解析xml中的根标签(第一个元素)
  • 2.如果根标签是merge,那么调用rInflate进行解析,rInflate会将merge标签下的所有子View直接添加到根标签
  • 3.如果标签是普通元素,那么就调用createViewFromTag方法对元素进行解析
  • 4.调用rInflate解析temp根元素下的所有View,并且将这些子View都添加到temp下
  • 5.返回解析到的根视图

4.createViewFromTag方法详解

在以上的分析中我们提到了解析单个元素是用到的createViewFromTag,具体的源码如下:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,    boolean ignoreThemeAttr) {  if (name.equals("view")) {    name = attrs.getAttributeValue(null, "class");  }  if (!ignoreThemeAttr) {    final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);    final int themeResId = ta.getResourceId(0, 0);    if (themeResId != 0) {      context = new ContextThemeWrapper(context, themeResId);    }    ta.recycle();  }  // 特殊处理“1995”这个标签(ps: 平时我们写xml布局文件时基本没有使用过).  if (name.equals(TAG_1995)) {    // Let's party like it's 1995!    return new BlinkLayout(context, attrs);  }  try {    View view;    //用户可以通过设置LayoutInflater的factory来自行解析View,默认这些Factory都为空    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);    }    //没有Factory的情况下通过onCreateView或者createView创建View    if (view == null) {      final Object lastContext = mConstructorArgs[0];      mConstructorArgs[0] = context;      try {         //内置View控件解析        if (-1 == name.indexOf('.')) {          view = onCreateView(parent, name, attrs);        } else {        //自定义控件解析          view = createView(name, null, attrs);        }      } finally {        mConstructorArgs[0] = lastContext;      }    }    return view;  } catch (InflateException e) {    throw e;  } catch (ClassNotFoundException e) {    final InflateException ie = new InflateException(attrs.getPositionDescription()        + ": Error inflating class " + name);    ie.initCause(e);    throw ie;  } catch (Exception e) {    final InflateException ie = new InflateException(attrs.getPositionDescription()        + ": Error inflating class " + name);    ie.initCause(e);    throw ie;  }}

createViewFromTag会将元素的parent及其名字传递过来,当这个tag中没有包含”.”时,LayoutInflater会认为这是一个内置View,onCreateView方法就是在View标签名的前面设置一个“android.widget.”前缀,然后再传递到createView进行解析,也就是说内置View和自定义View最终都会调用createView进行解析,LayoutInflater解析时如果遇到只写类名的View,那么就认为是内置View控件,在onCreateView方法中会将”android.widget.”前缀传递给createView,最后在createView中构造View的完整路径进行解析,如果是自定义View的话,那么必须写完整的路径,然后调用createView且前缀为null进行解析。


5.createView方法解析

createView的源码如下:

public final View createView(String name, String prefix, AttributeSet attrs)  throws ClassNotFoundException, InflateException {  // 以View的name为key, 查询构造函数的缓存map中是否已经存在该View的构造函数    Constructor<? extends View> constructor = sConstructorMap.get(name);  Class<? extends View> clazz = null;  try {    // 构造函数在缓存中未命中    if (constructor == null) {      // 通过类名去加载控件的字节码      clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubClass(View.class);      // 如果有自定义的过滤器并且加载到字节码,则通过过滤器判断是否允许加载该View            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 {      if (mFilter != null) {        // 过滤的map是否包含了此类名        Boolean allowedState = mFilterMap.get(name);        if (allowedState == null) {          // 重新加载类的字节码          clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);          boolean allowed = clazz != null && mFilter.onLoadClass(clazz);          mFilterMap.put(name, allowed);          if (!allowed) {            failNotAllowed(name, prefix, attrs);          }        } else if (allowedState.equals(Boolean.FALSE)) {          failNotAllowed(name, prefix, attrs);        }      }    }    // 实例化类的参数数组(mConstructorArgs[0]为Context, [1]为View的属性)    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]))    }    return view;  } catch (NoSunchMethodException e) {    // ......  } catch (ClassNotFoundException e) {    // ......  } catch (Exception e) {    // ......  } finally {    // ......

createView相对比较简单,如果有前缀那么就构造View的完整路径,并将该类加载到虚拟机中,然后获取该类的构造函数并且缓存起来,再通过构造函数来创建该View的对象,最后将View对象返回。这就是构建一个单个View的过程,我们的窗口是一个View树,LayoutInflater需要解析这个完整的树,inflate方法的源码 我们在上面已经解释过了,


6.inflate方法

inflate方法在前面我们已经介绍过源码,所以我们只是一个简单的思路的分析
inflate通过深度优先遍历来构建视图树,每解析到一个View元素就会递归调用rInflate,直到这条路径剩下最后一个元素,然后再回溯过来将每个View元素添加到它们的parent中,通过rInflate的解析之后,整棵树就构建完成,当Activity的onResume调用之后,我们通过setContentView设置的内容就会出现在我们的视野当中。

OK,我们的setContentView方法详解就到此结束。


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 詹姆斯士兵12魔术贴老是掉怎么办 手机版本不支持陌陌视频聊天怎么办 私人单位不给员工写收入证明怎么办 cad图形缩小后找不到图了怎么办 离婚了老婆嫁给了别人怎么办 对方开车撞伤人逃逸不赔钱怎么办 帮老板开车撞伤人老板不愿赔怎么办 我开车撞人现在伤者住院怎么办 B照驾驶证扣3分怎么办l 驾驶证被盗后被别人拿去消分怎么办 碰瓷的手碰我后视镜怎么办 摩托被盗监控录像器没有记录怎么办 车贷逾期车被贷款公司拖走了怎么办 发现邻居家小孩偷了我的钱该怎么办 孕晚期挺着大肚子好累怎么办 古墓丽影崛起东西满了怎么办 塞尔达传说大师剑耐久没了怎么办 冒险岛遇见超能力者全屏挂机怎么办 当危险来临时该怎么办作文400字 生气把孩子手掌内侧打肿了怎么办 驾驶证未满一年扣12分怎么办 在高速上超速百分之10以下怎么办 中兴手机重启死机开不了机怎么办 太胖了太自卑了该怎么办 儿子因为长的胖特别自卑怎么办? 苹果ld叫我检查身份信息怎么办 我家墙让对面给漂水了怎么办 如果你流落到荒岛上你会怎么办 手机迅雷下载版权方不给下载怎么办 白色有彩色花纹的衣服染色了怎么办 载兰花假如下雪和打霜怎么办 皇室战争你的队友离开了对战怎么办 海岛奇兵发现求救信号第三个怎么办 海岛奇兵勋章太多对手太强怎么办 鱼为什么换缸鱼翅黑了怎么办 鱼丸捕鱼大作战换手机了怎么办 2o岁j'j小怎么办 为什么小米5s指纹不能用怎么办 cs录屏软件运行内存太大了怎么办 可是没有他我怎么办啊是什么电视剧 可是没有他我怎么办啊是哪个电视剧