Avoid passing null as the view root windowManager使用

来源:互联网 发布:淘宝村淘收费标准 编辑:程序博客网 时间:2024/06/03 06:24

Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element)  报错。

在使用windowmanager做弹窗的时候,会出现这个问题。只是一个警告,正常使用并不报错,代码如下:

LayoutInflater inflater = LayoutInflater.from(getApplication());//将布局文件填充到ViewmFloatLayout = (RelativeLayout) inflater.inflate(R.layout.result_windows, null);if(mFloatLayout.getParent() == null)winManager.addView(mFloatLayout, wmParams);

出现这个问题的原因,有一篇文章讲的比较透彻,点击我!

总结一句话就是说,当我们使用LayoutInflater这个“填充器”填充result_windows这个布局View(文中为mFloatLayout)的时候,后面null为root根ViewGroup对象。也就是说,没有指定根对象。下面是第二个参数的解释:

root Optional view to be the parent of the generated hierarchy.
一个可选的view对象,它是作为生成的对象的父对象。这是我的理解。LayoutInflater will automatically attempt to attach the inflated view to the supplied root. However, the framework has a check in place that if you pass null for the root it bypasses this attempt to avoid an application crash.

比较快速的修正这个警告的办法:

mFloatLayout = View.inflate(getApplicationContext(), R.layout.result_windows, null);//添加View填充--------------------------------if(mFloatLayout.getParent() == null)winManager.addView(mFloatLayout, wmParams);

下面是使用static方法获取生成的View,警告消除,我们可以看一下,eclipse的参数提示:

root A view group that will be the parent. Used to properly inflate the layout_* parameters
一看之下,貌似也没看明白,搜了点资料。发现其实没区别。View.inflate和Layoutinflater.inflate
   /**     * Inflate a view from an XML resource. This convenience method wraps the {@link     * LayoutInflater} class, which provides a full range of options for view inflation.     *     * @param context The Context object for your activity or application.     * @param resource The resource ID to inflate     * @param root A view group that will be the parent. Used to properly inflate the     * layout_* parameters.     * @see LayoutInflater     */    public static View inflate(Context context, int resource, ViewGroup root) {        LayoutInflater factory = LayoutInflater.from(context);        return factory.inflate(resource, root);    }

那么看起来,警告是消除了,其实是没有检测到而已,并没有本质上的区别罢了。一篇介绍参数的博文:参数介绍

下面来一个介绍View实例化过程的文章:View实例化过程详解    ,这个文章需要一点耐心来看,总体来说,比自己理解要来的简单一些。

重点看一下,inflate这个方法以及ViewGroup类:

