После ротации транзакция фрагмента дает IllegalStateException

У меня проблема с Fragments и BroadcastManager. В моем приложении я переключился на одно основное действие и использовал новый NavigationDrawer. Все содержимое содержится в фрагментах.

Один фрагмент (поиск пользователей) содержит две вкладки (поиск по имени, поиск по критерию) с использованием шаблона навигации «Боковая навигация» с веб-сайта Android Design: он имеет ViewPager с FragmentStatePagerAdapter.

| Main Activity | | Main Activity | ------------------- ------------------- | Search Fragment | | Search Fragment | | >Tab 1< | Tab 2 | | Tab 1 | >Tab 2< | | View Pager | | View Pager | | Fragment 1 | | Fragment 2 | 

Я хочу, чтобы обе вкладки использовали одно и то же меню действий (опции): действие поиска. Но на него должен реагировать только активный фрагмент.

Я пробовал разные подходы, наиболее раздражающим было то, что я не могу легко получить текущий фрагмент из пейджера представления напрямую (не полагаясь на детали не-API-реализации).

Подход

Теперь я использую LocalBroadcast для уведомления фрагментов, которые были нажаты на поиск. Каждый фрагмент регистрирует небольшой Wrapper-Receiver в onResume (и удаляет его в onPause), который пересылает onReceive самому фрагменту (метод, показанный ниже). Я переопределяю setMenuVisibility который является обратным вызовом, который FragmentStatePagerAdpater вызывает Фрагменты, чтобы узнать, что является активным фрагментом. Только фрагмент, который имеет видимое меню, будет реагировать на трансляцию.

 ActionBar Tab -> ViewPager -> ViewPagerAdapter -> Fragment.setMenuVisibility ActionBar Menu Action -> Broadcast -> ... -> BroadcastReceiver -> Fragments 

Оба фрагмента запускают транзакцию фрагментов для отображения результатов поиска и добавления их в задний стек.

Проблема. Операция фрагмента работает в целом, но когда я поворачиваю устройство, а затем нажимаю поиск, я получаю исключение IllegalStateException (не может фиксироваться после onSaveInstanceState).

 @Override public void onReceive(Context context, Intent intent) { if (m_menuVisible) { final String s = ((TextView) getView().findViewById(R.id.search_text)).getText().toString(); SearchResultsFragment f = new UserSearchResultsFragment(); Bundle b = new Bundle(); b.putString(SearchResultsFragment.EXTRA_SEARCHNAME, s); f.setArguments(b); FragmentTransaction transaction = getSherlockActivity().getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container_frame, f).addToBackStack(null).commit(); // <<< CRASH } } 

Я попытался изменить фиксацию на commitAllowingStateLoss , но затем я получил исключение IllegalStateException с «Активность была уничтожена».

Вы знаете, что здесь не так? Я в недоумении, что делать …


Дополнительный код:

MainActivities onCreate (на основе примера NavigationDrawer) и selectItem

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.fragment_container_layout); setupDrawer(); // Sets up the drawer layout, adapter, etc. if (savedInstanceState == null) { selectItem(0); // Selects and adds the fragment(s) for the position } // Other setup stuff } private void selectItem(int position) { // update the main content by replacing fragments Fragment f = null; switch (position) { case 0: f = ... break; case 1: ... default: throw new IllegalArgumentException("Could not find right fragment"); } f.setRetainInstance(true); m_drawerList.setItemChecked(position, true); setTitle(mDrawerTitles.get(position).titleRes); // Hide any progress bar that might be visible in the actionbar setProgressBarIndeterminateVisibility(false); // When we select something from the navigation drawer, the back stack is discarded final FragmentManager fm = getSupportFragmentManager(); fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); fm.beginTransaction().replace(R.id.fragment_container_frame, f).commit(); // update selected item and title, then close the drawer m_drawerLayout.closeDrawer(GravityCompat.START); } 

FragmentStatePagerAdapter в неисправном фрагменте с вкладками:

 protected class TabPagerAdapter extends FragmentStatePagerAdapter { private List<String> m_fragmentTags = new ArrayList<String>(); public TabPagerAdapter(FragmentManager fm) { super(fm); } public void addFragments(List<String> list) { ViewPager pager = (ViewPager) getView().findViewById(R.id.viewpager); startUpdate(pager); for (String tag : list) { m_fragmentTags.add(tag); } finishUpdate(pager); notifyDataSetChanged(); } public void addFragment(String tag) { addFragments(Collections.singletonList(tag)); } @Override public Fragment getItem(int pos) { // CreateFragmentForTag: Retrieves the classes, instantiates the fragment // Does not do retainInstance or any transaction there. return createFragmentForTag(m_fragmentTags.get(pos)); } @Override public int getCount() { return m_fragmentTags.size(); } } 

Урезанная версия проекта, содержащая только основные сведения, размещена на странице https://docs.google.com/file/d/0ByjUMh5UybW7cnB4a0NQeUlYM0E/edit?usp=sharing

Воспроизведите его следующим образом: запустите, вы увидите две вкладки. Выберите и нажмите кнопку Поиск в транзакции ActionBar -> Fragment. Используйте кнопку «Назад», поверните устройство и повторите -> Сбой! [Возможно, файл недоступен после того, как баунти закончилась или обнаружена ошибка.]

Изменить : (1) Уточнено, что onReceive живет в контексте фрагмента (2) Добавлен код для основной деятельности

Перемещение моего комментария в ответ здесь и расширение его.

Первая подсказка заключается в том, что вы рушитесь только после вращения, и вы setRetainInstance(true) . Сначала попробуйте установить значение false . Даже если это предотвратит крах, вам все равно нужно быть осторожным. Это может указывать на другую ошибку, которую вы теперь скрываете, не сохраняя экземпляр фрагмента.

Есть ли определенная причина, по которой вы setRetainInstance(true) в первую очередь?

Что касается того, почему вы впервые видели сбой, я все еще не могу указать проблему. Мое подозрение, что неправильный onReceive вызывается после вращения, но это всего лишь выстрел в темноте.

Если бы вы могли создать скелет своего приложения и где-нибудь отправить код, я бы мог посмотреть на него. Я знаю, что вы уже обобщили, как вы «продвигаете» onReceive() , но есть возможности для внесения ошибок там, если это не сделано правильно.

Если это невозможно, вот что я предлагаю – поставьте записи в основных жизненных циклах вашей Activity , ваших Fragment и в onReceive() . Это скажет вам точную последовательность того, что происходит.

Причина ошибки, которую вы получаете изначально при использовании commit() заключается в том, что транзакция должна быть зафиксирована перед любым вызовом onSaveInstanceState() для содержащего действия, и именно поэтому ошибка была исправлена ​​при использовании commitAllowingStateLoss () , что позволяет Потеря состояния после совершения.

После того, как этот метод изменится, вы будете получать исключение Activity has been destroyed во время изменений ориентации, поскольку фиксация фрагмента содержит ссылку на исходную активность, которая была уничтожена во время изменения ориентации. Таким образом, вместо определения фрагмента в вашем макете xml вы можете определить контейнер фрагмента и добавить свой фрагмент в контейнер в onCreate() родительской активности, чтобы убедиться, что когда активность воссоздана, вы также создаете новый фрагмент и фиксируете

Здесь вы можете найти информацию о setRetainInstance .