Фрагмент 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} нет прилагается к деятельности

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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();
}

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

Пожелайте, чтобы это помогло кому-то!:)

Ответ 3

Проблема с вашим кодом заключается в том, как вы используете 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;
        }

    }
}

Ответ 4

Проблема заключается в том, что вы пытаетесь получить доступ к ресурсам (в данном случае, к строкам) с помощью метода 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(...)

Ответ 5

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

Таким образом, в случае 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


        }
    }

Ответ 6

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

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

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

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

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

Ответ 7

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

При отладке это выглядело так, как метод 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");
                    }
                }
            }
        };
        // ...
    }

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

Ответ 8

if (getActivity() == null) return;

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

Ответ 9

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

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

    @Override
    public void onCreate() {
        super.onCreate();
        GLOBAL_APP_CONTEXT = this;
    }
}

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

Ответ 10

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

getActivity().onBackPressed();

Ответ 11

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

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

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