調用 RelativeLayout measure()報 java.lang.NullPointerException

  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; <span style="margin: 0px; padding: 0px; color: rgb(255, 102, 102);">// 編者注:此處子View的<span style="margin: 0px; padding: 0px; font-family: Arial, Helvetica, sans-serif;">mLayoutParams被設置</span></span>
    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;  <span style="margin: 0px; padding: 0px; color: rgb(255, 102, 102); font-family: Arial, Helvetica, sans-serif;">// 編者注:直接不理會子View請求/意願的case,直接由父View分配,強迫子View接受</span>
    1986        } else {
    1987            child.setLayoutParams(params);  <span style="margin: 0px; padding: 0px; color: rgb(255, 102, 102); font-family: Arial, Helvetica, sans-serif;">// 編者注:溫柔一刀的做法,非常體諒的設置給子View,到底滿意不滿意,取決於子View自身</span>
    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) { <span style="margin: 0px; padding: 0px; color: rgb(255, 102, 102); font-family: Arial, Helvetica, sans-serif;">// 編者注:該處爲onMeasure方法中第一次使用到mLayoutParams的地方</span>
    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;
            }

    完整工程代碼截圖:Measure驗證工程截圖

  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        }
  6. <span style="font-family: Arial; background-color: rgb(255, 255, 255);">轉自:</span><span style="font-family: Arial; background-color: rgb(255, 255, 255);">http://blog.csdn.net/wangfei584521/article/details/25987377</span>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章