Фрагмент MyFragment не привязан к действию

Я создал небольшое тестовое приложение, которое представляет мою проблему. Я использую ActionBarSherlock для реализации вкладок с (Шерлоком) фрагментами.

Мой код: TestActivity.java

 public class TestActivity extends SherlockFragmentActivity { private ActionBar actionBar; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupTabs(savedInstanceState); } private void setupTabs(Bundle savedInstanceState) { actionBar = getSupportActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); addTab1(); addTab2(); } private void addTab1() { Tab tab1 = actionBar.newTab(); tab1.setTag("1"); String tabText = "1"; tab1.setText(tabText); tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class)); actionBar.addTab(tab1); } private void addTab2() { Tab tab1 = actionBar.newTab(); tab1.setTag("2"); String tabText = "2"; tab1.setText(tabText); tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class)); actionBar.addTab(tab1); } } 

TabListener.java

 public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener { private final SherlockFragmentActivity mActivity; private final String mTag; private final Class<T> mClass; public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; } /* The following are each of the ActionBar.TabListener callbacks */ public void onTabSelected(Tab tab, FragmentTransaction ft) { SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag); // Check if the fragment is already initialized if (preInitializedFragment == null) { // If not, instantiate and add it to the activity SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName()); ft.add(android.R.id.content, mFragment, mTag); } else { ft.attach(preInitializedFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag); if (preInitializedFragment != null) { // Detach the fragment, because another one is being attached ft.detach(preInitializedFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { // User selected the already selected tab. Usually do nothing. } } 

MyFragment.java

 public class MyFragment extends SherlockFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException ex) { } return null; } @Override protected void onPostExecute(Void result){ getResources().getString(R.string.app_name); } }.execute(); } } 

Я добавил часть Thread.sleep для имитации загрузки данных. Код в onPostExecute должен имитировать использование Fragment .

Когда я поворачиваю экран очень быстро между пейзажем и портретом, я получаю Исключение в коде onPostExecute :

Java.lang.IllegalStateException: Фрагмент MyFragment {410f6060}, не привязанный к Activity

Я думаю, что это MyFragment с тем, что в то же время был создан новый MyFragment и был присоединен к Activity перед завершением AsyncTask . Код в onPostExecute вызывает непривязанный MyFragment .

Но как я могу это исправить?

Solutions Collecting From Web of "Фрагмент MyFragment не привязан к действию"

Я нашел очень простой ответ: isAdded() :

Возвращаем true если фрагмент в настоящее время добавлен к его активности.

 @Override protected void onPostExecute(Void result){ if(isAdded()){ getResources().getString(R.string.app_name); } } 

Чтобы избежать onPostExecute от onPostExecute , когда Fragment не привязан к Activity это отменить AsyncTask при приостановке или остановке Fragment . Тогда isAdded() больше не понадобится. Тем не менее, рекомендуется сохранить эту проверку на месте.

Здесь я столкнулся с двумя различными сценариями:

