Preguntas para compartir la vida



A cada desarrollador se le ocurrieron preguntas sobre el ciclo de vida de la Actividad: qué es un servicio de enlace, cómo guardar el estado de la interfaz cuando se gira la pantalla y cómo Fragmento difiere de la Actividad.
En FunCorp hemos acumulado una lista de preguntas sobre temas similares, pero con ciertos matices. Quiero compartir algunas de ellas contigo.


1. Todos saben que si abre la segunda actividad encima de la primera y gira la pantalla, la cadena de llamadas del ciclo de vida se verá así:


Actividad de apertura

FirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop


Girar

Segunda actividad: en pausa
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume


Volver atrás

Segunda actividad: en pausa
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
SecondActivity: onStop


¿Y qué sucederá si la segunda actividad es transparente?


Solución


En el caso de la actividad superior transparente, en términos de lógica, todo es ligeramente diferente. Precisamente porque es transparente, después de la rotación, es necesario restaurar el contenido y la actividad que está directamente debajo de él. Por lo tanto, el orden de las llamadas será ligeramente diferente:


Actividad de descubrimiento

FirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume


Girar

Segunda actividad: en pausa
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
FirstActivity: onResume
FirstActivity: onPause


2. Ninguna aplicación puede hacerlo sin agregar dinámicamente una vista, pero a veces tiene que mover la misma vista entre diferentes pantallas. ¿Se puede agregar el mismo objeto simultáneamente a dos actividades diferentes? ¿Qué sucede si lo creo con el contexto de la aplicación y quiero agregarlo a diferentes actividades al mismo tiempo?


¿Por qué se necesita esto?
Hay bibliotecas "no muy agradables" que contienen lógica empresarial importante dentro de las vistas personalizadas, y volver a crear estas vistas dentro de cada nueva actividad es una mala decisión, porque Quiero tener un conjunto de datos.



Solución


Nada impide crear una vista con el contexto de la Aplicación. Simplemente aplicará estilos predeterminados que no están relacionados con ninguna actividad. También puede mover esta vista entre diferentes actividades sin problemas, pero debe asegurarse de que se agregue a un solo padre


private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { ... if (child.getParent() != null) { throw new IllegalStateException("The specified child already has a parent. " + "You must call removeView() on the child's parent first."); } ... } 

Puede, por ejemplo, suscribirse a ActivityLifecycleCallbacks, eliminar onStop (removeView) de la actividad actual, agregar onStart a la siguiente apertura (addView).


3. El fragmento se puede agregar a través de agregar y reemplazar. ¿Y cuál es la diferencia entre estas dos opciones en términos del orden de los métodos del ciclo de vida? ¿Cuáles son las ventajas de cada uno de ellos?


Solución


Incluso si agrega un fragmento mediante el reemplazo, esto no significa que se haya reemplazado por completo. Esto significa que en este punto en el contenedor su vista será reemplazada, por lo tanto, onDestroyView será llamado para el fragmento actual, y onCreateView será llamado nuevamente cuando regrese.



Esto cambia mucho el juego. Debe separar todos los controladores y clases asociados con la interfaz de usuario en onDestroyView. Es necesario separar claramente la recepción de los datos necesarios para el fragmento y el llenado de la vista (listas, etc.), ya que el llenado y la ruptura de la vista ocurrirán con mucha más frecuencia que la recepción de datos (leer algunos datos de la base de datos).


También hay matices con la recuperación de estado: por ejemplo, onSaveInstanceState a veces viene después de onDestroyView. Además, debe tenerse en cuenta que si nulo entró en OnViewStateRestored, esto significa que no necesita restaurar nada, pero no restablecer el estado predeterminado.


Si hablamos de la conveniencia entre agregar y reemplazar, reemplazar es más económico en la memoria si tiene una navegación profunda (tenemos una profundidad de navegación del usuario de uno de los KPI del producto). También es mucho más conveniente reemplazar la barra de herramientas con reemplazar, ya que en onCreateView puede reinfundirla. De las ventajas de agregar: menos problemas con el ciclo de vida, cuando regresa, no vuelvo a crear la vista y no necesito volver a llenar nada.


4. A veces todavía tiene que trabajar directamente con los servicios e incluso con los servicios de enlace. La actividad interactúa con uno de estos servicios (solo una actividad). Se conecta al servicio y le transfiere datos. Cuando enciende la pantalla, nuestra actividad se destruye y estamos obligados a recuperar este servicio. Pero si no hay conexión, entonces el servicio se destruye, y después de activar la vinculación será a un servicio completamente diferente. ¿Cómo asegurarse de que cuando active el servicio permanezca activo?


