Фрагмент MyFragment не привязан к активности
я создал небольшое тестовое приложение, которое представляет собой моя проблема. Я использую ActionBarSherlock для реализации вкладок с фрагментами (Sherlock).
мой код:
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.ленг.IllegalStateException: фрагмент MyFragment{410f6060} нет прилагается к деятельности
я думаю, что это потому, что новый MyFragment
был создан в то же время, и был прикреплен к деятельности перед AsyncTask
закончил. Код в onPostExecute
призывает непривязанного MyFragment
.
но как я могу это исправить?
11 ответов:
Я нашел очень простой ответ:
isAdded()
:возвращение
true
Если фрагмент в данный момент добавлен к его активности.@Override protected void onPostExecute(Void result){ if(isAdded()){ getResources().getString(R.string.app_name); } }
избежать
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(); }
Я не нашел никаких проблем с этим, хотя я также использую (возможно) более сложный способ, который включает запуск задач из действия вместо фрагментов.
жаль, что это помогает кому-то! :)
проблема в том, что вы пытаетесь получить доступ к ресурсам (в данном случае, строк) с помощью getResources().getString (), который будет пытаться получить ресурсы из действия. Смотрите этот исходный код класса фрагмента:
/** * 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(...)
проблема с вашим кодом заключается в том, как вы используете 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; } } }
их довольно хитрое решение для этого и утечки фрагмента из активности.
таким образом, в случае 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 } }
я столкнулся с той же проблемой я просто добавить синглетный экземпляр, чтобы получить ресурс, как указано Эрик
MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);
вы также можете использовать
getActivity().getResources().getString(R.string.app_name);
Я надеюсь, что это поможет.
if (getActivity() == null) return;
работает также в некоторых случаях. Просто ломает выполнение кода из него и убедитесь, что приложение не сбой
я столкнулся с аналогичными проблемами, когда активность настроек приложения с загруженными настройками была видна. Если бы я изменил одно из предпочтений, а затем заставил содержимое дисплея вращаться и снова изменить предпочтение, он бы аварийно завершил работу с сообщением о том, что фрагмент (мой класс предпочтений) не был прикреплен к действию.
при отладке это выглядело как onCreate () метод PreferencesFragment вызывается дважды, когда содержимое дисплея вращается. Это было уже достаточно странно. Затем я добавил проверку isAdded () за пределами блока, где она будет указывать на сбой, и это решило проблему.
вот код прослушивателя, который обновляет сводку настроек, чтобы показать новую запись. Он расположен в методе onCreate () класса my 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"); } } } }; // ... }
Я надеюсь, что это поможет другим!
Если вы расширяете
Application
класс и поддерживать статический "глобальный" объект контекста, как показано ниже, то вы можете использовать это вместо действия для загрузки строкового ресурса.public class MyApplication extends Application { public static Context GLOBAL_APP_CONTEXT; @Override public void onCreate() { super.onCreate(); GLOBAL_APP_CONTEXT = this; } }
Если вы используете это, вы можете уйти с
Toast
и загрузка ресурсов, не беспокоясь о жизненных циклах.
старый пост, но я был удивлен самым популярным ответом.
правильным решением для этого должно быть отменить asynctask в onStop (или там, где это уместно в вашем фрагменте). Таким образом, вы не вводите утечку памяти (asynctask, хранящий ссылку на ваш уничтоженный фрагмент), и у вас есть лучший контроль над тем, что происходит в вашем фрагменте.
@Override public void onStop() { super.onStop(); mYourAsyncTask.cancel(true); }