AndroidLayoutInflater加载布局详解及实例代码

AndroidLayoutInflater加载布局详解及实例代码,第1张

概述Android LayoutInflater加载布局详解对于有一定Android开发经验的同学来说,一定使用过LayoutInflater.inflater()来加载布局文件,但并不一定去深究过它的原理,比如

AndroID  LayoutInflater加载布局详解

对于有一定AndroID开发经验的同学来说,一定使用过LayoutInflater.inflater()来加载布局文件,但并不一定去深究过它的原理,比如

1.LayoutInflater为什么可以加载layout文件?
2.加载layout文件之后,又是怎么变成供我们使用的VIEw的?
3.我们定义VIEw的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢?

既然在这篇文章提出来,那说明这三个问题都是跟LayoutInflater脱不了干系的。在我们的分析过程中,会对这些问题一一进行解答。

我们一步一步来,首先当我们需要从layout中加载VIEw的时候,会调用这个方法

LayoutInflater.from(context).inflater(R.layout.main_activity,null);

1.如何创建LayoutInflater?

这有什么值得说的?如果你打开了LayoutInflater.Java你自然就明白了,LayoutInflater是一个抽象类,而抽象类是不能直接被实例化的,也就是说我们创建的对象肯定是LayoutInflater的某一个实现类。

我们进入LayoutInflater.from方法中可以看到

public static LayoutInflater from(Context context) {    LayoutInflater LayoutInflater =        (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);    if (LayoutInflater == null) {      throw new AssertionError("LayoutInflater not found.");    }    return LayoutInflater;  }

好吧,是获取的系统服务!是从context中获取,没吃过猪肉还没见过猪跑么,一说到context对象十有八九是说得ContextImpl对象,于是我们直接去到ContextImpl.java中,找到getSystemService方法

@OverrIDe  public Object getSystemService(String name) {    return SystemServiceRegistry.getSystemService(this,name);  }

额。。。又要去SystemServiceRegistry.java文件中

  /**   * Gets a system service from a given context.   */  public static Object getSystemService(ContextImpl ctx,String name) {    ServiceFetcher<?> fetcher = SYstem_SERVICE_FETCHERS.get(name);    return fetcher != null ? fetcher.getService(ctx) : null;  }

代码可知,我们的Service是从SYstem_SERVICE_FETCHERS这个HashMap中获得的,而稍微看一下代码就会发现,这个HashMap是在static模块中赋值的,这里注册了很多的系统服务,什么ActivityService,什么AlarmService等等都是在这个HashMap中。从LayoutInflater.from方法中可以知道,我们找到是Context.LAYOUT_INFLATER_SERVICE对应的Service

registerService(Context.LAYOUT_INFLATER_SERVICE,LayoutInflater.class,new CachedServiceFetcher<LayoutInflater>() {      @OverrIDe      public LayoutInflater createService(ContextImpl ctx) {        return new PhoneLayoutInflater(ctx.getouterContext());      }});

好啦,主角终于登场了――PhoneLayoutInflater,我们获取的LayoutInflater就是这个类的对象。

那么,这一部分的成果就是我们找到了PhoneLayoutInflater,具体有什么作用,后面再说。

2.inflater方法分析

这个才是最重要的方法,因为就是这个方法把我们的layout转换成了VIEw对象。这个方法直接就在LayoutInflater抽象类中定义

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

传入的参数一个是layout的ID,一个是是否指定ParentVIEw,而真正的实现我们还得往下看

public VIEw inflate(@LayoutRes int resource,@Nullable VIEwGroup root,boolean attachToRoot) {    final Resources res = getContext().getResources();    if (DEBUG) {      Log.d(TAG,"INFLATING from resource: \"" + res.getResourcename(resource) + "\" ("          + Integer.toHexString(resource) + ")");    }    final XmlResourceParser parser = res.getLayout(resource);    try {      return inflate(parser,attachToRoot);    } finally {      parser.close();    }  }

我们先从context中获取了Resources对象,然后通过res.getLayout(resource)方法获取一个xml文件解析器XmlResourceParser(关于在AndroID中的xml文件解析器这里就不详细讲了,免得扯得太远,不了解的同学可以在网上查找相关资料阅读),而这其实是把我们定义layout的xml文件给加载进来了。

然后,继续调用了另一个inflate方法