1) Когда я хочу, чтобы асинхронная задача заканчивалась в любом случае: представьте, что мой onPostExecute хранит полученные данные, а затем вызывает слушателя для обновления представлений, поэтому, чтобы быть более эффективными, я хочу, чтобы задача была закончена в любом случае, поэтому у меня есть готовые данные, когда пользовательские имена назад. В этом случае я обычно делаю это:

 @Override protected void onPostExecute(void result) { // do whatever you do to save data if (this.getView() != null) { // update views } } 

2) Когда я хочу, чтобы асинхронная задача заканчивалась только при обновлении представлений: случай, который вы предлагаете здесь, задача только обновляет представления, не требует хранения данных, поэтому у нее нет никакой подсказки для завершения задачи, если представления Больше не показываются. Я делаю это:

 @Override protected void onStop() { // notice here that I keep a reference to the task being executed as a class member: if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true); super.onStop(); } 

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

Хотите, чтобы это помогло кому-то! 🙂

Проблема с вашим кодом заключается в том, как вы используете AsyncTask, потому что когда вы вращаете экран во время потока сна:

 Thread.sleep(2000) 

AsyncTask все еще работает, потому что вы не отменили экземпляр AsyncTask должным образом в onDestroy () до восстановления фрагмента (когда вы вращаетесь), и когда этот тот же экземпляр AsyncTask (после поворота) работает onPostExecute (), он пытается найти Ресурсы с помощью getResources () со старым экземпляром фрагмента (недопустимый экземпляр):

 getResources().getString(R.string.app_name) 

Что эквивалентно:

 MyFragment.this.getResources().getString(R.string.app_name) 

Таким образом, окончательное решение управляет экземпляром AsyncTask (чтобы отменить, если он все еще работает) до восстановления фрагмента при повороте экрана и, если он отменен во время перехода, перезапустите AsyncTask после восстановления с помощью логического флага:

 public class MyFragment extends SherlockFragment { private MyAsyncTask myAsyncTask = null; private boolean myAsyncTaskIsRunning = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(savedInstanceState!=null) { myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning"); } if(myAsyncTaskIsRunning) { myAsyncTask = new MyAsyncTask(); myAsyncTask.execute(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning); } @Override public void onDestroy() { super.onDestroy(); if(myAsyncTask!=null) myAsyncTask.cancel(true); myAsyncTask = null; } public class MyAsyncTask extends AsyncTask<Void, Void, Void>() { public MyAsyncTask(){} @Override protected void onPreExecute() { super.onPreExecute(); myAsyncTaskIsRunning = true; } @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException ex) {} return null; } @Override protected void onPostExecute(Void result){ getResources().getString(R.string.app_name); myAsyncTaskIsRunning = false; myAsyncTask = null; } } } 

Проблема заключается в том, что вы пытаетесь получить доступ к ресурсам (в данном случае, к строкам) с помощью метода getResources (). GetString (), который попытается получить ресурсы из Activity. См. Этот исходный код класса Fragment:

  /** * Return <code>getActivity().getResources()</code>. */ final public Resources getResources() { if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } return mHost.getContext().getResources(); } 

mHost – это объект, который содержит вашу активность.

Поскольку действие может не быть присоединено, ваш вызов getResources () вызовет исключение.

Принятое решение IMHO – это не путь, потому что вы просто скрываете проблему. Правильный способ – просто получить ресурсы из другого места, которые всегда гарантированно существуют, например контекст приложения:

 youApplicationObject.getResources().getString(...) 

Я столкнулся с той же проблемой, я просто добавляю экземпляр singleletone для получения ресурса, как упоминал Эрик

 MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name); 

Вы также можете использовать

 getActivity().getResources().getString(R.string.app_name); 

Я надеюсь, это поможет.

Это довольно трюковое решение для этого и утечка фрагмента из активности.

Таким образом, в случае getResource или чего-либо, зависящего от контекста активности, доступного из Фрагмента, он всегда проверяет статус активности и статус фрагментов следующим образом

  Activity activity = getActivity(); if(activity != null && isAdded()) getResources().getString(R.string.no_internet_error_msg); //Or any other depends on activity context to be live like dailog } } 

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

При отладке это выглядело так, как метод onCreate () PreferencesFragment вызывался дважды, когда отображаемое содержимое вращалось. Это уже было довольно странно. Затем я добавил проверку isAdded () за пределами блока, где будет указывать на сбой, и он решил проблему.

Вот код слушателя, который обновляет сводку настроек, чтобы показать новую запись. Он находится в методе onCreate () моего класса Preferences, который расширяет класс PreferenceFragment:

 public static class Preferences extends PreferenceFragment { SharedPreferences.OnSharedPreferenceChangeListener listener; @Override public void onCreate(Bundle savedInstanceState) { // ... listener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // check if the fragment has been added to the activity yet (necessary to avoid crashes) if (isAdded()) { // for the preferences of type "list" set the summary to be the entry of the selected item if (key.equals(getString(R.string.pref_fileviewer_textsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Display file content with a text size of " + listPref.getEntry()); } else if (key.equals(getString(R.string.pref_fileviewer_segmentsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once"); } } } }; // ... } 

Надеюсь, это поможет другим!

 if (getActivity() == null) return; 

Работает также в некоторых случаях. Просто нарушает выполнение кода из него и удостоверяется, что приложение не сбой

В моем случае методы фрагмента были вызваны после

 getActivity().onBackPressed(); 

Старая почта, но я был удивлен самым голосующим ответом.

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

 @Override public void onStop() { super.onStop(); mYourAsyncTask.cancel(true); } 

Если вы расширяете класс Application и поддерживаете статический «глобальный» объект Context, то вы можете использовать это вместо активности для загрузки ресурса String.

 public class MyApplication extends Application { public static Context GLOBAL_APP_CONTEXT; @Override public void onCreate() { super.onCreate(); GLOBAL_APP_CONTEXT = this; } } 

Если вы используете это, вы можете уйти с помощью Toast и загрузки ресурсов, не беспокоясь о жизненных циклах.