Avoid passing null as the view root windowManager使用

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());
//将布局文件填充到View
mFloatLayout = (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....

谢谢了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值