public VIEw inflate(XmlPullParser parser,boolean attachToRoot) {    synchronized (mConstructorArgs) {      final Context inflaterContext = mContext;      //快看,VIEw的构造函数中的attrs就是这个!!!      final AttributeSet attrs = Xml.asAttributeSet(parser);      //这个数组很重要,从名字就可以看出来,这是构造函数要用到的参数      mConstructorArgs[0] = inflaterContext;      VIEw result = root;      try {        // 找到根节点,找到第一个START_TAG就跳出while循环,        // 比如<TextVIEw>是START_TAG,而</TextVIEw>是END_TAG        int type;        while ((type = parser.next()) != XmlPullParser.START_TAG &&            type != XmlPullParser.END_document) {          // Empty        }        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,inflaterContext,attrs,false);        } else {          // 这里需要调用到PhoneLayoutInflater中的方法,获取到根节点对应的VIEw          final VIEw temp = createVIEwFromTag(root,name,attrs);          VIEwGroup.LayoutParams params = null;          //如果指定了parentVIEw(root),则生成layoutParams,          //并且在后面会将temp添加到root中          if (root != null) {            params = root.generateLayoutParams(attrs);            if (!attachToRoot) {              temp.setLayoutParams(params);            }          }          // 上面解析了根节点,这里解析根节点下面的子节点          rInflateChildren(parser,temp,true);          if (root != null && attachToRoot) {            root.addVIEw(temp,params);          }          if (root == null || !attachToRoot) {            result = temp;          }        }      } catch (Exception e) {      } finally {        // Don't retain static reference on context.        mConstructorArgs[0] = lastContext;        mConstructorArgs[1] = null;      }      return result;    }  }

这个就稍微有点长了,我稍微去除了一些跟逻辑无关的代码,并且添加了注释,如果有耐心看的话应该是能看懂了。这里主要讲两个部分,首先是rInflateChildren这个方法,其实就是一层一层的把所有节点取出来,然后通过createVIEwFromTag方法将其转换成VIEw对象。所以重点是在如何转换成VIEw对象的。

3.createVIEwFromTag

我们一层层跟进代码,最后会到这里

VIEw createVIEwFromTag(VIEw parent,String name,Context context,AttributeSet attrs,boolean ignorethemeAttr) {      ......      ......    try {      ......      if (vIEw == null) {        final Object lastContext = mConstructorArgs[0];        mConstructorArgs[0] = context;        try {          //不含“.” 说明是系统自带的控件          if (-1 == name.indexOf('.')) {            vIEw = onCreateVIEw(parent,attrs);          } else {            vIEw = createVIEw(name,null,attrs);          }        } finally {          mConstructorArgs[0] = lastContext;        }      }      return vIEw;    } catch (InflateException e) {      throw e;      ......    }  }

为了方便理解,将无关的代码去掉了,我们看到其实就是调用的createVIEw方法来从xml节点转换成VIEw的。如果name中不包含'.' 就调用onCreateVIEw方法,否则直接调用createVIEw方法。

在上面的PhoneLayoutInflater中就复写了onCreateVIEw方法,而且不管是否重写,该方法最后都会调用createVIEw。唯一的区别应该是系统的VIEw的完整类名由onCreateVIEw来提供,而如果是自定义控件在布局文件中本来就是用的完整类名。

4. createVIEw方法

public final VIEw createVIEw(String name,String prefix,AttributeSet attrs)      throws ClassNotFoundException,InflateException {    //1.通过传入的类名,获取该类的构造器    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);        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) {          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,attrs);            }          } else if (allowedState.equals(Boolean.FALSE)) {            failNotAllowed(name,attrs);          }        }      }      //2.通过获得的构造器,创建VIEw实例      Object[] args = mConstructorArgs;      args[1] = attrs;      final VIEw vIEw = constructor.newInstance(args);      if (vIEw instanceof VIEwStub) {        final VIEwStub vIEwStub = (VIEwStub) vIEw;        vIEwStub.setLayoutInflater(cloneInContext((Context) args[0]));      }      return vIEw;    } catch (NoSuchMethodException e) {     ......    }   }

这段代码主要做了两件事情

第一,根据Classname将类加载到内存,然后获取指定的构造器constructor。构造器是通过传入参数类型和数量来指定,这里传入的是mConstructorSignature

static final Class<?>[] mConstructorSignature = new Class[] {      Context.class,AttributeSet.class};

即传入参数是Context和AttributeSet,是不是猛然醒悟了!!!这就是为什么我们在自定义view的时候,必须要重写VIEw(Context context,AttributeSet attrs)则个构造方法,才能在layout中使用我们的VIEw。

第二,使用获得的构造器constructor来创建一个VIEw实例。

5.回答问题

还记得上面我们提到的三个问题吗?现在我们来一一解答:

1.LayoutInflater为什么可以加载layout文件?

因为LayoutInflater其实是通过xml解析器来加载xml文件,而layout文件的格式就是xml,所以可以读取。

2.加载layout文件之后,又是怎么变成供我们使用的VIEw的?

LayoutInflater加载到xml文件中内容之后,通过反射将每一个标签的名字取出来,并生成对应的类名,然后通过反射获得该类的构造器函数,参数为Context和AttributeSet。然后通过构造器创建VIEw对象。

3.我们定义VIEw的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢?

因为LayoutInflater在解析xml文件的时候,会将xml中的内容转换成一个AttributeSet对象,该对象中包含了在xml文件设定的属性值。需要在构造函数中将这些属性值取出来,赋给该实例的属性。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

总结

以上是内存溢出为你收集整理的Android LayoutInflater加载布局详解及实例代码全部内容,希望文章能够帮你解决Android LayoutInflater加载布局详解及实例代码所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1146921.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存