zoukankan      html  css  js  c++  java
  • 分析java.lang.NullPointerException thrown in RelativeLayout measure()

    1.  典型的再现环境
      模型: Sony Ericsson
      Android version: 2.3.4
      StackTrace:
      E/AndroidRuntime( 3579): FATAL EXCEPTION: main
      E/AndroidRuntime( 3579): java.lang.NullPointerException
      E/AndroidRuntime( 3579): 	at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:431)
      E/AndroidRuntime( 3579): 	at android.view.View.measure(View.java:8462)
      E/AndroidRuntime( 3579): 	at com.example.measureverify.MainActivity$MenuListAdapter.getView(MainActivity.java:85)
      E/AndroidRuntime( 3579): 	at android.widget.AbsListView.obtainView(AbsListView.java:1519)
      E/AndroidRuntime( 3579): 	at android.widget.ListView.measureHeightOfChildren(ListView.java:1220)
      E/AndroidRuntime( 3579): 	at android.widget.ListView.onMeasure(ListView.java:1131)
      E/AndroidRuntime( 3579): 	at android.view.View.measure(View.java:8462)
      E/AndroidRuntime( 3579): 	at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:581)
      E/AndroidRuntime( 3579): 	at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:365)
      E/AndroidRuntime( 3579): 	at android.view.View.measure(View.java:8462)
      E/AndroidRuntime( 3579): 	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:3231)
      E/AndroidRuntime( 3579): 	at android.widget.FrameLayout.onMeasure(FrameLayout.java:254)
      E/AndroidRuntime( 3579): 	at android.view.View.measure(View.java:8462)
      E/AndroidRuntime( 3579): 	at android.widget.LinearLayout.measureVertical(LinearLayout.java:535)
      E/AndroidRuntime( 3579): 	at android.widget.LinearLayout.onMeasure(LinearLayout.java:313)
      E/AndroidRuntime( 3579): 	at android.view.View.measure(View.java:8462)
      E/AndroidRuntime( 3579): 	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:3231)
      E/AndroidRuntime( 3579): 	at android.widget.FrameLayout.onMeasure(FrameLayout.java:254)
      E/AndroidRuntime( 3579): 	at android.view.View.measure(View.java:8462)
      E/AndroidRuntime( 3579): 	at android.view.ViewRoot.performTraversals(ViewRoot.java:861)
      E/AndroidRuntime( 3579): 	at android.view.ViewRoot.handleMessage(ViewRoot.java:1882)
      E/AndroidRuntime( 3579): 	at android.os.Handler.dispatchMessage(Handler.java:99)
      E/AndroidRuntime( 3579): 	at android.os.Looper.loop(Looper.java:130)
      E/AndroidRuntime( 3579): 	at android.app.ActivityThread.main(ActivityThread.java:3701)
      E/AndroidRuntime( 3579): 	at java.lang.reflect.Method.invokeNative(Native Method)
      E/AndroidRuntime( 3579): 	at java.lang.reflect.Method.invoke(Method.java:507)
      E/AndroidRuntime( 3579): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
      E/AndroidRuntime( 3579): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:624)
      E/AndroidRuntime( 3579): 	at dalvik.system.NativeStart.main(Native Method)
      W/ActivityManager(  237):   Force finishing activity com.example.measureverify/.MainActivity
      
      验证代码:
          public class MenuListAdapter extends ArrayAdapter<ListItem> {
              private Activity context;
      
              public MenuListAdapter(Activity context) {
                  super(context, 0);
                  this.context = context;
              }
      
              public View getView(int position, View convertView, ViewGroup parent) {
                  if (convertView == null) {
                      if (unsafetyInflate) {
                          // risk inflate case
                          convertView = LayoutInflater.from(context).inflate(
                                  R.layout.custom_infowindow, null);
                      } else {
                          // safety inflate case(### Solution 1 ###)
                          // The second parameter "mListView" provides a set of
                          // LayoutParams values for root of the returned hierarchy
                          convertView = LayoutInflater.from(context).inflate(
                                  R.layout.custom_infowindow, mListView, false);
                      }
      
                      // ### Solution 2 ###
                      if (MainActivity.this.setLayoutParamsProgrammatically) {
                          // NOTE: the layout params set here should be of the
                          // {ParentView}.LayoutParams
                          convertView
                                  .setLayoutParams(new ListView.LayoutParams(
                                          ListView.LayoutParams.WRAP_CONTENT,
                                          ListView.LayoutParams.WRAP_CONTENT));
                      }
                      Log.d(TAG, "case 1 parent:" + convertView.getParent() + " layoutParams:"
                              + convertView.getLayoutParams());
                      final int width = context.getWindow().getDecorView().getWidth();
                      final int height = context.getWindow().getDecorView().getHeight();
                      convertView.measure(width, height);
                      MainActivity.this.mLayout = convertView;
                  }
                  final ListItem item = (ListItem) getItem(position);
                  TextView title = (TextView) convertView.findViewById(R.id.title);
      
                  title.setText("title " + Math.random() + item.prop_1);
                  TextView snippet = (TextView) convertView.findViewById(R.id.snippet);
                  snippet.setText("snippet " + Math.random() + item.prop_2);
                  return convertView;
              }
      
          }



    2. 相关Android2.3.6 Framework层代码
      View.java中有一成员代表layout參数(子View对父View的请求,或者一种期望值,终于由整个View层次树决定)
          /**
      1623     * The layout parameters associated with this view and used by the parent
      1624     * {@link android.view.ViewGroup} to determine how this view should be
      1625     * laid out.
      1626     * {@hide}
      1627     */
      1628    protected ViewGroup.LayoutParams mLayoutParams;
      以及获取该成员的函数:
      4973    /**
      4974     * Get the LayoutParams associated with this view. All views should have
      4975     * layout parameters. These supply parameters to the <i>parent</i> of this
      4976     * view specifying how it should be arranged. There are many subclasses of
      4977     * ViewGroup.LayoutParams, and these correspond to the different subclasses
      4978     * of ViewGroup that are responsible for arranging their children.
      4979     * @return The LayoutParams associated with this view
      4980     */
      4981    @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")
      4982    public ViewGroup.LayoutParams getLayoutParams() {
      4983        return mLayoutParams;
      4984    }

      ViewGroup类中有一个方法用于将detached的View attach到父View:
      2352     * This method should be called only for view which were detached from their parent.
      2353     *
      2354     * @param child the child to attach
      2355     * @param index the index at which the child should be attached
      2356     * @param params the layout parameters of the child
      2357     *
      2358     * @see #removeDetachedView(View, boolean)
      2359     * @see #detachAllViewsFromParent()
      2360     * @see #detachViewFromParent(View)
      2361     * @see #detachViewFromParent(int)
      2362     */
      2363    protected void attachViewToParent(View child, int index, LayoutParams params) {
      2364        child.mLayoutParams = params; // 编者注:此处子View的mLayoutParams被设置
      2365
      2366        if (index < 0) {
      2367            index = mChildrenCount;
      2368        }
      2369
      2370        addInArray(child, index);
      2371
      2372        child.mParent = this;
      2373        child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) | DRAWN;
      2374
      2375        if (child.hasFocus()) {
      2376            requestChildFocus(child, child.findFocus());
      2377        }
      2378    }

      在ListView/GridView中都用调用此方法来设置好子View。
      ViewGroup类中同一时候还有另外一个更主流的方法(整个Layout被从xml中铺陈开attach到Window并变得有活力的过程中该方法会被调用到)
       private void addViewInner(View child, int index, LayoutParams params,
      1973            boolean preventRequestLayout) {
      1974
      1975        if (child.getParent() != null) {
      1976            throw new IllegalStateException("The specified child already has a parent. " +
      1977                    "You must call removeView() on the child's parent first.");
      1978        }
      1979
      1980        if (!checkLayoutParams(params)) {
      1981            params = generateLayoutParams(params);
      1982        }
      1983
      1984        if (preventRequestLayout) {
      1985            child.mLayoutParams = params;  // 编者注:直接不理会子View请求/意愿的case,直接由父View分配,强迫子View接受
      1986        } else {
      1987            child.setLayoutParams(params);  // 编者注:温和一刀的做法。很体谅的设置给子View。究竟惬意不惬意,取决于子View自身
      1988        }
      1989
      1990        if (index < 0) {
      1991            index = mChildrenCount;
      1992        }
      1993
      1994        addInArray(child, index);
      1995
      1996        // tell our children
      1997        if (preventRequestLayout) {
      1998            child.assignParent(this);
      1999        } else {
      2000            child.mParent = this;
      2001        }
      2002
      2003        if (child.hasFocus()) {
      2004            requestChildFocus(child, child.findFocus());
      2005        }
      2006
      2007        AttachInfo ai = mAttachInfo;
      2008        if (ai != null) {
      2009            boolean lastKeepOn = ai.mKeepScreenOn;
      2010            ai.mKeepScreenOn = false;
      2011            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
      2012            if (ai.mKeepScreenOn) {
      2013                needGlobalAttributesUpdate(true);
      2014            }
      2015            ai.mKeepScreenOn = lastKeepOn;
      2016        }
      2017
      2018        if (mOnHierarchyChangeListener != null) {
      2019            mOnHierarchyChangeListener.onChildViewAdded(this, child);
      2020        }
      2021
      2022        if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
      2023            mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
      2024        }
      2025    }
      2026
      由以上代码片断综合可知,在一个View只从XML中Inflate出来未被attach到View层次树里边去的时候,View.mLayoutParams成员为空。

    3. RelativeLayout为什么会NullPointerException在onMeasure
      回到文章开头的StackTrace,同一时候參照RelativeLayout的onMeasure():
      
      426        if (isWrapContentWidth) {
      427            // Width already has left padding in it since it was calculated by looking at
      428            // the right of each child view
      429            width += mPaddingRight;
      430
      431            if (mLayoutParams.width >= 0) { // 编者注:该处为onMeasure方法中第一次使用到mLayoutParams的地方
      432                width = Math.max(width, mLayoutParams.width);
      433            }
      434
      435            width = Math.max(width, getSuggestedMinimumWidth());
      436            width = resolveSize(width, widthMeasureSpec);
      437
      438            if (offsetHorizontalAxis) {
      439                for (int i = 0; i < count; i++) {
      440                    View child = getChildAt(i);
      441                    if (child.getVisibility() != GONE) {
      442                        LayoutParams params = (LayoutParams) child.getLayoutParams();
      不难看出,在RelativeLayout被add/attach到父View之前mLayoutParams成员为空。调用measure方法将导致上图标注处代码抛出空指针异常。
    4. 解决方式有两种
      a) 在measure之前显式设置LayoutParams(代表着对父View的Layout请求。必须是父View的内部LayoutParams类型)
      b) 自己主动设置LayoutParams的inflate方式
      请见代码:
      
           public View getView(int position, View convertView, ViewGroup parent) {
                  if (convertView == null) {
                      if (unsafetyInflate) {
                          // risk inflate case
                          convertView = LayoutInflater.from(context).inflate(
                                  R.layout.custom_infowindow, null);
                      } else {
                          // safety inflate case(### Solution 1 ###)
                          // The second parameter "mListView" provides a set of
                          // LayoutParams values for root of the returned hierarchy
                          convertView = LayoutInflater.from(context).inflate(
                                  R.layout.custom_infowindow, mListView, false);
                      }
      
                      // ### Solution 2 ###
                      if (MainActivity.this.setLayoutParamsProgrammatically) {
                          // NOTE: the layout params set here should be of the
                          // {ParentView}.LayoutParams
                          convertView
                                  .setLayoutParams(new ListView.LayoutParams(
                                          ListView.LayoutParams.WRAP_CONTENT,
                                          ListView.LayoutParams.WRAP_CONTENT));
                      }
                      Log.d(TAG, "case 1 parent:" + convertView.getParent() + " layoutParams:"
                              + convertView.getLayoutParams());
                      final int width = context.getWindow().getDecorView().getWidth();
                      final int height = context.getWindow().getDecorView().getHeight();
                      convertView.measure(width, height);
                      MainActivity.this.mLayout = convertView;
                  }
                  final ListItem item = (ListItem) getItem(position);
                  TextView title = (TextView) convertView.findViewById(R.id.title);
      
                  title.setText("title " + Math.random() + item.prop_1);
                  TextView snippet = (TextView) convertView.findViewById(R.id.snippet);
                  snippet.setText("snippet " + Math.random() + item.prop_2);
                  return convertView;
              }

      完整project代码截图:Measure验证project截图

    5. Android各个平台上这一问题的可复现性
      在查看多个版本号Android源代码后发现,Android 4.4_r1中已经针对该NullPointerException做了防范:
      
      539            if (mLayoutParams != null && mLayoutParams.width >= 0) {
      540                width = Math.max(width, mLayoutParams.width);
      541            }
      542
      543            width = Math.max(width, getSuggestedMinimumWidth());
      544            width = resolveSize(width, widthMeasureSpec);
      545
      546            if (offsetHorizontalAxis) {
      547                for (int i = 0; i < count; i++) {
      548                    View child = getChildAt(i);
      549                    if (child.getVisibility() != GONE) {
      550                        LayoutParams params = (LayoutParams) child.getLayoutParams();
      551                        final int[] rules = params.getRules(layoutDirection);
      552                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
      553                            centerHorizontal(child, params, width);
      554                        } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
      555                            final int childWidth = child.getMeasuredWidth();
      556                            params.mLeft = width - mPaddingRight - childWidth;
      557                            params.mRight = params.mLeft + childWidth;
      558                        }
      559                    }
      560                }
      561            }
      562        }

    版权声明:本文博客原创文章。博客,未经同意,不得转载。

  • 相关阅读:
    Maven的安装与配置(eclipse,idea)
    用java代码打印九九乘法表
    HDFS的Java客户端操作代码(HDFS删除文件或目录)
    Java代码操作HDFS(在/user/root/下面創建目錄)
    第二种方式读取并显示HDFS中的内容
    HDFS的java客户端操作代码(Windows上面打jar包,提交至linux运行)
    HDFS的Java客户端操作代码(HDFS的查看、创建)
    CentOs6.8安装Git并安装oh my zsh
    搭建maven开发环境测试Hadoop组件HDFS文件系统的一些命令
    R-大数据分析挖掘(5-R基础回顾)
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/4683442.html
Copyright © 2011-2022 走看看