FrameLayout.onMeasure()中的NullPointerException

编辑:我创build了一个Github项目,崩溃的方式和我的应用程序完全相同。 你可以在这里find它

编辑:正如我debugging了这一点,并尝试各种更改,我意识到我张贴的原始代码并不真正相关,所以我删除了它:

我有一个Activity ,可以切换3个不同的Fragment之间,当用户按下3个IconButton的一个,我已经插入到自定义布局的ActionBar中,就像在各种模式之间切换:

在这里输入图像说明

当我第一次启动应用程序(卸载它),我默认为全球模式。 Globe模式在ViewPager有几个片段,大多数片段是android.support.v4.app.ListFragment子类。 我在主Activity的onCreate方法中创build了所有8个选项卡。 这按预期工作,一切正常加载。

当我进入“个人档案”模式(通过点击脸部图标button),我可以login到应用程序,并看到我的个人资料。 这也按预期工作,一切正常加载。

当我再次从Android Studio 推送APK时 ,由于我现在已经login(已保存的首选项),因此默认为“好友进纸”模式(双手持握)。 我可以切换模式到configuration文件,一切都按预期工作,但是当我切换到地球模式时,它在Android代码崩溃:

 08-14 14:43:22.744 28804-29323/com.trover E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.trover, PID: 28804 java.lang.NullPointerException at android.widget.FrameLayout.onMeasure(FrameLayout.java:309) at android.view.View.measure(View.java:16497) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125) at com.android.internal.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:327) at android.view.View.measure(View.java:16497) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125) at android.widget.FrameLayout.onMeasure(FrameLayout.java:310) at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2291) at android.view.View.measure(View.java:16497) at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1912) at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1109) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1291) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761) at android.view.Choreographer.doCallbacks(Choreographer.java:574) at android.view.Choreographer.doFrame(Choreographer.java:544) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5001) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601) at dalvik.system.NativeStart.main(Native Method) 

在过去的两周里,我对这个项目做了两个重大的改变:

  1. 我已经删除了ActionbarSherlock库。 我原本希望能够移除support.v4库,直到我意识到我需要ViewPager。
  2. 我从Eclipse切换到Android Studio,其中包括将所有文件移动到新的Android Studio结构中。

今天早上,我重新安装了Eclipse,并在删除ActionbarSherlock后立即进入了正确的位置,并且无法重新创build这个崩溃。

