Fragmento MyFragment no adjunto a la actividad


He creado una pequeña aplicación de prueba que representa mi problema. Estoy usando ActionBarSherlock para implementar pestañas con fragmentos (Sherlock).

Mi código: 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();
    }
}

He añadido la parte Thread.sleep para simular la descarga de datos. El código en el onPostExecute es para simular el uso del Fragment.

Cuando giro la pantalla muy rápido entre el paisaje y el retrato, obtengo una Excepción en el código onPostExecute:

Java.lang.IllegalStateException: Fragmento de MyFragment{410f6060} no anexo a la actividad

Creo que es porque se ha creado un nuevo MyFragment mientras tanto, y se adjuntó a la Actividad antes de que el AsyncTask terminara. El código en onPostExecute llama a un MyFragment no conectado.

Pero ¿cómo puedo arreglar esto?

Author: nhaarman, 2012-06-06

11 answers

He encontrado la respuesta muy simple: isAdded():

Devuelve true si el fragmento está actualmente añadido a su actividad.

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

Evitar que onPostExecute sea llamado cuando el Fragment no está unido al Activity es cancelar el AsyncTask al pausar o detener el Fragment. Entonces isAdded() ya no sería necesario. Sin embargo, es aconsejable mantener este control en su lugar.

 724
Author: nhaarman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-21 09:57:28

Me he enfrentado a dos escenarios diferentes aquí:

1) Cuando quiero que la tarea asíncrona termine de todos modos: imagine que mi onPostExecute almacena los datos recibidos y luego llama a un oyente para actualizar las vistas, por lo que, para ser más eficiente, quiero que la tarea termine de todos modos para tener los datos listos cuando el usuario regrese. En este caso suelo hacer esto:

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

2) Cuando quiero que la tarea asíncrona solo termine cuando las vistas se puedan actualizar: el caso que está proponiendo aquí, la tarea solo actualiza vistas, no se necesita almacenamiento de datos, por lo que no tiene ni idea de que la tarea termine si las vistas ya no se muestran. Hago esto:

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

No he encontrado ningún problema con esto, aunque también uso una forma (tal vez) más compleja que incluye el lanzamiento de tareas desde la actividad en lugar de los fragmentos.

¡Ojalá esto ayude a alguien! :)

 24
Author: luixal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-24 10:13:35

El problema es que está intentando acceder a recursos (en este caso, cadenas) usando getResources().getString (), que intentará obtener los recursos de la Actividad. Vea este código fuente de la clase 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 es el objeto que contiene su Actividad.

Debido a que la Actividad puede no estar adjunta, tu llamada a getResources() lanzará una Excepción.

La solución aceptada en mi humilde opinión no es el camino a seguir, ya que solo está ocultando el problema. La forma correcta es solo para obtener los recursos de otro lugar que siempre se garantiza que existen, como el contexto de la aplicación:

youApplicationObject.getResources().getString(...)
 21
Author: Tiago,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-30 21:00:55

El problema con su código es la forma en que está utilizando la AsyncTask, porque cuando gira la pantalla durante su hilo de suspensión:

Thread.sleep(2000) 

La AsyncTask sigue funcionando, es porque no cancelaste la instancia AsyncTask correctamente en onDestroy() antes de que el fragmento se reconstruya (cuando rotes) y cuando esta misma instancia AsyncTask (después de rotar) se ejecuta onPostExecute (), esto intenta encontrar los recursos con getResources () con la instancia de fragmento anterior (una instancia no válida):

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

Que es equivalente a:

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

Así que la solución final es administrar la instancia de AsyncTask (para cancelar si esto sigue funcionando) antes de que el fragmento se reconstruya cuando gire la pantalla, y si se cancela durante la transición, reinicie la AsyncTask después de la reconstrucción con la ayuda de una bandera booleana:

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

    }
}
 18
Author: Erick Reátegui Diaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-30 15:57:08

Su solución es bastante truco para esto y fuga de fragmento de la actividad.

Por lo tanto, en el caso de getResource o cualquier cosa que dependa del contexto de la actividad que acceda desde el fragmento, siempre se verifica el estado de la actividad y el estado de los fragmentos de la siguiente manera

 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


        }
    }
 14
Author: Vinayak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-02 09:44:26

Me enfrenté al mismo problema que acabo de añadir la instancia singletone para obtener el recurso como se refiere por Erick

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

También puedes usar

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

Espero que esto ayude.

 10
Author: Aristo Michael,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-27 15:03:47
if (getActivity() == null) return;

Funciona también en algunos casos. Solo rompe la ejecución de código de ella y asegúrese de que la aplicación no se bloquee

 6
Author: superUser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-17 19:55:26

Me enfrenté a problemas similares cuando la actividad de configuración de la aplicación con las preferencias cargadas era visible. Si cambiara una de las preferencias y luego hiciera que el contenido de la pantalla girara y cambiara la preferencia nuevamente, se bloquearía con un mensaje de que el fragmento (mi clase de preferencias) no estaba adjunto a una actividad.

Al depurar parecía que el método onCreate() de PreferencesFragment estaba siendo llamado dos veces cuando el contenido de display giraba. Eso fue extraño. ya es suficiente. Luego agregué la comprobación isAdded () fuera del bloque donde indicaría el bloqueo y resolvió el problema.

Aquí está el código del receptor que actualiza el resumen de preferencias para mostrar la nueva entrada. Se encuentra en el método onCreate() de mi clase Preferences que extiende la clase 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");
                    }
                }
            }
        };
        // ...
    }

¡Espero que esto ayude a otros!

 2
Author: ohgodnotanotherone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-10 21:37:15

Si extiende la clase Application y mantiene un objeto de contexto 'global' estático, de la siguiente manera, entonces puede usar eso en lugar de la actividad para cargar un recurso de cadena.

public class MyApplication extends Application {
    public static Context GLOBAL_APP_CONTEXT;

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

Si usa esto, puede salirse con la suya con Toast y la carga de recursos sin preocuparse por los ciclos de vida.

 0
Author: Anthony Chuinard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-08 22:39:41

En mi caso, los métodos de fragmento se han llamado después de

getActivity().onBackPressed();
 0
Author: CoolMind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-18 12:33:09

Un post antiguo, pero me sorprendió la respuesta más votada.

La solución adecuada para esto debería ser cancelar la asynctask en onStop (o donde sea apropiado en tu fragmento). De esta manera no introducirás una fuga de memoria (una asynctask manteniendo una referencia a tu fragmento destruido) y tendrás un mejor control de lo que está pasando en tu fragmento.

@Override
public void onStop() {
    super.onStop();
    mYourAsyncTask.cancel(true);
}
 0
Author: Raz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-20 21:13:04