public View inflate(int resource, ViewGroup root) {         return inflate(resource, root, root != null);   }     public View inflate(int resource, ViewGroup root, boolean attachToRoot) {         /*可以看到通过resource id返回了一个XmlResourceParser,通过类名就可以猜测        这是一个xml的解析类。但点进去一看,发现它只是一个接口,它继承自 XmlPullParser用于pull方式解析xml的接口。和AttributeSet用于获取此view的所有属性。     那么需要能找到它的实现类。先看下面resource类。        */         XmlResourceParser parser = getContext().getResources().getLayout(resource);         try {             return inflate(parser, root, attachToRoot);  //说实话,从这里开始没完没了的函数调用,我就不再往下贴了。。。       } finally {             parser.close();         }   }  ............./** * 真正创建一个view的方法, * 此方法是用反射获取构造器来实例对象而不是直接new出来这是为了处于性能优化考虑, * 同一个类名的不同对象,可以直接得到缓存的构造器直接获取一个构造器对象实例。而不需要 * 重复进行new操作。    * * @param name 此View的全名 * @param prefix 前缀,值为 "android.view."其实就是是否包含包名 * @param attrs 此view的属性值,传递给此view的构造函数 */   public final View createView(String name, String prefix, AttributeSet attrs)             throws ClassNotFoundException, InflateException {         Constructor constructor = sConstructorMap.get(name); //缓存中是否已经有了一个构造函数         Class clazz = null;           try {             if (constructor == null) {                 //通过类名获得一个class对象                 clazz = mContext.getClassLoader().loadClass(                         prefix != null ? (prefix + name) : name);                                  if (mFilter != null && clazz != null) {                     boolean allowed = mFilter.onLoadClass(clazz);                     if (!allowed) {                         failNotAllowed(name, prefix, attrs);                     }                 }              //通过参数类型获得一个构造器,参数列表为context,attrs                 constructor = clazz.getConstructor(mConstructorSignature);                 sConstructorMap.put(name, constructor);      //把此构造器缓存起来             } else {                 // If we have a filter, apply it to cached constructor                 if (mFilter != null) {                     // Have we seen this name before?                     Boolean allowedState = mFilterMap.get(name);                     if (allowedState == null) {                         // New class -- remember whether it is allowed                         clazz = mContext.getClassLoader().loadClass(                                 prefix != null ? (prefix + name) : name);                                                  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);                     }                 }             }               Object[] args = mConstructorArgs;             args[1] = attrs;     //args[0]已经在前面初始好了。这里只要初始化args[1]             return (View) constructor.newInstance(args);     //通过反射new出一个对象。。大功告成           } catch (NoSuchMethodException e) {             InflateException ie = new InflateException(attrs.getPositionDescription()                     + ": Error inflating class "                     + (prefix != null ? (prefix + name) : name));             ie.initCause(e);             throw ie;           } catch (ClassNotFoundException e) {             // If loadClass fails, we should propagate the exception.             throw e;         } catch (Exception e) {             InflateException ie = new InflateException(attrs.getPositionDescription()                     + ": Error inflating class "                     + (clazz == null ? "<unknown>" : clazz.getName()));             ie.initCause(e);             throw ie;         }     }  

View的子类ViewGroup,可以简单理解为View的一个“组”,包含了一组View,其中重写了一个函数:

//哈哈,果然重写了此方法。其实就是在viewgroup包含的  //子view数组中进行遍历。那么view是什么时候被加入进  //viewgroup中的呢?如果是在代码中写,肯定是直接使用  //addView方法把view加入viewGroup。如果写在xml布局文件  //中,其实是在第二种方法中被加入view的。inflate加载父view  //时会同时把其所有的子view加载完,同时addView到父view中   protected View findViewTraversal(int id) {          if (id == mID) {              return this;          }            final View[] where = mChildren;          final int len = mChildrenCount;            for (int i = 0; i < len; i++) {              View v = where[i];                if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {                  v = v.findViewById(id);                    if (v != null) {                      return v;                  }              }          }            return null;      }  


人家还是总结的不错的:

也就是说:假如root设置为null或者root!=nul但attachToRoot设置为false,则这个xml加载出来的view不会挂载到别的视图节点上去;当root!=null并且attachToRoot设置为true时,就会挂载上去,return为root当root!=nul但attachToRoot设置为false时,这个root用来view生成正确的params布局参数来match root。


总结来说,root这个参数是用来指定新生成的View附加位置(父节点),而后面那个boolean值则是选择是否附加到该节点(也就是ViewGroup)。

这个是我结合前人的基础上的理解,所以,我这里填null,也就导致新生成的windows不会附加于任何View,它是直接面向屏幕的,这个也符合windowManager的特性:整个Android的窗口机制是基于一个叫做 WindowManager,这个接口可以添加view到屏幕,也可以从屏幕删除view。它面向的对象一端是屏幕,另一端就是View,直接忽略我们以前的Activity或者Dialog之类的,这个 WindowManager是全局的,整个系统就是这个唯一的,这也就是说,不可以new出来,它最好也不要依附于任何activity是最好的。

还是有很多不明白的地方,希望明白的朋友回一下。 T ~ T....

谢谢了!

1 0
原创粉丝点击