我已经尝试过的事情:

  • 用Gradle手动构build项目,然后手动安装并运行它。 当我做同样的步骤时,它以同样的方式崩溃。
  • 通过debugging器运行,并停在崩溃。 309行如下:

      if (mMeasureAllChildren || child.getVisibility() != GONE) { 

在代码的这一点上, childnull ,但是,对第296行的getChildCount()的调用返回值为2. 我仍然不知道哪一个View实际上在这一点崩溃,没有任何定义值在debugging器中通过它们浏览variables时,

  • 当您login/注销时,溢出菜单中的项目将被更改。 当您点击顶部的IconButton ,我会调用IconButton上的setBackgroundDrawable来设置黄色突出显示(如果不再是活动button,则设置为null )。 当我注释掉onCreateOptionsMenu的代码和setHighlightedIconButton()的代码时,崩溃不再发生
  • 当我从ViewPager删除所有的片段时,崩溃不再发生
  • 正如下面提到的@ kcoppock,可能是因为我不适当地在某个地方夸大了某些东西。 在他链接的文章inflater.inflate(R.layout.somelayout, viewGroupContainer, false) ,我修改了每个调用inflater.inflate(R.layout.somelayout, null)应用程序,而不是inflater.inflate(R.layout.somelayout, viewGroupContainer, false)在那里我创build自定义视图的标签(我不知道viewGroup父母下)。 在这种情况下,我手动设置LayoutParameters。

在这一点上,我完全不知所措,接下来要做甚么。

更新:更改应用程序始终在全球模式启动防止崩溃,所以它似乎是切换到地球模式,如果我们没有开始活跃。

这里是我的代码来build立主Activity的viewPager的标签:

  // We HAVE to read this value out before creating the layout, or onTabSelected will set it back to zero final SharedPreferences prefs = getApplicationContext().getSharedPreferences( Const.Preferences.PREFS_FILE, Context.MODE_PRIVATE); boolean deniedLocationServices = prefs.getBoolean(Const.Preferences.LOCATION_SERVICES_DENIED, false); int previousTab = prefs.getInt(Const.Preferences.PREVIOUS_TAB, 0); setContentView(R.layout.main_browse); mFragmentManager = getSupportFragmentManager(); mActionBar = getActionBar(); mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); // these three lines hide the 'Trover' logo mActionBar.setDisplayHomeAsUpEnabled(false); mActionBar.setDisplayUseLogoEnabled(false); mActionBar.setIcon(new ColorDrawable(android.R.color.black)); mActionBar.setCustomView(R.layout.action_bar_icons); mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME); final int numTabs = 8; TabbedBrowsePagerTab[] tabs = new TabbedBrowsePagerTab[numTabs]; tabs[mAllTabPosition] = buildAllTab(); tabs[mWhatsHotTabPosition] = buildWhatsHotTab(); tabs[mWhatsNewTabPosition] = buildWhatsNewTab(); tabs[mJumpToTabPosition] = buildJumpToTab(); tabs[mFoodTabPosition] = buildFoodTab(); tabs[mOutdoorTabPosition] = buildOutdoorTab(); tabs[mArtTabPosition] = buildArtTab(); tabs[mLatestTabPosition] = buildLatestTab(); mPagerAdapter = new TabbedBrowsePagerAdapter(this, tabs); mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mPagerAdapter); mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(final int position) { try { mActionBar.setSelectedNavigationItem(position); } catch (final IllegalStateException e) { TroverApplication.logError(TAG, "IllegalArgumentsException setting tab position in PageChangeListener"); } } }); // Build the actual tabs on the actionbar for (int i = 0; i < tabs.length; i++) { mActionBar.addTab(mActionBar.newTab() .setTabListener(this)); LayoutInflater inflater = LayoutInflater.from(this); View customView = inflater.inflate(R.layout.custom_action_bar_tabs, null); customView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); TextView tabCustom = (TextView) customView.findViewById(R.id.custom_action_bar_tab); tabCustom.setText(tabs[i].getTabTitle()); tabCustom.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); tabCustom.setTypeface(TroverApplication.getDefaultFontBold()); mActionBar.getTabAt(i).setCustomView(tabCustom); } // Now set up the button listeners for those icons mActionBar.getCustomView().findViewById(R.id.action_bar_friend_feed_button).setOnClickListener(this); mActionBar.getCustomView().findViewById(R.id.action_bar_me_button).setOnClickListener(this); mActionBar.getCustomView().findViewById(R.id.action_bar_nearby_button).setOnClickListener(this); mViewPager.setCurrentItem(previousTab); TroverLocationManager manger = TroverLocationManager.get(); if (!manger.isNetworkLocationEnabled() && !deniedLocationServices) { showLocationServicesDialog(); } if (AuthManager.get().isAuthenticated()) { mCurrentMode = Mode.NEWS_MODE; // <----- if I change this to GLOBE_MODE it doesn't crash } else { mCurrentMode = Mode.GLOBE_MODE; } validateView(); 

这是main_browse.xml布局:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:layout_width="fill_parent" > <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" /> <Button android:id="@+id/main_camera_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_marginBottom="15dp" android:layout_marginRight="15dp" android:background="@drawable/camera_floating" /> </RelativeLayout> 

这里是setHighlightedIconButton函数:

 private void setHighlightedIconButton() { ImageButton button; View iconContainer = mActionBar.getCustomView(); PaintDrawable selectedBackground = new PaintDrawable(getResources().getColor(R.color.trover_selected_icon_background)); button = (ImageButton) iconContainer.findViewById(R.id.action_bar_friend_feed_button); if (mCurrentMode == Mode.NEWS_MODE) { button.setBackgroundDrawable(selectedBackground); button.setClickable(false); } else { button.setBackgroundDrawable(null); button.setClickable(true); } button = (ImageButton) iconContainer.findViewById(R.id.action_bar_nearby_button); if (mCurrentMode == Mode.GLOBE_MODE) { button.setBackgroundDrawable(selectedBackground); button.setClickable(false); } else { button.setBackgroundDrawable(null); button.setClickable(true); } button = (ImageButton) iconContainer.findViewById(R.id.action_bar_me_button); if (mCurrentMode == Mode.PROFILE_MODE) { button.setBackgroundDrawable(selectedBackground); button.setClickable(false); } else { button.setBackgroundDrawable(null); button.setClickable(true); } } 

这是调用它的validateView()函数:

 /** * Handles transitions between different fragments by checking the current mode and authentication state. * This function can handle being called multiple times, and will always try to do the least work possible * each time. */ private void validateView() { invalidateOptionsMenu(); setHighlightedIconButton(); boolean authenticated = AuthManager.get().isAuthenticated(); switch(mCurrentMode) { case GLOBE_MODE: // Note - we don't record a screen here because the tabs will do that removeMeFragment(); removeNewsFragment(); removeOnboardingFragment(); mViewPager.setVisibility(View.VISIBLE); mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); mPagerAdapter.madeVisible(); break; case NEWS_MODE: if (mPreviousTabPosition == mJumpToTabPosition) { InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0); } recordScreen(getCurrentModeTrackingString(), this); removeMeFragment(); removeOnboardingFragment(); if (mNewsFeedFragment == null) { mNewsFeedFragment = DiscoveryListFragment.newNewsFeedInstance(); mFragmentManager.beginTransaction().add(android.R.id.content, mNewsFeedFragment).commit(); } mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); mViewPager.setVisibility(View.GONE); break; case PROFILE_MODE: if (mPreviousTabPosition == mJumpToTabPosition) { InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0); } if (authenticated) { recordScreen(getCurrentModeTrackingString(), this); removeNewsFragment(); removeOnboardingFragment(); if (mProfileFragment == null) { mProfileFragment = UserDetailFragment.newMeInstance(); mFragmentManager.beginTransaction().add(android.R.id.content, mProfileFragment).commit(); } } else { recordScreen(getCurrentModeTrackingString() + "/onboarding", this); removeMeFragment(); removeNewsFragment(); if (mOnboardingFragment == null) { removeOnboardingFragment(); mOnboardingFragment = new OnboardingFragment(); mFragmentManager.beginTransaction().add(android.R.id.content, mOnboardingFragment).commit(); } } mViewPager.setVisibility(View.GONE); break; default: TroverApplication.logError(TAG, "Invalid mode!"); } } 

这里是主Activity的onCreateOptionsMenu

 public boolean onCreateOptionsMenu(final Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); if (!AuthManager.get().isAuthenticated()) { MenuItem item = menu.findItem(R.id.menu_notifications); if (item != null) { item.setVisible(false); } item = menu.findItem(R.id.menu_recommended_users); if (item != null) { item.setVisible(false); } } return true; } 

这是FrameLayout.onMeasure()函数的一部分,它是Android API 19源代码崩溃的一部分:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); <-- this is returning 2 final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { <-- crash happens here measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } 

Solutions Collecting From Web of "FrameLayout.onMeasure()中的NullPointerException"

 View customView = inflater.inflate(R.layout.custom_action_bar_tabs, null); 

这可能是你的问题的根源。 如果你传入null ,inflater不知道要生成什么types​​的LayoutParams(它从父ViewGroup生成它们)。 我相信这生成了默认的ViewGroup.LayoutParams,但也许它根本不提供任何的LayoutParams。

您应该将其replace为:

 View customView = inflater.inflate(R.layout.custom_action_bar_tabs, parent, false); 

其中parent是将要添加customView的ViewGroup。 如果您没有可用的父级,则可以手动设置一些自定义LayoutParams:

 View customView = inflater.inflate(R.layout.custom_action_bar_tabs, null); customView.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 

问题2:

由于在布局过程中引发了exception,因此在validateView方法中的两个调用看起来有些不正确。 将这两个调用replace为: mViewPager.setVisibility(View.GONE);mViewPager.setVisibility(View.INVISIBLE);

当进入全球模式时,消失和可见之间的转换是触发布局。 从不可见到可见不会触发布局,这是NPE发生的一个很好的猜测。

更新。 根据你的评论,让我们假设已经消失 – >可见的过渡只是触发一个不同的布局,即在你的自定义操作栏。

通过转储示例应用程序(Eclipse> DDMS> Dump View Hierachy for View Automator)的视图层次结构,我在我的操作栏中find了一些FrameLayouts ,这很有趣。 我的示例应用程序当然不是使用您的自定义视图,但即使在操作栏的图标/主页部分,框架布局中也有一个ImageView

所以,请尝试replace这个棘手的代码:

 // these three lines hide the 'Trover' logo mActionBar.setDisplayHomeAsUpEnabled(false); mActionBar.setDisplayUseLogoEnabled(false); mActionBar.setIcon(new ColorDrawable(android.R.color.black)); mActionBar.setCustomView(R.layout.action_bar_icons); mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM|ActionBar.DISPLAY_SHOW_HOME); 

这个简单的代码应该以安全的方式做同样的事情:

 mActionBar.setCustomView(R.layout.action_bar_icons); mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); 

