3

Мое приложение состоит из нескольких фрагментов. До сих пор у меня были ссылки на них, хранящиеся в пользовательском объекте приложения, но я начинаю думать, что я делаю что-то неправильно.Ссылка фрагмента на mActivity становится нулевой после изменения ориентации. Неэффективное обслуживание состояния фрагментов

Мои проблемы начались, когда я понял, что все ссылки моего фрагмента на mActivity становятся нулевыми после изменения ориентации. Поэтому, когда я вызываю getActivity() после изменения ориентации, генерируется исключение NullPointerException. Я проверил, что мой onAttach() вызывается перед тем, как сделать вызов getActivity(), но он все равно возвращает null.

Ниже приведена разделенная версия моего MainActivity, которая является единственным видом деятельности в моем приложении.

public class MainActivity extends BaseActivity implements OnItemClickListener, 
     OnBackStackChangedListener, OnSlidingMenuActionListener { 

    private ListView mSlidingMenuListView; 
    private SlidingMenu mSlidingMenu; 

    private boolean mMenuFragmentVisible; 
    private boolean mContentFragmentVisible; 
    private boolean mQuickAccessFragmentVisible; 

    private FragmentManager mManager; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     /* 
     * Boolean variables indicating which of the 3 fragment slots are visible at a given time 
     */ 
     mMenuFragmentVisible = findViewById(R.id.menuFragment) != null; 
     mContentFragmentVisible = findViewById(R.id.contentFragment) != null; 
     mQuickAccessFragmentVisible = findViewById(R.id.quickAccessFragment) != null; 

     if(!savedInstanceState != null) { 
      if(!mMenuFragmentVisible && mContentFragmentVisible) { 
       setupSlidingMenu(true); 
      } else if(mMenuFragmentVisible && mContentFragmentVisible) { 
       setupSlidingMenu(false); 
      } 

      return; 
     } 

     mManager = getSupportFragmentManager(); 
     mManager.addOnBackStackChangedListener(this); 

     final FragmentTransaction ft = mManager.beginTransaction(); 
     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 

     if (!mMenuFragmentVisible && mContentFragmentVisible) { 
      /* 
      * Only the content fragment is visible, will enable sliding menu 
      */ 
      setupSlidingMenu(true); 
      onToggle(); 

      ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG); 

     } else if (mMenuFragmentVisible && mContentFragmentVisible) { 
      setupSlidingMenu(false); 
      /* 
      * Both menu and content fragments are visible 
      */ 
      ft.replace(R.id.menuFragment, getCustomApplication().getMenuFragment(), MenuFragment.TAG); 
      ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG); 
     } 

     if (mQuickAccessFragmentVisible) { 
      /* 
      * The quick access fragment is visible 
      */ 
      ft.replace(R.id.quickAccessFragment, getCustomApplication().getQuickAccessFragment()); 
     } 

     ft.commit(); 
    } 

    private void setupSlidingMenu(boolean enable) { 
     /* 
     * if enable is true, enable sliding menu, if false 
     * disable it 
     */ 
    } 

    @Override 
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 

     // launch the fragment that was clicked from the menu 
    } 

    @Override 
    public void onBackPressed() { 
     // Will let the user press the back button when 
     // the sliding menu is open to display the content. 
     if (mSlidingMenu != null && mSlidingMenu.isMenuShowing()) { 
      onShowContent(); 
     } else { 
      super.onBackPressed(); 
     } 
    } 

    @Override 
    public void onBackStackChanged() { 
     /* 
     * Change selected position when the back stack changes 
     */ 
     if(mSlidingMenuListView != null) { 
      mSlidingMenuListView.setItemChecked(getCustomApplication().getSelectedPosition(), true);  
     } 
    } 

    @Override 
    public void onToggle() { 
     if (mSlidingMenu != null) { 
      mSlidingMenu.toggle(); 
     } 
    } 

    @Override 
    public void onShowContent() { 
     if (mSlidingMenu != null) { 
      mSlidingMenu.showContent(); 
     } 
    } 
} 

Ниже приведена разделенная версия CustomApplication. Мои мысли, стоящие за этой реализацией, заключались в том, чтобы гарантировать только один экземпляр каждого фрагмента на протяжении всего жизненного цикла приложения.

public class CustomApplication extends Application { 

    private Fragment mSsportsFragment; 
    private Fragment mCarsFragment; 
    private Fragment mMusicFragment; 
    private Fragment mMoviesFragment; 

    public Fragment getSportsFragment() { 
     if(mSsportsFragment == null) { 
      mSsportsFragment = new SportsFragment(); 
     } 

     return mSsportsFragment; 
    } 

