您的位置:首页 > 编程语言 > Java开发

分析java.lang.NullPointerException thrown in RelativeLayout measure()

2014-09-23 16:49 537 查看
典型重现环境

机型: Sony Ericsson

Android version: 2.3.4

StackTrace:

[html] view
plaincopy





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

验证代码:

[java] view
plaincopy





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;

}

}

相关Android2.3.6 Framework层代码

View.java中有一成员代表layout参数(子View对父View的请求,或者一种期望值,最终由整个View层次树决定)

[java] view
plaincopy





/**

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;

以及获取该成员的函数:

[java] view
plaincopy





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:

[java] view
plaincopy





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="color:#ff6666;">// 编者注:此处子View的<span style="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并变得有活力的过程中该方法会被调用到)
[java] view plaincopy



 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="color: rgb(255, 102, 102); font-family: Arial, Helvetica, sans-serif;">// 编者注:直接不理会子View请求/意愿的case,直接由父View分配,强迫子View接受</span>
1986        } else {
1987            child.setLayoutParams(params);  <span style="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成员为空。


RelativeLayout为什么会NullPointerException在onMeasure
回到文章开头的StackTrace,同时参照RelativeLayout的onMeasure():
[java] view plaincopy



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="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方法将导致上图标注处代码抛出空指针异常。


解决方案有两种
a) 在measure之前显式设置LayoutParams(代表着对父View的Layout请求,必须是父View的内部LayoutParams类型
b) 自动设置LayoutParams的inflate方式
请见代码:
[java] view plaincopy



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;
}

完整工程代码截图:


Android各个平台上这一问题的可复现性
在查看多个版本Android源码后发现,Android 4.4_r1中已经针对该NullPointerException做了防范:
[java] view plaincopy



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        }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