example - navegar entre fragments android studio
Fragmentar la pila posterior y isRemoving() (5)
La única idea que puedo decir: el método isRemoving()
devuelve el parámetro interno mRemoving
que significa ''la eliminación está en progreso''. Por lo general, significa que hay un administrador que, por ejemplo, libera memoria en otro hilo. Es por eso que de vez en cuando recibirá los diferentes valores. Obvoiusly no es una devolución de llamada. Solo un estado simple.
Encontré valores de retorno inconsistentes de Fragment.isRemoving()
cuando la actividad acaba de agregar el fragmento a la pila de respaldo. La primera vez que el fragmento se destruye temporalmente debido al cambio de configuración, isRemoving()
devuelve verdadero. Si el fragmento se destruye temporalmente por segunda vez, isRemoving()
devuelve falso.
Mi código:
public class MainActivityFragment extends Fragment {
private static final String TAG = "MainActivityFragment";
private static final String LEVEL = "MainActivityFragment.LEVEL";
public MainActivityFragment() {
}
public static MainActivityFragment newInstance(int n) {
MainActivityFragment f = new MainActivityFragment();
f.setArguments(new Bundle());
f.getArguments().putInt(LEVEL, n);
return f;
}
private int getLevel() {
return (getArguments() == null) ? 0 : getArguments().getInt(LEVEL);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
Button button = (Button) rootView.findViewById(R.id.button);
button.setText(String.valueOf(getLevel()));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment, MainActivityFragment.newInstance(getLevel() + 1))
.addToBackStack(null)
.commit();
}
});
return rootView;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, String.valueOf(getLevel()) + ": onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, String.valueOf(getLevel()) + ": onDestroy");
Log.i(TAG, String.valueOf(getLevel()) + ": isChangingConfigurations() == " + getActivity().isChangingConfigurations());
Log.i(TAG, String.valueOf(getLevel()) + ": isRemoving() == " + isRemoving());
}
El registro (las líneas que comienzan con # son mis comentarios):
# Start Activity
I/MainActivityFragment: 0: onCreate
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
I/MainActivityFragment: 1: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true # ???????
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Correct result
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
I/MainActivityFragment: 2: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Ok, correct
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true # WHY????
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: onCreate
¿Es esto un error en Android o entiendo esto mal?
Actualización: agregué una llamada a Fragment.dump () en onDestroy y obtuve los siguientes resultados:
Antes de que el fragmento se ponga en la pila posterior:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=2 mIndex=0 mWho=android:fragment:0 mBackStackNesting=0
mAdded=true mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{336d670b in HostCallbacks{387c69e8}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@387c69e8
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
Child FragmentManager{2b6916a6 in null}}:
FragmentManager misc state:
mHost=null
mContainer=null
mCurState=0 mStateSaved=true mDestroyed=true
Después de que el fragmento se pone en la pila posterior y se destruye la primera vez:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=true mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{34638ae1 in HostCallbacks{2db8e006}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@2db8e006
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
Child FragmentManager{169d66c7 in null}}:
FragmentManager misc state:
mHost=null
mContainer=null
mCurState=0 mStateSaved=true mDestroyed=true
Destruido la segunda vez:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{23beb2bc in HostCallbacks{c0f9245}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@c0f9245
mSavedFragmentState=Bundle[{android:view_state={2131492979=android.view.AbsSavedState$1@6adf801}}]
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
Las diferencias entre el primero (no en la parte posterior de la pila todavía) y el segundo (poner en la pila de atrás) son:
- mState = 2 (
ACTIVITY_CREATED
) vs. mState = 1 (CREATED
) - mBackStackNesting = 0 frente a mBackStackNesting = 1
- mAdded = verdadero vs. mAdded = falso
- mRemoving = falso vs. mRemoving = verdadero (obviamente)
Las diferencias entre el segundo (primera vez que se destruye) y el tercero (segundo + tiempo de eliminación) son:
- mRemoving = verdadero vs. mRemoving = falso
- mSavedFragmentState = null vs mSavedFragmentState = Bundle [...]
- tiene Child FragmentManager vs. no tiene Child FragmentManager
Sin embargo, no tengo idea de cómo interpretar estos resultados.
Estoy empezando a pensar que isRemoving
no es lo que necesito (lo que realmente necesito es algo equivalente a Activity.isFinishing
pero para fragmentos. Necesito saber que "este fragmento nunca volverá a ser reutilizado", así que puedo cancelar las tareas en segundo plano. En este momento estoy usando isRemoving() && !getActivity().isChangingConfigurations()
pero no estoy seguro de que sea la solución correcta).
Se llama a isRemoving
cuando un fragmento está siendo reemplazado por otro fragmento utilizando la llamada .replace
no en los cambios de configuración. Agregué un registro:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, String.valueOf(getLevel()) + ": isAdded() == " +
isAdded());
Log.i(TAG, String.valueOf(getLevel()) + ": onCreate");
}
En resumen:
# Start Activity
**PORTRAIT**
I/MainActivityFragment: 0: isAdded() == true // **0.portrait Added**
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
**REPLACE FRAGMENT**
I/MainActivityFragment: 1: isAdded() == true // **1.portrait Added**
# Rotate the device
**LANDSCAPE**
I/MainActivityFragment: 0: isRemoving() == true // replaced by 1 **0.portrait Removed**
I/MainActivityFragment: 1: isRemoving() == false // Not replaced in portrait
I/MainActivityFragment: 1: isAdded() == true // **1.landscape Added**
# Rotate the device a second time
**PORTRAIT**
I/MainActivityFragment: 1: isRemoving() == false // Not being replaced in landscape
I/MainActivityFragment: 1: isAdded() == true // Display portrait again **1.portrait add**
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
**REPLACE FRAGMENT**
I/MainActivityFragment: 2: isAdded() == true **2.portrait Added**
# Rotate the device
**LANDSCAPE**
I/MainActivityFragment: 1: isRemoving() == true
// Is being replaced from previous replace fragment
I/MainActivityFragment: 2: isAdded() == true // Adding to landscape **1.landscape Added**
Logcat de longitud completa con anotaciones añadidas
# Start Activity
**PORTRAIT**
I/MainActivityFragment: 0: isAdded() == true // **0.portrait Added**
I/MainActivityFragment: 0: onCreate
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
**REPLACE FRAGMENT**
I/MainActivityFragment: 1: isAdded() == true // **1.portrait Added**
I/MainActivityFragment: 1: onCreate
# Rotate the device
**LANDSCAPE**
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true // replaced by 1 **0.portrait Removed**
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false // Not replaced in portrait
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false // **nothing to do**
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true // **1.landscape Added**
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
**PORTRAIT**
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false // Not being replaced in landscape
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true // Display **1.portrait add**
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
**REPLACE FRAGMENT**
I/MainActivityFragment: 2: isAdded() == true **2.portrait Added**
I/MainActivityFragment: 2: onCreate
# Rotate the device
**LANDSCAPE**
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragme/nt: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true // Is being replaced
I/MainActivityFragment: 1: isDetached() == false
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 2: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == false
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: isAdded() == true // Adding to landscape **1.landscape Added**
I/MainActivityFragment: 2: onCreate
Como puede ver cuando el fragmento se reemplaza una vez, el retiro es solo requerido una vez, el resto del logcat muestra rotaciones solo después de eso.
Parece que el cambio de configuración gestiona el cambio de fragmento de forma diferente al uso de reemplazar.
//Start
I/MainActivityFragment: 0: isAdded() == true
I/MainActivityFragment: 0: onCreate
//Replace fragment
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
No estoy del todo claro en tu pregunta real sobre qué estás tratando de dejar de usar cuando un fragmento se ha ido. Siempre que el fragmento sea reemplazado o en cambio de configuración, puede deshacerse de ese fragmento y todo lo que esté asociado a él, si ese es su deseo. Si ve estos resultados, verá que los otros fragmentos aún están en segundo plano, por lo que lo que decida hacer con ellos es su elección.
isRemoving()
devuelve mRemoving
que, del comentario del código significa:
Si se establece, este fragmento se eliminará de su actividad.
Esto se establece principalmente en FragmentManager.removeFragment()
También tenga en cuenta que:
- Este comportamiento puede cambiar a través de las implementaciones, particularmente framework vs AppCompat
- Las transacciones de fragmentos son asincrónicas, es posible que el valor devuelto por
isRemoving()
cambie al reproducir su experimento
No sé qué quieres hacer con esta información. Si quieres saber si el fragmento está activo, puedes usar:
isAdded() && !isRemoving() && !isDetached()
Editar : Ahora pregunta cómo saber que una instancia de un fragmento debe detener el trabajo asincrónico (porque el fragmento se está eliminando). Haría esto con:
getActivity().isFinishing() || isRemoving() || isDetached()
Original no es la respuesta correcta
No estoy seguro de si es o no un error o diseño, pero un fragmento solo se configura para su eliminación en el método FragmentManager.removeFragment
de la biblioteca de v4 de soporte v23.1.1.
Esto podría ser muy diferente dependiendo de si está utilizando la biblioteca de soporte y qué versión, pero para el código que tiene en el repositorio de GitHub esta es la razón.
Este método solo se llama cuando se elimina un fragmento que se ha colocado en la pila posterior.
Aquí está el método completo de referencia:
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
if (mAdded != null) {
mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
fragment.mRemoving = true;
moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
transition, transitionStyle, false);
}
}
Posible respuesta a la pregunta "Cómo saber que este fragmento nunca volverá a usarse"
Para responder a su pregunta sobre cómo saber puede cancelar sus tareas en segundo plano en un fragmento, generalmente esos fragmentos usan setRetainInstance(true)
De esta forma, cuando se cambie la orientación del dispositivo, se reutilizará el mismo Fragmento y se podrán conservar las operaciones de fondo en curso.
Cuando retenga la instancia es verdadera, el método del fragmento onDestroy()
no se onDestroy()
durante los cambios de orientación, de modo que puede poner su lógica de cancelación allí para saber si el fragmento se va para siempre.
Una mejor respuesta a cómo funciona isRemoving basado en la revisión del código fuente
Viendo que esta respuesta ha sido aceptada, creo que debería arreglar un par de imprecisiones de mi respuesta original. Dije: "Este método solo se llama cuando se quita un fragmento que se ha colocado en la pila posterior", lo que no es del todo correcto. La sustitución de un fragmento también llama al método y establece correctamente isRemoving en true como un ejemplo.
Ahora, para responder a su pregunta sobre por qué isRemoving parece inconsistente en todas las rotaciones, analice su registro. Mis comentarios adicionales comienzan con ##
# Start Activity
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
## FragmentManager.removeFragment is called on fragment 0 setting mRemoving to true
I/MainActivityFragment: 1: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true ## To emphasize, this is true because as soon as you replaced fragment 0 it was set to true in the FragmentManager.removeFragment method.
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false ## fragment 1 is never actually removed so mRemoving is false.
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
## after rotating the device the first time your same fragments are not reused but new instances are created. This resets all the internal state of the fragments so mRemoving is false for all fragments.
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Correct result
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
## fragment 1 now has mRemoving set to true in FragmentManager.removeFragment
I/MainActivityFragment: 2: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false ## still false from prior rotation
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true ## true because mRemoving was set to true in FragmentManager.removeFragment.
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: onCreate
Si rotó el dispositivo nuevamente, todos los fragmentos devolverían falso de isRemoving ().
Curiosamente, incluso si se utilizaran las mismas instancias de fragmentos, probablemente obtendría el mismo resultado. Hay un método en la clase Fragment
llamado initState
que tiene el siguiente comentario:
Llamado por el administrador de fragmentos una vez que se ha eliminado este fragmento, para que no tengamos ningún estado restante si la aplicación decide reutilizar la instancia. Esto solo aclara que el marco administra internamente, no las cosas que establece la aplicación.
Este método se llamó una vez para cada fragmento durante la rotación y una de las cosas que hace es restablecer mRemoving a false.
Flujo posible:
La aplicación comienza. Al crear el Fragmento 0, mRemoving
se inicializa a falso. Al reemplazar el Fragmento 0 con el Fragmento 1, mRemoving
se establece en verdadero para el Fragmento 0 mediante removeFragment()
.
Cambios de configuración Durante el cambio de configuración, ese campo ( mRemoving
en el Fragmento 0) se lee como verdadero, ya que fue establecido por removeFragment()
cuando reemplazamos por primera vez el Fragmento 0.
Pero cuando se produce un cambio de configuración, es posible que FragmentManager maneje el flujo de manera diferente a nuestra transacción manual de replace()
. Y, como sabemos al agregar Fragments mRemoving
se inicializa a falso, y considerando removeFragment()
no se llama, mRemoving
es falso la segunda vez.