【问题标题】:Updating a fragment in response to Android Navigation Drawer interaction更新片段以响应 Android Navigation Drawer 交互
【发布时间】:2015-09-06 16:13:47
【问题描述】:

我正在开发的 Android 应用程序有一个 MainActivity,应用程序的每个屏幕都作为片段实现。每个片段在 MainActivity 中被实例化为私有类变量:

public class MainActivity extends Activity implements MainStateListener {

   private FragmentManager fm = getFragmentManager();
   private BrowseFragment browseFragment = BrowseFragment.newInstance();

...

有一个“片段框架”可以加载每个屏幕片段。在应用程序中切换屏幕时,调用此代码以加载片段:

FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.frag_frame, incoming);
ft.addToBackStack(null);
ft.commit();
fm.executePendingTransactions();

每个屏幕片段都有一个监听器,使片段能够调用 MainActivity 中的各种方法:

public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mainStateListener = (MainStateListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement MainStateListener");
    }
}

我遇到的问题是从 MainActivity 中退出的 Navigation Drawer 更新片段的一个方面。导航抽屉必须更新片段,它使用以下代码来执行此操作:

        navigationDrawer.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                browseFragment.doSomethingOnBrowserFragment();
            }
        });

在您更改方向之前一切正常。然后当前屏幕片段加载正常(browseFragment)。但是,当您单击导致 doSomethingOnBrowserFragment() 方法执行的导航抽屉时,由于 mainStateListener 对象本身(附加到browseFragment)为空,我得到一个空指针异常。根据我对 Fragment 生命周期的了解,这个变量不应该为空,因为 onAttach() 方法在任何事情之前首先执行并设置 mainStateListener 变量。此外,如果我在该 browserFragment 上有一个使用 mainStateListener 对象的按钮(在方向更改之后),则单击该按钮永远不会出现此空指针问题。

堆栈跟踪:

08-04 16:23:28.937  14770-14770/co.openplanit.totago E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: co.openplanit.totago, PID: 14770
    java.lang.NullPointerException
            at co.openplanit.totago.MapFragment.enableOfflineMode(MapFragment.java:489)
            at co.openplanit.totago.MainActivity.setMapMode(MainActivity.java:663)
            at co.openplanit.totago.MainActivity.itineraryMapDrawerSelectItem(MainActivity.java:610)
            at co.openplanit.totago.MainActivity.access$200(MainActivity.java:52)
            at co.openplanit.totago.MainActivity$5.onItemClick(MainActivity.java:420)
            at android.widget.AdapterView.performItemClick(AdapterView.java:299)
            at android.widget.AbsListView.performItemClick(AbsListView.java:1158)
            at android.widget.AbsListView$PerformClick.run(AbsListView.java:2957)
            at android.widget.AbsListView$3.run(AbsListView.java:3850)
            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:5103)
            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:790)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:606)
            at dalvik.system.NativeStart.main(Native Method)

在我看来,问题可能是使用 Navigation Drawer 实际上是与 browseFragment 生命周期进行交互并导致它分离或其他原因。

任何有关如何解决此问题的建议将不胜感激。

【问题讨论】:

  • 请发布日志猫错误跟踪。
  • 我在帖子中添加了错误跟踪。
  • 根据堆栈跟踪,onItemClick 中似乎没有任何内容导致 NullPointerException。它发生在 MapFragment 的第 489 行。那一行是什么?可能在方向更改时 MapFragment 中的某些内容没有恢复并且为空。
  • 只需在清单中使用以下行 和片段 onAttach() 方法中的 setretaininstance(true) .....它将完成工作

标签: android android-fragments navigation-drawer


【解决方案1】:

我认为可能发生的是,您的活动的 browseFragment 可能与片段管理器显示的 BrowseFragment 不同(如果您单击该片段中的按钮,这似乎可以正常工作)。

在轮换时,活动将为您的 browseFragment 变量(未附加到活动)创建一个新的 BrowseFragment 实例 - private BrowseFragment browseFragment = BrowseFragment.newInstance() 在每次活动时运行已创建,但片段管理器将重用您的变量未指向的 EXISTING BrowseFragment 实例。重用的 BrowseFragment 将被附加并运行该代码以更新 mainStateListener,未使用的新 browseFragment 不会附加到活动,除非您运行添加它的 fragmentTransaction - 因此其中的 mainStateListener 将为空(未初始化)。

与其创建片段并将其存储在变量中,然后尝试在轮换后访问该变量,不如使用片段标签并根据片段管理器中的标签获取片段。

private static final String BROWSE_TAG = "browseFrag";

ft.replace(R.id.frag_frame, browseFragment, BROWSE_TAG);

navigationDrawer.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        Fragment browseFragment = fm.findFragmentByTag(BROWSE_TAG);
        if (browseFragment != null) {
            browseFragment.doSomethingOnBrowserFragment();
        }
   }
});

【讨论】:

  • 不幸的是,这种方法会导致同样的空指针问题。
  • 也许您应该发送广播并将广播接收器放入父活动中,而不是直接与父活动对话 - 然后您不必担心保留引用并通过轮换更新它们.
【解决方案2】:

我相信你需要在每次创建活动时设置你的监听器。使用弱引用也很有帮助。

请在此处查看我的答案: IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager

【讨论】:

    【解决方案3】:

    在片段管理器为您重新创建片段的情况下,保留对片段的引用可能会使您与对已分离的旧片段的引用不同步。

    解决方法是找到frag_frame中当前的片段,伪代码:

    Fragment fragment = fm.findFragmentById(R.id.frag_frame);
    if(fragment instanceof BrowseFragment) {
       // do your stuff
    }
    

    【讨论】:

    • 我相信你的解释有点不对劲。片段不会在旋转时重新创建 - 相同的片段仍然存在,所有数据也是如此(onCreate 不再运行) - OnCreateView 再次运行,因此重新创建了 GUI。重新创建 Activity 时,引用正在重新初始化 - private BrowseFragment browseFragment = BrowseFragment.newInstance(); 将运行每次旋转。话虽如此,您建议的修复应该可以工作 - 因为它是从片段管理器获取片段,而不是尝试使用新创建的 browseFragment。
    • @jt-gilkeson 关于在方向更改时没有重新创建片段的问题,您可能是对的。保留对 Fragment 的引用不会在所有情况下都存在,我记得旋转也是这种情况,但我的记忆可能是错误的。
    【解决方案4】:

    只需在 mainactivity 中用这个替换 onclick 监听器代码。

    由于 NullPointerException 而发生错误,

    原因:空指针传入替换第二列。

    解决方案:启动片段类(新片段())

    case R.id.home:
        hfragment = new homefragment();
        FragmentTransaction hfragmentTransaction= getSupportFragmentManager().beginTransaction();
        hfragmentTransaction.replace(R.id.frame, hfragment);
        hfragmentTransaction.commit();
        //do ur task here or in fragment class
        return true;
                                        
                        
    case R.id.notification:
        return true;
    
    default:
        Toast.makeText(getApplicationContext(),"Somethings Wrong",Toast.LENGTH_SHORT).show();
        return true;
    
                
    

    【讨论】:

      【解决方案5】:

      我注意到由于以下代码,您基本上是在缓存片段:

      public class MainActivity extends Activity implements MainStateListener {
         private FragmentManager fm = getFragmentManager();
         private BrowseFragment browseFragment = BrowseFragment.newInstance();
      

      但实现这一点可能很棘手。您可以创建管理这些片段的代码/类,例如使用片段数组,或者使用像 FragmentPagerAdapter 这样的类。

      如果我可以建议,不要缓存片段,因为您必须了解它的生命周期,只有当片段的布局复杂时,缓存才是一个好主意。只需在您的代码 public void onItemClick() 中创建它的一个新实例,就像 Google 的建议 @Creating a Navigation Drawer 一样,以防您没有阅读它。 网页中的代码sn-p:

      private class DrawerItemClickListener implements ListView.OnItemClickListener {
          @Override
          public void onItemClick(AdapterView parent, View view, int position, long id) {
              selectItem(position);
          }
      }
      
      private void selectItem(int position) {
          // Create a new fragment and specify the planet to show based on position
          Fragment fragment = new PlanetFragment();
          Bundle args = new Bundle();
          args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
          fragment.setArguments(args);
      
          // Insert the fragment by replacing any existing fragment
          FragmentManager fragmentManager = getFragmentManager();
          fragmentManager.beginTransaction()
                         .replace(R.id.content_frame, fragment)
                         .commit();
      

      注意:片段的新实例使用new PlanetFragment() 完成。

      【讨论】:

      • 这很有用,但令人惊讶的是,当我尝试此解决方案时,它仍然没有解决问题。我得到了同样的空指针异常。
      • @AdrianLaurenzi。在我看来,您真正的问题出在 logcat 上列出的 MapFragment 中。如果您发布另一个问题,请在 MapFragment 中发布相关代码。然后在这个帖子中告诉我你是否发布了它。祝你好运......
      猜你喜欢
      • 1970-01-01
      • 2021-07-13
      • 2014-01-17
      • 2014-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多