Я создал небольшое тестовое приложение, которое представляет мою проблему. Я использую 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} не присоединен к операции
Я думаю, это потому MyFragment
, что тем временем был создан новый объект , который был прикреплен к действию до его AsyncTask
завершения. Код в onPostExecute
призыве к неприсоединению MyFragment
.
Но как я могу это исправить?
mView = inflater.inflate(R.layout.my_layout, container, false)
И теперь использовать эту точку зрения , когда вы хотите , чтобы получить ресурсы:mView.getResources().***
. Это поможет мне исправить эту ошибку.Context
которая прикреплена к вашему `mView`.mView
в onDestroy?Ответы:
Я нашел очень простой ответ
isAdded()
:Для того, чтобы избежать
onPostExecute
от вызова , когдаFragment
не привязан кActivity
это отменить ,AsyncTask
когда паузы или остановкиFragment
. ТогдаisAdded()
не было бы необходимости больше. Тем не менее, желательно сохранить эту проверку на месте.источник
isDetached()
, что было добавлено на уровне API 13Проблема в том, что вы пытаетесь получить доступ к ресурсам (в данном случае к строкам) с помощью getResources (). GetString (), который попытается получить ресурсы из Activity. Посмотрите этот исходный код класса Fragment:
mHost
это объект, который содержит вашу активность.Поскольку действие не может быть присоединено, ваш вызов getResources () вызовет исключение.
Принятое решение ИМХО - это не тот путь, которым вы просто скрываете проблему. Правильный способ - просто получить ресурсы из другого места, которое всегда гарантированно существует, например, из контекста приложения:
источник
getString()
когда мой фрагмент был приостановлен. СпасибоЗдесь я столкнулся с двумя разными сценариями:
1) Когда я хочу в любом случае завершить асинхронную задачу: представьте, что мой onPostExecute сохраняет полученные данные, а затем вызывает слушателя для обновления представлений, поэтому для большей эффективности я хочу, чтобы задача все равно завершилась, чтобы у меня были данные готовы, когда пользователь придет обратно. В этом случае я обычно делаю это:
2) Когда я хочу, чтобы асинхронная задача завершилась только тогда, когда представления могут быть обновлены: в случае, который вы здесь предлагаете, задача обновляет только представления, хранилище данных не требуется, поэтому у нее нет подсказки для завершения задачи, если представления больше не показывается. Я делаю это:
Я не нашел никаких проблем с этим, хотя я также использую (возможно) более сложный способ, который включает запуск задач из действия вместо фрагментов.
Жаль, что это помогает кому-то! :)
источник
Проблема с вашим кодом заключается в том, как вы используете AsyncTask, потому что когда вы поворачиваете экран во время спящего потока:
AsyncTask все еще работает, это потому, что вы не отменили экземпляр AsyncTask должным образом в onDestroy () до перестройки фрагмента (при вращении), и когда этот же экземпляр AsyncTask (после вращения) запускается onPostExecute (), он пытается найти ресурсы с getResources () со старым экземпляром фрагмента (недопустимый экземпляр):
что эквивалентно:
Таким образом, окончательное решение заключается в управлении экземпляром AsyncTask (чтобы отменить, если он все еще работает), прежде чем фрагмент перестраивается при повороте экрана, и, если он отменен во время перехода, перезапустите AsyncTask после реконструкции с помощью логического флага:
источник
getResources().***
использованиеFragments.this.getResource().***
помоглоЭто довольно хитрое решение для этого и утечка фрагмента из активности.
Таким образом, в случае getResource или чего-либо, что зависит от доступа к контексту активности из фрагмента, всегда проверяется статус активности и статус фрагментов следующим образом.
источник
isAdded
достаточно. Я никогда не видел ситуацию, когдаgetString()
разбился, еслиisAdded == true
. Вы уверены, что активность была показана, а фрагмент был прикреплен?работает также в некоторых случаях. Просто прервите выполнение кода и убедитесь, что приложение не падает
источник
Я столкнулся с той же проблемой, я просто добавил экземпляр синглета, чтобы получить ресурс, как указано Эриком
Вы также можете использовать
Я надеюсь, это поможет.
источник
Я сталкивался с подобными проблемами, когда была видна активность настроек приложения с загруженными настройками. Если бы я изменил одно из предпочтений, а затем повернул отображаемое содержимое и снова изменил это предпочтение, он вылетел бы с сообщением о том, что фрагмент (мой класс предпочтений) не был прикреплен к действию.
При отладке это выглядело так, как метод onCreate () в PreferencesFragment вызывался дважды, когда содержимое дисплея вращалось. Это было уже достаточно странно. Затем я добавил проверку isAdded () вне блока, где он будет указывать на сбой, и это решило проблему.
Вот код слушателя, который обновляет сводку настроек, чтобы показать новую запись. Он находится в методе onCreate () моего класса Preferences, который расширяет класс PreferenceFragment:
Я надеюсь, что это поможет другим!
источник
Если вы расширяете
Application
класс и поддерживаете статический объект «глобального» контекста, как показано ниже, вы можете использовать его вместо действия для загрузки ресурса String.Если вы используете это, вы можете сойти с рук
Toast
и загрузки ресурсов, не беспокоясь о жизненных циклах.источник
В моем случае методы фрагмента были вызваны после
источник
Старый пост, но я был удивлен самым голосуемым ответом.
Правильное решение для этого должно состоять в том, чтобы отменить asynctask в onStop (или где это уместно в вашем фрагменте). Таким образом, вы не вносите утечку памяти (асинхронную задачу, сохраняющую ссылку на ваш уничтоженный фрагмент), и вы лучше контролируете то, что происходит в вашем фрагменте.
источник
cancel
может не помешатьonPostExecute
быть вызванным.