2012-06-06 19 views
323

He creado una pequeña aplicación de prueba que representa mi problema. Estoy usando ActionBarSherlock para implementar pestañas con (Sherlock) Fragments.Fragmento MiFragmento no asociado a la Actividad

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

Cuando puedo rotar la pantalla muy rápido entre el paisaje y el retrato, me sale una excepción en el código onPostExecute:

java.lang.IllegalStateException: Fragmento MyFragment {} 410f6060 no unido a la actividad

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

Pero, ¿cómo puedo solucionarlo?

+0

Debe usar view from fragment inflater. 'mView = inflater.inflate (R.layout.my_layout, container, false)' Y ahora usa esta vista cuando quieras obtener recursos: 'mView.getResources(). ***'. Me ayuda a solucionar este error. – foxis

+0

@foxis que filtra el 'Contexto' que está adjunto a su' mView'. – nhaarman

+0

Puede ser que aún no lo compruebo. Para evitar fugas, ¿qué tal si obtienes 'mView' nulo en onDestroy? – foxis

Respuesta

685

que he encontrado la respuesta muy simple: isAdded():

Volver true si el fragmento se añade actualmente a su actividad.

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

Para evitar onPostExecute de ser llamado cuando el Fragment no está unida a la Activity es cancelar la AsyncTask cuando pausar o detener la Fragment. Entonces isAdded() ya no sería necesario. Sin embargo, es aconsejable mantener este control en su lugar.

+0

En mi caso, cuando estoy lanzando otra aplicación con intención de ... entonces estoy obteniendo el mismo error ... ¿alguna sugerencia? – CoDe

+0

No funcionará en el nivel de API 10 o menos –

+1

@Lucas ¿por qué es eso? – nhaarman

22

que he enfrentado dos escenarios diferentes aquí:

1) Cuando quiero la tarea asíncrona para terminar de todos modos: imaginar mi onPostExecute no recibió almacenar datos y luego llamar a un oyente para actualizar vistas así, a ser más eficiente , Quiero que la tarea termine de todos modos, así que tengo los datos listos cuando el usuario responde. En este caso, yo 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 la tarea asíncrona sólo para terminar cuando las opiniones se pueden actualizar: el caso que estamos proponiendo aquí, la tarea sólo actualiza los puntos de vista, no hay almacenamiento de datos necesario, por lo que no tiene ninguna pista para que la tarea finalice 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(); 
} 

he encontrado ningún problema con esto, aunque también utilizo un (tal vez) de manera más compleja que incluye tareas de lanzamiento de la actividad en lugar de los fragmentos.

¡Ojalá esto ayude a alguien! :)

18

El problema con el código es la forma en la que está utilizando el AsyncTask, porque cuando se gira la pantalla durante el hilo del sueño:

Thread.sleep(2000) 

la AsyncTask todavía está trabajando, es porque no lo hiciste 'cancelar la instancia AsyncTask correctamente en onDestroy() antes de que el fragmento se reconstruya (cuando gira) y cuando esta misma instancia AsyncTask (después de rotar) se ejecuta en PostExecute(), esto intenta encontrar los recursos con getResources() con la instancia de fragmento anterior (una instancia no válida):

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

lo que equivale a:

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

Así que la solución final es gestionar la instancia AsyncTask (para cancelar si esto sigue trabajando) antes de que el fragmento reconstruye cuando se gira la pantalla, y si se cancela durante la transición, reinicie el AsyncTask después de la reconstrucción con la ayuda de un indicador booleano:

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

    } 
} 
+0

en su lugar si 'getResources(). ***' usando 'Fragments.this.getResource(). ***' me ayudó – Prabs

10

que se enfrentó al mismo problema que acaba de añadir la instancia Singletone para obtener recursos a que se refiere por Erick

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

también se puede utilizar

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

espero que esto ayudará.

0

Si amplía la clase Application y mantiene un objeto Contexto 'global' estático, de la siguiente manera, puede usarlo 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 se utiliza este, puede salirse con la carga de recursos Toast y sin preocuparse de los ciclos de vida.

+5

Estoy siendo degradado pero nadie me ha explicado por qué. Los contextos estáticos suelen ser malos, pero tenía la idea de que no se trata de una pérdida de memoria si tiene una referencia de aplicación estática. –

+0

Su respuesta está volcada debido a que esta es una solución propia de un hack no. Comprueba la solución compartida por @nhaarman –

2

Tuve 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 de nuevo, 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 se llamaba dos veces cuando se giraba el contenido de la pantalla. Eso ya era bastante extraño. Luego agregué el control 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 se 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!

14

El problema es que está intentando acceder a los recursos (en este caso, cadenas de caracteres) utilizando getResources(). GetString(), que intentará obtener los recursos de la Actividad. Ver el 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 lleva a cabo su actividad.

Como es posible que la actividad no esté conectada, la llamada a getResources() emitirá 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 sólo para obtener los recursos de otro lugar que siempre está garantizado de existir, al igual que el contexto de aplicación:

youApplicationObject.getResources().getString(...) 
+0

Utilicé esta solución porque necesitaba ejecutar 'getString()' cuando mi fragmento estaba en pausa. Gracias – Geekarist

13

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

Así que en caso de getResource o cualquier cosa que uno que está en función del contexto actividad accediendo desde Fragmento siempre es comprobar el estado de la actividad y fragmentos de estado de la siguiente

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 


     } 
    } 
+5

isAdded() es suficiente porque: final public boolean isAdded() { return mHost! = Null && mAdded; } – NguyenDat

0

En mis métodos de fragmentos de casos han sido llamados después

getActivity().onBackPressed(); 
0

Una publicación anterior, pero me sorprendió la respuesta más votada.

La solución adecuada para esto debería ser cancelar la asynctask en onStop (o donde corresponda en su fragmento). De esta forma, no introduce una pérdida de memoria (una tarea asíncrona que mantiene 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); 
} 
+1

La respuesta más votada incluye esto. Además, 'cancel' no puede impedir que se invoque' onPostExecute'. – nhaarman

+0

La cancelación de llamada garantiza onPostExecute nunca se llamará, ambas llamadas se ejecutan en el mismo subproceso, por lo tanto, se garantiza que no se invocará después de llamar cancelar – Raz

3
if (getActivity() == null) return; 

obras también en algunos casos. Simplemente rompe la ejecución del código y se asegura de que la aplicación no se bloquee.

Cuestiones relacionadas