    public Fragment getCarsFragment() { 
     if(mCarsFragment == null) { 
      mCarsFragment = new CarsFragment(); 
     } 

     return mCarsFragment; 
    } 

    public Fragment getMusicFragment() { 
     if(mMusicFragment == null) { 
      mMusicFragment = new MusicFragment(); 
     } 

     return mMusicFragment; 
    } 

    public Fragment getMoviesFragment() { 
     if(mMoviesFragment == null) { 
      mMoviesFragment = new MoviesFragment(); 
     } 

     return mMoviesFragment; 
    } 
} 

Мне очень интересны советы о том, как наилучшим образом реализовать множественные фрагменты и как поддерживать их состояния. Для вашей информации мое приложение состоит из 15 + фрагментов. Я провел некоторое исследование, и кажется, что FragmentManager.findFragmentByTag() - хорошая ставка, но я не смог его успешно реализовать.

Мое выполнение, похоже, работает хорошо, за исключением того факта, что ссылки на токтивность становятся нулевыми после изменений ориентации, что позволяет мне поверить, что у меня могут быть проблемы с утечкой памяти.

Если вам нужно увидеть больше кода, пожалуйста, дайте мне знать. Я намеренно избегал использования кода фрагмента, так как я уверен, что проблемы связаны с моими реализациями Activity и Application, но я могу ошибаться.

Спасибо за ваше время.

ответ

10

Мои мысли позади этой реализации было гарантировать только один экземпляр каждого фрагмента на протяжении всего жизненного цикла моего приложения

Это, вероятно, часть, если не все, из источника вашей трудности.

При изменении конфигурации Android заново создаст ваши фрагменты, используя конструктор с открытым аргументом нуля для создания нового экземпляра. Следовательно, ваши фрагменты глобальной области не будут «гарантировать только один экземпляр каждого фрагмента».

Удалите этот обычай Application класс. Пожалуйста, позвольте фрагментам восстанавливаться естественным образом, или если они должны жить в течение одного действия, используйте setRetainInstance(true). Не пытайтесь повторно использовать фрагменты в разных действиях.

+0

Спасибо за ваш быстрый ответ! Я сделаю необходимые изменения в соответствии :) Только один быстрый вопрос: когда мое приложение будет уничтожено Android для ресурсов, как я могу гарантировать, что когда пользователь запустит мое приложение, последний фрагмент, который он видел (до того, как приложение было уничтожено) отображается вместо «начального фрагмента» по умолчанию? Мне также нужно, чтобы мой фрагмент «меню» имел правильную позицию «выбрано», когда это происходит. Предпочтения приходят на ум, но у вас может быть лучшая идея? –

+3

@MortenSalte: «Как я могу гарантировать, что когда пользователь запустит мое приложение, последний фрагмент, который он просматривал (до того, как приложение было уничтожено), отображается« - в 'onPause()' или 'onStop()', выписывает («SharedPreferences', file, database), которая будет сохраняться после завершения процесса. «Предпочтения приходят на ум, но у вас может быть лучшая идея?» - предпочтения будут типичным решением для чего-то подобного, но любой упорный магазин должен работать. – CommonsWare

3

Я не вижу, где вы используете ссылку на mActivity. Но не держите ссылку на нее. Всегда используйте getActivity, так как активность может быть воссоздана после изменения ориентации. Кроме того, не ступала поля фрагмента путем сеттеров или путем присвоения всегда использовать Bundle и аргументы

Best practice for instantiating a new Android Fragment

Также вы можете использовать setRetainInstance (истина), чтобы сохранить все элементы фрагмента во время изменения ориентации.

Understanding Fragment's setRetainInstance(boolean)

2

Чтобы решить эту проблему, вы должны использовать объект деятельности, предусмотренный методом onAttach фрагмента таким образом при изменении фрагмента ориентации воссоздано так onAttach дать вам ссылку на текущую

0

Если вы не» t setRetainInstance(true) в onCreate ... коллекция, например, List<Object>, Vector<Object> в классе Application, получит значение null. Удостоверьтесь, что вы сделали setRetainInstance(true), чтобы оживить их.

0

вы можете использовать onAttach(Context context) создать приватный переменный контекст в фрагменте как этого

@Override 
public void onAttach(Context context) { 
    this.context = context; 
    super.onAttach(context); 
} 

при изменении ориентации, onAttach дает вам новую ссылку на контекст, если вы хотите ссылку на деятельность, вы можете типаж контекста Мероприятия.