Solución


Si conoce una solución hermosa, escriba los comentarios. Solo se me ocurre algo similar:


  @Override protected void onDestroy() { super.onDestroy(); ThreadsUtils.postOnUiThread(new Runnable() { @Override public void run() { unbindService(mConnection); } }); } 

5. Recientemente, rehicimos la navegación dentro de nuestra aplicación en Actividad única (usando una de las bibliotecas disponibles). Anteriormente, cada pantalla de aplicación era una actividad separada, ahora la navegación funciona en fragmentos. El problema de volver a la actividad en el medio de la pila se resolvió mediante indicadores de intención. ¿Cómo puedo volver al fragmento en el medio de la pila?


Solución


Sí, FragmentManager no proporciona soluciones listas para usar. Cicerone hace algo similar en sí mismo:


  protected void backTo(BackTo command) { String key = command.getScreenKey(); if (key == null) { backToRoot(); } else { int index = localStackCopy.indexOf(key); int size = localStackCopy.size(); if (index != -1) { for (int i = 1; i < size - index; i++) { localStackCopy.pop(); } fragmentManager.popBackStack(key, 0); } else { backToUnexisting(command.getScreenKey()); } } } 

6. Además, recientemente nos deshicimos de un componente tan ineficiente y complejo como ViewPager, porque la lógica de interactuar con él es muy complicada y el comportamiento de los fragmentos es impredecible en ciertos casos. En algunos fragmentos, utilizamos fragmentos internos. ¿Qué sucede cuando se usan fragmentos dentro de los elementos de RecycleView?


Solución


En general, no habrá nada malo. El fragmento se agregará y mostrará sin ningún problema. Lo único que enfrentamos es inconsistencias con su ciclo de vida. La implementación en ViewPager gestiona el ciclo de vida de los fragmentos a través de setUserVisibleHint, y RecycleView hace todo frente sin pensar en la visibilidad real y la accesibilidad de los fragmentos.


7. Todo por la misma razón por la que cambiamos de ViewPager, nos encontramos con el problema de restaurar el estado. En el caso de los fragmentos, esto fue implementado por el marco: en los lugares correctos, simplemente redefinimos onSaveInstanceState y guardamos todos los datos necesarios en el Bundle. Cuando recrea el ViewPager, el FragmentManager restaura todos los fragmentos y devuelve su estado. ¿Qué hacer con RecycleView y su ViewHolder?


Solución


"Tienes que escribir todo en la base de datos y leerlo cada vez", dices. O la lógica para guardar el estado debería estar fuera, y la lista es solo una pantalla. En un mundo ideal lo es. Pero en nuestro caso, cada elemento de la lista es una pantalla compleja con su propia lógica. Por lo tanto, tuve que inventar mi bicicleta al estilo de "hagamos la misma lógica que en ViewPager y fragment":


Adaptador
 public class RecycleViewGalleryAdapter extends RecyclerView.Adapter<GalleryItemViewHolder> implements GalleryAdapter { private static final String RV_STATE_KEY = "RV_STATE"; @Nullable private Bundle mSavedState; @Override public void onBindViewHolder(GalleryItemViewHolder holder, int position) { if (holder.isAttached()) { holder.detach(); } holder.attach(createArgs(position, getItemViewType(position))); restoreItemState(holder); } @Override public void saveState(Bundle bundle) { Bundle adapterState = new Bundle(); saveItemsState(adapterState); bundle.putBundle(RV_STATE_KEY, adapterState); } @Override public void restoreState(@Nullable Bundle bundle) { if (bundle == null) { return; } mSavedState = bundle.getBundle(RV_STATE_KEY); } private void restoreItemState(GalleryItemViewHolder holder) { if (mSavedState == null) { holder.restoreState(null); return; } String stateKey = String.valueOf(holder.getGalleryItemId()); Bundle state = mSavedState.getBundle(stateKey); if (state == null) { holder.restoreState(null); mSavedState = null; return; } holder.restoreState(state); mSavedState.remove(stateKey); } private void saveItemsState(Bundle outState) { GalleryItemHolder holder = getCurrentGalleryViewItem(); saveItemState(outState, (GalleryItemViewHolder) holder); } private void saveItemState(Bundle bundle, GalleryItemViewHolder holder) { Bundle itemState = new Bundle(); holder.saveState(itemState); bundle.putBundle(String.valueOf(holder.getGalleryItemId()), itemState); } } 

En Fragment.onSaveInstanceState leemos el estado de los titulares que necesitamos y los colocamos en el paquete. Al volver a crear los titulares, obtenemos el paquete guardado y en onBindViewHolder pasamos los estados encontrados dentro de los titulares:


8. ¿Con qué nos amenaza esto?


  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); ViewGroup root = findViewById(R.id.default_id); ViewGroup view1 = new LinearLayout(this); view1.setId(R.id.default_id); root.addView(view1); ViewGroup view2 = new FrameLayout(this); view2.setId(R.id.default_id); view1.addView(view2); ViewGroup view3 = new RelativeLayout(this); view3.setId(R.id.default_id); view2.addView(view3); } 

Solución


De hecho, no hay nada de malo en eso. En el mismo RecycleView, se almacenan listas de elementos con la misma identificación. Sin embargo, todavía hay un pequeño matiz:


  @Override protected <T extends View> T findViewTraversal(@IdRes int id) { if (id == mID) { return (T) this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return (T) v; } } } return null; } 

Debe tener cuidado si tenemos elementos con la misma identificación en la jerarquía, porque siempre es el primer elemento encontrado que siempre se devuelve, y en diferentes niveles de la llamada findViewById puede haber diferentes objetos.


9. Usted cae de TooLargeTransaction cuando gira la pantalla (sí, aquí nuestro ViewPager todavía tiene la culpa indirectamente). ¿Cómo encontrar al culpable?


Solución


Es bastante simple: cuelgue ActivityLifecycleCallbacks en la aplicación, capture todo enActivitySaveInstanceState y analice todo lo que se encuentra dentro del paquete. Allí puede obtener el estado de todas las vistas y todos los fragmentos dentro de esta actividad.


A continuación se muestra un ejemplo de cómo obtenemos el estado de los fragmentos del paquete:


 /** * Tries to find saved [FragmentState] in bundle using 'android:support:fragments' key. */ fun Bundle.getFragmentsStateList(): List<FragmentBundle>? { try { val fragmentManagerState: FragmentManagerState? = getParcelable("android:support:fragments") val active = fragmentManagerState?.mActive ?: return emptyList() return active.filter { it.mSavedFragmentState != null }.map { fragmentState -> FragmentBundle(fragmentState.mClassName, fragmentState.mSavedFragmentState) } } catch (throwable: Throwable) { Assert.fail(throwable) return null } } fun init() { application.registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallback() { override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { super.onActivitySaveInstanceState(activity, outState) outState?.let { ThreadsUtils.runOnMainThread { trackActivitySaveState(activity, outState) } } } }) } @MainThread private fun trackActivitySaveState(activity: Activity, outState: Bundle) { val sizeInBytes = outState.getSizeInBytes() val fragmentsInfos = outState.getFragmentsStateList() ?.map { mapFragmentsSaveInstanceSaveInfo(it) } ... } 

A continuación, simplemente calculamos el tamaño del paquete y lo registramos:


  fun Bundle.getSizeInBytes(): Int { val parcel = Parcel.obtain() return try { parcel.writeValue(this) parcel.dataSize() } finally { parcel.recycle() } } 

10. Supongamos que tenemos una actividad y un conjunto de dependencias en ella. Bajo ciertas condiciones, necesitamos recrear un conjunto de estas dependencias (por ejemplo, haciendo clic en cierto experimento con otra IU). ¿Cómo implementamos esto?


Solución


Por supuesto, puede jugar con banderas y hacer que sea una especie de reinicio "muleta" de la actividad a través del lanzamiento de la intención. Pero, de hecho, todo es muy simple: la actividad tiene un método de recreación.


Lo más probable es que la mayor parte de este conocimiento no te sea útil, ya que no vienes a cada uno de ellos desde una buena vida. Sin embargo, algunos de ellos demuestran bien cómo una persona sabe razonar y proponer soluciones. Usamos preguntas similares en las entrevistas. Si tiene tareas interesantes que se le pidió que resolviera en las entrevistas, o las estableció usted mismo, escríbalas en los comentarios; ¡será interesante discutirlas!

Source: https://habr.com/ru/post/es426215/


All Articles