android - Fragmento MiFragmento no adjunto a la Actividad




android-fragments actionbarsherlock (10)

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 onPostExecute es simular el uso del Fragment .

Cuando roto la pantalla muy rápido entre horizontal y vertical, obtengo una excepción en el código onPostExecute :

java.lang.IllegalStateException: Fragmento MyFragment {410f6060} no adjunto a la Actividad

Creo que se debe a que se ha creado un nuevo MyFragment mientras tanto, y se adjuntó a la Actividad antes de que finalizara la AsyncTask . El código en onPostExecute llama a un MyFragment sin MyFragment .

Pero, ¿cómo puedo solucionar esto?


Answers

El problema con tu código es la forma en que estás usando la AsyncTask, porque cuando giras la pantalla durante el hilo de suspensión:

Thread.sleep(2000) 

AsyncTask sigue funcionando, es porque no canceló la instancia de AsyncTask correctamente en onDestroy () antes de que el fragmento se reconstruya (cuando se gire) y cuando esta misma instancia de AsyncTask (después de rotate) se ejecuta enPostExecute (), 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 todavía funciona) antes de que el fragmento se vuelva a generar cuando gire la pantalla, y si se cancela durante la transición, reinicie 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;
        }

    }
}

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

La solución adecuada para esto debería ser cancelar la asincronización en onStop (o donde sea apropiado en su fragmento). De esta manera, no introduce una pérdida de memoria (una asincreta que guarda una referencia a su fragmento destruido) y tiene un mejor control de lo que está sucediendo en su fragmento.

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

He enfrentado dos escenarios diferentes aquí:

1) Cuando quiero que la tarea asincrónica termine de todos modos: imagina que onPostExecute almacena los datos recibidos y luego llama a un oyente para que actualice las vistas, para que sea más eficiente, quiero que la tarea termine de todos modos, así que tengo la información lista cuando el usuario lo indique. atrás. 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 pueden actualizarse: en el caso que está proponiendo aquí, la tarea solo actualiza las vistas, no se necesita almacenamiento de datos, por lo que no tiene la menor idea de que la tarea finalice si las vistas son Ya no se muestra. 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 (quizás) más compleja que incluye el inicio de tareas desde la actividad en lugar de los fragmentos.

¡Ojalá esto ayude a alguien! :)


if (getActivity() == null) return;

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


Si extiende la clase de Application y mantiene un objeto de contexto 'global' estático, de la siguiente manera, 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 Toast y la carga de recursos sin preocuparse por los ciclos de vida.


En mi caso los métodos de fragmentación han sido llamados después.

getActivity().onBackPressed();

El problema es que está intentando acceder a los recursos (en este caso, cadenas) utilizando getResources (). GetString (), que intentará obtener los recursos de la Actividad. Vea este código fuente de la clase Fragmento:

 /**
  * 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 tu actividad.

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

La solución aceptada IMHO no es el camino a seguir, ya que simplemente está ocultando el problema. La forma correcta es obtener los recursos de otro lugar que siempre se garantiza que exista, como el contexto de la aplicación:

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

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

Cuando se hacía la depuración, se parecía al método onCreate () del PreferencesFragment cuando se giraba el contenido de la pantalla. Eso ya era bastante extraño. Luego agregué la verificación isAdded () fuera del bloque donde indicaría el bloqueo y resolvió el problema.

Aquí está el código del oyente que actualiza el resumen de preferencias para mostrar la nueva entrada. Se encuentra en el método onCreate () de mi clase de Preferencias 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!


Son una solución bastante difícil para esto y la pérdida de fragmentos de la actividad.

Por lo tanto, en el caso de getResource o cualquier otro que dependa del contexto de actividad al que se acceda desde Fragment, 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


        }
    }

-fpic hacer que el código en la posición de su biblioteca sea independiente ... agregue -fpic o -fPIC a su LOCALC_FLAGS en su Android.mk y también debe asegurarse de que no está enlazando con ninguna biblioteca estática o compartida que contenga reubicaciones de texto sí mismos. Si lo hacen y puede volver a compilarlos, use uno de los indicadores mencionados anteriormente.







android android-fragments actionbarsherlock