如果不是这样,那么抛弃实际的视图并在自定义操作栏中调查FrameLayouts应该是有帮助的。

我最终提出了一个针对AOSP的错误:

https://code.google.com/p/android/issues/detail?id=75056

我到那里(相当简洁)的回应表明,在ViewPager.onMeasure()函数中,它试图执行任何悬而未决的Fragment事务。 不幸的是,这听起来像我的显示/隐藏的其他碎片干扰这个过程,并导致NPE。

他们鼓励的解决方法是我在我的validateView()函数的末尾调用FragmentManager.executePendingTransactions() ,大概是为了最终确定我已经提交的片段事务,以便viewPager不受它们的影响。

你有错误的ID:

 <ProgressBar android:id="@+id/trover_list.loading" ... /> 

你不能使用点. 焦炭。

只是一个预感,但尝试移动setHighlightedIconButton(); 之后在validateView()中切换大小写。 这可能会迫使NEWS_MODE的孩子实例化,当你改变背景时,有希望阻止onMeasure中的空指针。

我会刺穿它。 跳出来的一件事是dynamic选项菜单以非常规方式实现。 你应该使用:

 public boolean onCreateOptionsMenu(final Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return super.onCreateOptionsMenu(menu); } 

其次是:

 public boolean onPrepareOptionsMenu(final Menu menu) { boolean auth = AuthManager.get().isAuthenticated(); MenuItem item = menu.findItem(R.id.menu_notifications); if (item != null) { item.setVisible(auth); } item = menu.findItem(R.id.menu_recommended_users); if (item != null) { item.setVisible(auth); } return super.onPrepareOptionsMenu(); } 

这两个改变是:(1)使用onPrepareOptionsMenu对菜单进行dynamic更改,(2)确保调用超类。

看看我自己的代码,我看到我已经违反规则(2),而没有问题,但是当代码崩溃是值得一试。

对于Android 3+,您需要调用invalidateOptionsMenu()来触发on prepare方法的新调用,但是我已经在代码中看到了,所以应该没问题。