分析java.lang.NullPointerException thrown in RelativeLayout

典型重现环境机型: Sony EricssonAndroid version: 2.3.4StackTrace:E/AndroidRuntime( 3579): FATAL EXCEPTION: mainE/AndroidRuntime( 3579): java.lang.NullPointerExceptionE/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 caseconvertView = 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 hierarchyconvertView = 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}.LayoutParamsconvertView.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层次树决定) /**1623* The layout parameters associated with this view and used by the parent1624* {@link android.view.ViewGroup} to determine how this view should be1625* laid out.1626* {@hide}1627*/1628 protected ViewGroup.LayoutParams mLayoutParams;以及获取该成员的函数:4973 /**4974* Get the LayoutParams associated with this view. All views should have4975* layout parameters. These supply parameters to the <i>parent</i> of this4976* view specifying how it should be arranged. There are many subclasses of4977* ViewGroup.LayoutParams, and these correspond to the different subclasses4978* of ViewGroup that are responsible for arranging their children.4979* @return The LayoutParams associated with this view4980*/4981 @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")4982 public ViewGroup.LayoutParams getLayoutParams() {4983return 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 attach2355* @param index the index at which the child should be attached2356* @param params the layout parameters of the child2357*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) {2364child.mLayoutParams = params; // 编者注:此处子View的mLayoutParams被设置23652366if (index < 0) {2367index = mChildrenCount;2368}23692370addInArray(child, index);23712372child.mParent = this;2373child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) | DRAWN;23742375if (child.hasFocus()) {2376requestChildFocus(child, child.findFocus());2377}2378 }在ListView/GridView中都用调用此方法来设置好子View。ViewGroup类中同时还有另外一个更主流的方法(整个Layout被从xml中铺陈开attach到Window并变得有活力的过程中该方法会被调用到) private void addViewInner(View child, int index, LayoutParams params,1973boolean preventRequestLayout) {19741975if (child.getParent() != null) {1976throw new IllegalStateException("The specified child already has a parent. " +1977"You must call removeView() on the child’s parent first.");1978}19791980if (!checkLayoutParams(params)) {1981params = generateLayoutParams(params);1982}19831984if (preventRequestLayout) {1985child.mLayoutParams = params; // 编者注:直接不理会子View请求/意愿的case,直接由父View分配,强迫子View接受1986} else {1987child.setLayoutParams(params); // 编者注:温柔一刀的做法,,非常体谅的设置给子View,到底满意不满意,取决于子View自身1988}19891990if (index < 0) {1991index = mChildrenCount;1992}19931994addInArray(child, index);19951996// tell our children1997if (preventRequestLayout) {1998child.assignParent(this);1999} else {2000child.mParent = this;2001}20022003if (child.hasFocus()) {2004requestChildFocus(child, child.findFocus());2005}20062007AttachInfo ai = mAttachInfo;2008if (ai != null) {2009boolean lastKeepOn = ai.mKeepScreenOn;2010ai.mKeepScreenOn = false;2011child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));2012if (ai.mKeepScreenOn) {2013needGlobalAttributesUpdate(true);2014}2015ai.mKeepScreenOn = lastKeepOn;2016}20172018if (mOnHierarchyChangeListener != null) {2019mOnHierarchyChangeListener.onChildViewAdded(this, child);2020}20212022if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {2023mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;2024}2025 }2026由以上代码片断综合可知,在一个View仅仅从XML中Inflate出来未被attach到View层次树里边去的时候,View.mLayoutParams成员为空。RelativeLayout为什么会NullPointerException在onMeasure回到文章开头的StackTrace,同时参照RelativeLayout的:426if (isWrapContentWidth) {427// Width already has left padding in it since it was calculated by looking at428// the right of each child view429width += mPaddingRight;430431if (mLayoutParams.width >= 0) { // 编者注:该处为onMeasure方法中第一次使用到mLayoutParams的地方432width = Math.max(width, mLayoutParams.width);433}434435width = Math.max(width, getSuggestedMinimumWidth());436width = resolveSize(width, widthMeasureSpec);437438if (offsetHorizontalAxis) {439for (int i = 0; i < count; i++) {440View child = getChildAt(i);441if (child.getVisibility() != GONE) {442LayoutParams params = (LayoutParams) child.getLayoutParams();不难看出,在RelativeLayout被add/attach到父View之前mLayoutParams成员为空,调用measure方法将导致上图标注处代码抛出空指针异常。解决方案有两种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 caseconvertView = 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 hierarchyconvertView = 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}.LayoutParamsconvertView.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做了防范:539if (mLayoutParams != null && mLayoutParams.width >= 0) {540width = Math.max(width, mLayoutParams.width);541}542543width = Math.max(width, getSuggestedMinimumWidth());544width = resolveSize(width, widthMeasureSpec);545546if (offsetHorizontalAxis) {547for (int i = 0; i < count; i++) {548View child = getChildAt(i);549if (child.getVisibility() != GONE) {550LayoutParams params = (LayoutParams) child.getLayoutParams();551final int[] rules = params.getRules(layoutDirection);552if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {553centerHorizontal(child, params, width);554} else if (rules[ALIGN_PARENT_RIGHT] != 0) {555final int childWidth = child.getMeasuredWidth();556params.mLeft = width – mPaddingRight – childWidth;557params.mRight = params.mLeft + childWidth;558}559}560}561}562}

也会有想更换的念头。旅行,正好提供了这样一个机会,

分析java.lang.NullPointerException thrown in RelativeLayout

相关文章:

你感兴趣的文章:

标签云: