onCreateOptionsMenu is being called too many times in ActionBar using tabs

二次信任 提交于 2020-01-19 23:19:12

问题


Here is my problem. I have an app where I am using ActionBar Sherlock with tabs, fragments with option menus. Every time I rotate the emulator, menus are added for all the fragments even those that are hidded/removed (I tried both).

This is the setting: One FragmentActivity, that has an ActionBar with

  final ActionBar bar = getSupportActionBar();

  bar.addTab(bar.newTab()
        .setText("1")
        .setTabListener(new MyTabListener(new FragmentList1())));

  bar.addTab(bar.newTab()
        .setText("2")
        .setTabListener(new MyTabListener(new FragmentList2())));

  bar.addTab(bar.newTab()
        .setText("3")
        .setTabListener(new MyTabListener(new FragmentList3())));

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
  bar.setDisplayShowHomeEnabled(true);
  bar.setDisplayShowTitleEnabled(true);

The tabs all use the same Listener:

private class MyTabListener implements ActionBar.TabListener {
  private final FragmentListBase m_fragment;


  public MyTabListener(FragmentListBase fragment) {
     m_fragment = fragment;
  }


  public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
     FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager();
     FragmentTransaction transaction = fragmentMgr.beginTransaction();

        transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG);

     transaction.commit();
  }


  public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
     FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager();
     FragmentTransaction transaction = fragmentMgr.beginTransaction();

     transaction.remove(m_fragment);
     transaction.commit();
  }


  public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
  }
}

Each subclass of FragmentListBase has its own menu and therefore all 3 subclasses have :

  setHasOptionsMenu(true);

and the appropriate

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
  Log.d(TAG, "OnCreateOptionsMenu");

  inflater.inflate(R.menu.il_options_menu, menu);
}

When I run the app I can see that the onCreateOptionsMenu is being called multiple times, for all the different fragments.

I'm totally stumped.

I tried posting the most code as possible without being overwhelming, if you find that something is missing, please advise.

[Edit] I added more logging, and it turns out that the fragment is being attached twice (or more) on rotation. One thing that I notice is that everything is being called multiple times except for the onCreate() method which is being called only once.

06.704:/WindowManager(72): Setting rotation to 0, animFlags=0
06.926:/ActivityManager(72): Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=1/1/2 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=35}
07.374:/FragmentList1(6880): onAttach
07.524:/FragmentList1(6880): onCreateView
07.564:/FragmentList1(6880): onAttach
07.564:/FragmentListBase(6880): onCreate
07.564:/FragmentList1(6880): OnCreateOptionsMenu
07.574:/FragmentList1(6880): OnCreateOptionsMenu
07.604:/FragmentList1(6880): onCreateView

[Edit 2]

Ok, I started tracing back into Android code and found this part here (that I edited to shorten this post).

/com_actionbarsherlock/src/android/support/v4/app/FragmentManager.java

public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    if (mActive != null) {
        for (int i=0; i<mAdded.size(); i++) {
            Fragment f = mAdded.get(i);
            if (f != null && !f.mHidden && f.mHasMenu) {
                f.onCreateOptionsMenu(menu, inflater);
            }
        }
    }

The problem is that mAdded does indeed have multiple instances of FragmentList1 in it, so the onCreateOptionsMenu() method is "correctly" being called 3 times, but for different instances of the the FragmentList1 class. What I don't understand is why that class is being added multiple times... But that is a hell of a good lead.


回答1:


I seem to have found the problem(s). I say problem(s) because on top of the multitude of menus, there is now also an Exception.

1) the call to

  bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

which is after the calls to addTab() has a side effect of calling onTabSelected(). My TabListener would then add a FragmentList1 to the FragmentManager

2) rotating the device would destroy the Activity as expected, but would not destroy the Fragments. When the new Activity is created after rotation it would do two things :

  1. create another set of Fragments that it would add to the FragmentManager. This is what was causing the multitude of Menus
  2. call onTabSelected (via setNavigationMode()) which would perform the following code:

     if (null != fragmentMgr.findFragmentByTag(m_fragment.LIST_TAG)) {
        transaction.attach(m_fragment);
        transaction.show(m_fragment);
     }
     else {
        transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG);
     }
    

Basically if the fragment is already in the FragmentManager there is no need to add it, just show it. But there lies the problem. It's not the same Fragment! It's the Fragment that was created by the earlier instance of the Activity. So it would try to attach and show this newly created Fragment which would cause an Exception

The Solution.

There were a few things to do in order to fix all of this.

1) I moved the setNavigationMode() above the addTab()s.

2) this is how I now create my tabs:

  FragmentListBase fragment = (FragmentListBase)fragmentMgr.findFragmentByTag(FragmentList1.LIST_TAG_STATIC);
  if (null == fragment) {
     fragment = new FragmentList1();
  }
  bar.addTab(bar.newTab()
        .setText("1")
        .setTabListener(new MyTabListener(fragment)));

So upon Activity creation I have to check to see if the Fragments are already in the FragmentManager. If they are I use those instances, if not then I create new ones. This is done for all three tabs.

You may have noticed that there are two similar labels: m_fragment.LIST_TAG and FragmentList1.LIST_TAG_STATIC. Ah, this is lovely... ( <- sarcasm)

In ordrer to use my TagListener polymorphically I have declared the following non static variable in the base class:

public class FragmentListBase extends Fragment {
   public String LIST_TAG = null;
}

It is assigned from inside the descendents and allows me to look in the FragmentManager for the different descendents of FragmentListBase .

But I also need to search for specific descendents BEFORE they are created (because I need to know if I must create them or not), so I also have to declare the following static variable.

public class FragmentList1 extends FragmentListBase {
   public final static String LIST_TAG_STATIC = "TAG_LIST_1";

   public FragmentList1() {
      LIST_TAG = LIST_TAG_STATIC;
   };
}

Suffice to say that I am disapointed that nobody came up with this simple and elegant solution ( <- more sarcasm)

Thanks a lot to Jake Wharton who took the time to look at this for me :)




回答2:


public FragmentListBase() {
    setRetainInstance(true);
    setHasOptionsMenu(true);
}

This will save/restore the individual states of each of the fragments upon rotation.


Another simple change you might want to make is calling transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG) in the tab selected callback and getting rid of the content in the unselected callback.




回答3:


I had this very similar issues with "stackable" menus on rotation. I don't use tabs but I do use ViewPager with FragmentStatePagerAdapter so I can't really reuse my Fragments. After banging my head for 2 days I found very simple solution. Indeed the problem seems to be with onCreateOptionsMenu called multiple times. This little code snippet takes care (masks?) of all the problems:

/** to prevent multiple calls to inflate menu */
private boolean menuIsInflated;

@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
    if (!menuIsInflated) {
        inflater.inflate(R.menu.job_details_fragment_menu, menu);
        menuIsInflated = true;
    }
}



回答4:


What worked for me was moving the setHasMenuOptions(true) to the calling activity ie the activity in which the fragment was declared. I previously had it in the onCreate method of the fragment.

Here is the code snippet:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        ForecastFragment forecastFragment = new ForecastFragment();
        forecastFragment.setHasOptionsMenu(true);
        fragmentTransaction.add(R.id.fragment, forecastFragment);
        fragmentTransaction.commit();
    }



回答5:


Just a quite note on your polymorphic tag frustrations.

Declare your base class like so:

public abstract class ListFragmentBase {
  protected abstract String getListTag();
}

Now declare your sub classes something like this:

public class FragmentList1 extends ListFragmentBase {
    public static final String LIST_TAG = "TAG_LIST_1";

    @Override
    protected String getListTag() {
        return LIST_TAG;
    }
}

Now the polymorphic way to get the instance tag is like this:

ListFragmentBase frag = new FragmentList1();
frag.getListTag();

Get the tag statically like so:

FragmentList1.LIST_TAG;



回答6:


At least on Honeycomb related SDK's the problem is solved by adding

android:configChanges="orientation"

to the Activity declaration in your AndroidManifest.xml file. You still can add and remove fragments as shown in the Adding Tabs section of http://developer.android.com/guide/topics/ui/actionbar.html



来源:https://stackoverflow.com/questions/7224415/oncreateoptionsmenu-is-being-called-too-many-times-in-actionbar-using-tabs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!