La forma de "apilar" de Ming Xiu: superar la trampa de la pila de inicio de Android

Autor: equipo front-end de Internet vivo - Zhao Kaiping

Este artículo parte de un problema que se encontró en un caso de negocios y usa el indicador FLAG_ACTIVITY_NEW_TASK como un punto de entrada para explorar una tarea importante antes de que la actividad comience la verificación de la pila.

El artículo enumera una serie de condiciones anormales que se pueden encontrar en el negocio, describe en detalle el "pozo" que se puede encontrar al usar FLAG_ACTIVITY_NEW_TASK y explora su causa raíz desde el código fuente. Solo mediante el uso razonable de flag y launchMode podemos evitar una serie de problemas de inicio inesperados debido a la particularidad del mecanismo de pila.

1. Problema y antecedentes

La interconexión y el salto mutuo entre aplicaciones es un medio importante para lograr la integridad del sistema y la consistencia de la experiencia, y también es el método más simple.

Cuando usamos el método más común para iniciar la actividad, encontraremos fallas. En los negocios reales, encontramos una excepción de este tipo: cuando un usuario hace clic en un botón, quiere "simplemente" saltar a otra aplicación, pero no sucede nada.

Con una gran experiencia, ¿tiene varias conjeturas en mente? ¿Se debe a que la actividad de destino o incluso la aplicación de destino no existe? ¿La actividad de destino no está abierta al mundo exterior? Hay una restricción de permisos o la acción/uri del salto está mal...

La verdadera razón está oculta por capas de funciones como la bandera, el modo de lanzamiento y la intención, que pueden exceder su pensamiento en este momento.

Este artículo comenzará desde el código fuente, explorará la causa y el efecto, ampliará y hablará sobre las "tribulaciones" que deben pasar antes de que startActivity() esté realmente listo para iniciar una Actividad, y cómo resolver la excepción de inicio causada por el problema de pila de acuerdo con la evidencia.

1.1 Problemas encontrados en los negocios

El escenario en el negocio es así, hay tres aplicaciones A, B y C.

(1) Saltar de la aplicación A-Actividad1 a la aplicación B-Actividad2;

(2) la aplicación B-Activity2 continúa saltando a la aplicación C-Activity3;

(3) Un botón en C saltará a B-Activity2 nuevamente, pero no sucede nada después de hacer clic. Si no pasa por el salto anterior de A a B, es posible que C salte directamente a B.

1.2 Código de pregunta

Las configuraciones de Androidmanifest de las tres actividades son las siguientes, todas las cuales pueden iniciarse mediante sus respectivas acciones, y el modo de inicio es el modo estándar.

<!--应用A--> 
      <activity
            android:name=".Activity1"
            android:exported="true">
            <intent-filter>
                <action android:name="com.zkp.task.ACTION_TO_A_PAGE1" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 
<!--应用B-->
        <activity
            android:name=".Activity2"
            android:exported="true">
            <intent-filter>
                <action android:name="com.zkp.task.ACTION_TO_B_PAGE2" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 
<!--应用C-->
        <activity
            android:name=".Activity3"
            android:exported="true">
            <intent-filter>
                <action android:name="com.zkp.task.ACTION_TO_C_PAGE3" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

Códigos de A-1 a B-2, especifique la bandera como FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

Códigos de B-2 a C-3, sin bandera especificada

private void jumpTo_C_Activity3_ByAction_NoTask() {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_C_PAGE3");
    startActivity(intent);
}

Los códigos de C-3 a B-2 son exactamente los mismos que los de A-1 a B-2, y el indicador especificado es FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

1.3 Análisis preliminar del código

Mirando el código del problema cuidadosamente, es muy simple en implementación y tiene dos características:

(1) Si salta directamente de B-2 a C-3, no hay problema, pero después de que A-1 haya saltado B-2, C-3 falla.

(2) Cuando A-1 y C-3 saltan a B-2, la bandera se establece en FLAG_ACTIVITY_NEW_TASK.

Según la experiencia, especulamos que está relacionado con la pila e intentamos imprimir el estado de la pila antes del salto, como se muestra en la figura a continuación.

Dado que FLAG_ACTIVITY_NEW_TASK se establece cuando A-1 salta a B-2 y no se establece cuando B-2 salta a C-3, 1 está en una pila independiente y 2 y 3 están en otra pila. Como se muestra en la siguiente figura.

C-3 generalmente tiene tres expectativas posibles para saltar a B-2, como se muestra en la figura a continuación: Imagina 1, crea una nueva tarea y comienza una B-2 en la nueva tarea; Imagina 2, reutiliza la B-2 existente ; Envision 3 , Cree una nueva instancia B-2 en la Tarea existente.

Pero, de hecho, ninguna de las tres expectativas se cumple, y cualquier ciclo de vida de todas las actividades permanece sin cambios, y la interfaz siempre permanece en C-3.

Eche un vistazo a los comentarios oficiales y los comentarios de código de FLAG_ACTIVITY_NEW_TASK, como se muestra a continuación:

Concéntrate en este párrafo:

Al usar este indicador, si ya se está ejecutando una tarea para la actividad que está iniciando ahora, no se iniciará una nueva actividad; en su lugar, la tarea actual simplemente aparecerá al frente de la pantalla con el estado en el que se encontraba por última vez.

Al usar este indicador, si la actividad que está iniciando ya se está ejecutando en una tarea, entonces no se iniciará una nueva actividad; en su lugar, la tarea actual simplemente se mostrará frente a la interfaz, mostrando su último estado.

——Obviamente, los documentos oficiales y los comentarios del código son consistentes con nuestro fenómeno anormal.Si la actividad objetivo 2 ya existe en la tarea, no se iniciará; la tarea se mostrará directamente al frente y mostrará el estado final. Dado que la Actividad3 de destino es la Actividad3 de origen, no hay cambios en la página.

Parece que el oficial sigue siendo muy confiable, pero ¿puede el efecto real ser siempre consistente con la descripción oficial? Veamos algunos escenarios.

2. Ampliación y verificación de escenarios

2.1 Expansión de escena

En el proceso de ajuste y reproducción según la descripción oficial, el autor encontró varias escenas interesantes.

PD: En el caso de negocios anterior, B-2 y C-3 están en diferentes aplicaciones y en la misma tarea, pero el hecho de que sean realmente la misma aplicación tiene poco impacto en el resultado. Para evitar la confusión de lectura causada por diferentes aplicaciones y diferentes tareas, todos realizamos saltos en la misma pila en esta aplicación, por lo que la escena en el negocio es equivalente a la siguiente [Escenario 0]

[Escenario 0] Cambiar el salto entre aplicaciones de B-2 a C-3 en el negocio al salto dentro de la aplicación de B-2 a B-3

// B-2跳转B-3
public static void jumpTo_B_3_ByAction_Null(Context context) {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3");
    context.startActivity(intent);
}

Como se muestra en la figura a continuación, A-1 establece NEW_TASK para saltar a B-2, luego salta a B-3 y finalmente establece NEW_TASK para saltar a B-2. Aunque el salto C-3 se cambió por el salto B-3, era consistente con el desempeño de la pregunta anterior, no hubo respuesta y se mantuvo en B-3.

Algunos lectores señalarán este problema: si usa NEW_TASK para saltar a la misma aplicación sin especificar el atributo taskAffinity del objetivo, en realidad es imposible comenzar en la nueva tarea. Ignore este problema. Se puede considerar que la operación del autor ha agregado taskAffinity, lo que no tiene ningún efecto en el resultado final.

[Escenario 1] Si la tarea de destino y la tarea de origen no son las mismas, ¿se reutilizará la tarea existente y se mostrará el último estado como se indica en el documento oficial? Cambiamos a B-3 para comenzar una nueva Actividad C-4 de una nueva Tarea, y luego regresamos a B-2 a C-4

// B-3跳转C-4
public static void jumpTo_C_4_ByAction_New(Context context) {
    Intent intent = new Intent("com.zkp.task.ACTION_TO_C_PAGE4");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}
// C-4跳转B-2
public static void jumpTo_B_2_ByAction_New(Context context) {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

Como se muestra en la figura a continuación, A-1 configura NEW_TASK para saltar a B-2, luego salta a B-3, luego configura NEW_TASK para saltar a C-4 y finalmente configura NEW_TASK para saltar a B-2.

El resultado esperado es: en lugar de saltar a B-2, salta a B-3 en el nivel superior de la Tarea donde se encuentra.

El resultado real: como era de esperar, se dio un salto a B-3.

[Escena 2] Modificar ligeramente la escena 1: cuando C-4 a B-2, no saltamos a través de la acción, sino que saltamos a través de setClassName en su lugar .

// C-4跳转B-2
public static void jumpTo_B_2_ByPath_New(Context context) {
    Intent intent = new Intent();
    intent.setClassName("com.zkp.b", "com.zkp.b.Activity2"); // 直接设置classname,不通过action
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

Como se muestra en la figura a continuación, A-1 configura NEW_TASK para saltar a B-2, luego salta a B-3, luego configura NEW_TASK para saltar a C-4 y finalmente configura NEW_TASK para saltar a B-2.

El resultado esperado es: consistente con la escena 0, saltará al nivel superior B-3 existente de la Tarea donde se encuentra B-2.

El resultado real es: en la Task2 existente, se genera una nueva instancia B-2.

Simplemente cambiando la forma de volver a saltar B-2, ¡el efecto es completamente diferente! ¡Esto es inconsistente con el comportamiento de la bandera y el valor del modo de lanzamiento "singleTask" mencionado en el documento oficial!

[Escenario 3] Vuelva a modificar la escena 1: esta vez C-4 no salta a B-2 en la parte inferior de la pila, sino que salta a B-3 y todavía usa acción.

// C-4跳转B-3
public static void jumpTo_B_3_ByAction_New(Context context) {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

Como se muestra en la figura a continuación, A-1 configura NEW_TASK para saltar a B-2, luego salta a B-3, luego configura NEW_TASK para saltar a C-4 y finalmente configura NEW_TASK para saltar a B-3.

El resultado esperado es: consistente con la escena 0, saltará a B-3 en el nivel superior de la Tarea donde se encuentra B-2.

El resultado real es: en la Task2 existente, se genera una nueva instancia de B-3.

¿No es bueno decir que cuando la Actividad ya existe, muestre el estado más reciente de la Tarea en la que se encuentra? Obviamente, ya existe B-3 en Task2, pero no se muestra directamente, sino que se genera una nueva instancia de B-3.

[Escenario 4] Dado que la actividad no se reutiliza, ¿se reutilizará la tarea? Modifique ligeramente la escena 3 y asigne directamente una afinidad separada a B-3.

<activity
    android:name=".Activity3"
    android:exported="true"
    android:taskAffinity="b3.task"><!--指定了亲和性标识-->
    <intent-filter>
        <action android:name="com.zkp.task.ACTION_TO_B_PAGE3" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Como se muestra en la figura a continuación, A-1 configura NEW_TASK para saltar a B-2, luego salta a B-3, luego configura NEW_TASK para saltar a C-4 y finalmente configura NEW_TASK para saltar a B-3.

——Esta vez, incluso la Tarea ya no se reutilizará... Se crea una instancia de Activity3 en una nueva pila.

Mirando hacia atrás en las notas oficiales, parecerá muy inexacto e incluso causará errores graves en la percepción de esta parte por parte del desarrollador. Cambiar ligeramente un atributo no relacionado en el proceso (como el objetivo de salto, el método de salto...) puede marcar una gran diferencia.

Al mirar los comentarios relacionados con la bandera, debemos establecer una conciencia: el efecto real de los saltos de tareas y actividades es el resultado de los efectos combinados de atributos como el modo de lanzamiento, la afinidad de tareas, el modo de salto y el nivel de actividad en la tarea. No creo en "palabras unilaterales".

Volviendo a la pregunta en sí, ¿cuáles son las razones de los diferentes efectos anteriores? Solo el código fuente es el más confiable.

3. Análisis de escenarios y exploración de código fuente.

Este artículo se basa en el código fuente de Android 12.0 para explorar. El rendimiento de los escenarios anteriores en diferentes versiones de Android es consistente.

3.1 Notas sobre la depuración del código fuente

El método de depuración del código fuente se ha enseñado en detalle en muchos artículos, por lo que este artículo no los repetirá. Aquí hay solo un breve resumen de las cosas que necesitan atención :

  1. Al descargar el emulador, no utilice la versión de Google Play. Esta versión es similar a la versión de usuario y no puede seleccionar el proceso system_process para los puntos de interrupción.

  2. Incluso para el código fuente y el simulador oficial de Google, habrá casos en los que el número de líneas no se corresponda con el punto de interrupción (por ejemplo: el simulador se ejecutará con el método A, pero cuando el punto de interrupción se interrumpa en el código fuente, el el método real no se puede ubicar El número de líneas correspondientes a A), no hay una buena solución para este problema, y ​​solo podemos evitarlo tanto como sea posible, como mantener la versión del simulador consistente con la versión del código fuente, establecer más puntos de interrupción para aumentar la probabilidad de localizar líneas clave.

3.2 Punto de quiebre preliminar, resultado de inicio claro

Tomando [Escenario 0] como ejemplo, confirmemos preliminarmente por qué no hay respuesta cuando B-3 salta a B-2, y si el sistema ha notificado el motivo.

3.2.1 Aclarar los resultados de la puesta en marcha y sus fuentes

En la depuración de puntos de interrupción del código fuente de Android, hay dos tipos comunes de procesos: proceso de aplicación y proceso system_process.

En el proceso de solicitud, podemos obtener el resultado del código de estado del resultado de inicio de la aplicación, que se usa para decirnos si el inicio se realizó correctamente. La pila involucrada se muestra en la siguiente figura (marcador 1):

Clase de actividad:: startActivity() → startActivityForResult() →  Clase de instrumentación:: execStartActivity(), el resultado del valor de retorno es el resultado de la ejecución de ATMS (ActivityTaskManagerService).

Como se indica en la figura anterior (Marca 2), el método ATMS class:: startActivity() devuelve resultado=3.

En el proceso system_process, veamos cómo se asigna el resultado = 3. Se omiten los pasos detallados del punto de interrupción y la pila real se muestra en la siguiente figura (marca 1):

Clase ATMS:: startActivity() → startActivityAsUser() →  ActivityStarter clase:: execute() → executeRequest() → startActivityUnchecked() → startActivityInner() → recycleTask(), el resultado se devuelve en recycleTask().

Como se muestra en la figura anterior (nota 2), el resultado se asigna cuando mMovedToFront=false, es decir, result=START_DELIVERED_TO_TOP=3, y START_SUCCESS=0 significa que la creación se realizó correctamente.

Eche un vistazo a la descripción de START_DELIVERED_TO_TOP en el código fuente, como se muestra a continuación:

Resultado de IActivityManaqer.startActivity: la actividad no se inició realmente, pero el Intent dado se asignó a la actividad principal existente.

(Resultado de IActivityManaqer.startActivityActivity: la actividad en realidad no se inició, pero la intención dada se le dio a una actividad de nivel superior existente).

"La actividad no se inicia realmente" - sí, porque se puede reutilizar

"La intención dada se proporcionó a la actividad de nivel superior existente"; en realidad, no, la actividad de nivel superior3 no recibió ninguna devolución de llamada, onNewIntent () no se ejecutó e incluso intentó pasar nuevos parámetros a través de Intent :: putExtra (), Actividad3 Tampoco recibido. ¿El documento oficial nos trae otro punto de interrogación? Registramos este problema y lo analizamos más adelante.

¿Qué condiciones se deben cumplir para causar el resultado de START_DELIVERED_TO_TOP? La idea del autor es encontrar las diferencias comparándolas con el proceso normal de inicio.

3.3 Punto de interrupción del proceso, explore el proceso de inicio

En términos generales, cuando localizamos un problema, estamos acostumbrados a inferir la causa a través del resultado, pero el proceso de inversión solo puede enfocarse en la rama del código que está fuertemente relacionada con el problema y no puede darnos una buena comprensión de la imagen completa. .

Por lo tanto, en esta sección, introduciremos la lógica que está fuertemente relacionada con la anterior [escenario 01234] en el proceso de startActivity mediante lectura secuencial. Brevemente de nuevo:

  1. [Escena 0] En la misma tarea, salta del B-3 superior al B-2, quédate en el B-3

  2. [Escenario 1] Desde C-4 en otra tarea, salta a B-2——salta a B-3

  3. [Escenario 2] Cambie el método de saltar de C-4 a B-2 en la Escena 1 a setClassName()——cree una nueva instancia B-2

  4. [Escenario 3] En el escenario 1, cambie el salto de C-4 a B-2 para saltar a B-3: cree una nueva instancia de B-3

  5. [Escenario 4] Asigne taskAffinity a B-3 en el Escenario 3: cree una nueva tarea y una nueva instancia de B-3

3.3.1 Descripción general del código fuente del proceso

En el código fuente, todo el proceso de inicio es muy largo y hay muchos métodos y lógicas involucrados. Para que sea más fácil para todos clasificar el orden de las llamadas a métodos y facilitar la lectura del contenido posterior, el autor organiza el las clases clave y las relaciones de llamadas a métodos involucradas en este artículo son las siguientes.

Si no conoce la relación de llamada en la lectura de seguimiento, puede volver aquí para verificar:

// ActivityStarter.java
 
    ActivityStarter::execute() {
        executeRequest(intent) {
            startActivityUnchecked() {
                startActivityInner();
        }
    }
    ActivityStarter::startActivityInner() {
        setInitialState();
        computeLaunchingTaskFlags();
        Task targetTask = getReusableTask(){
            findTask();
        }
        ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity();
        if (targetTaskTop != null) {
            startResult = recycleTask() {
                setTargetRootTaskIfNeeded();
                complyActivityFlags();
                if (mAddingToTask) {
                    return START_SUCCESS; //【场景2】【场景3】从recycleTask()返回
                }
                resumeFocusedTasksTopActivities()
                return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;//【场景1】【场景0】从recycleTask()返回
            }
        } else {
            mAddingToTask = true;
        }
        if (startResult != START_SUCCESS) {
            return startResult;//【场景1】【场景0】从startActivityInner()返回
        }
        deliverToCurrentTopIfNeeded();
        resumeFocusedTasksTopActivities();
        return startResult;
    }

3.3.2 Análisis de procesos clave

(1) Inicialización

startActivityInner() es el método más importante. Como se muestra en las siguientes figuras, este método primero llamará a setInitialState() para inicializar varias variables globales y llamará a reset() para restablecer varios estados en ActivityStarter.

En el camino, tomamos nota de dos variables clave, mMovedToFront y mAddingToTask, que aquí se restablecen en falso.

Entre ellos, mMovedToFront representa si la tarea de destino debe moverse al primer plano cuando la tarea es reutilizable; maddingToTask representa si se debe agregar la actividad a la tarea.

(2) Calcular y confirmar la bandera al iniciar

En este paso, se usará el método computeLaunchingTaskFlags() para realizar cálculos preliminares basados ​​en el modo de lanzamiento y los atributos de la actividad de origen para confirmar los LaunchFlags.

Aquí nos enfocamos en tratar varios escenarios en los que la actividad de origen está vacía, lo que no tiene nada que ver con los escenarios que mencionamos anteriormente, por lo que no los explicaremos más.

(3) Obtener una tarea que se pueda reutilizar

Este paso se implementa llamando a getReusableTask() para averiguar si hay una tarea que se puede reutilizar.

Permítanme hablar sobre la conclusión primero: en la escena 0123, se pueden obtener tareas reutilizables, pero en la escena 4, no se obtienen tareas reutilizables.

¿Por qué no se puede reutilizar la escena 4? Veamos la implementación clave de getReusableTask().

En la figura anterior (nota 1), putIntoExistingTask representa si se puede colocar una tarea existente. Cuando el indicador contiene NEW_TASK y no contiene MULTIPLE_TASK, o se especifica el modo de ejecución de una sola instancia o una sola tarea, y no se especifica ninguna tarea o se requiere que se devuelva el resultado, se cumplen las condiciones del escenario 01234.

Luego, en la figura anterior (etiqueta 2), busque la tarea que se puede reutilizar a través de findTask() y asigne la actividad en la parte superior de la pila que se encuentra en el proceso a intentActivity. Finalmente, la figura anterior (etiqueta 3) toma como resultado la Task correspondiente a la intentActivity.

¿Cómo encuentra findTask() qué tarea se puede reutilizar?

Es principalmente para confirmar los dos resultados mIdealRecord - "registro de actividad ideal" y mCandidateRecord - "registro de actividad candidato", como intentActivity, y tomar la tarea correspondiente a intentActivity como la tarea de multiplexación.

¿Qué ActivityRecord es el ActivityRecord ideal o candidato? Confirmado en mTmpFindTaskResult.process().

El programa atravesará todas las Tareas en el sistema actual y, en cada Tarea, realizará el trabajo que se muestra en la figura anterior: compare la Actividad real de la parte inferior de la Tarea con la Actividad de destino cls.

En la escena 012, queremos saltar a Activity2, es decir, cls es Activity2, que es lo mismo que realActivity2 en la parte inferior de Task, por lo que Activity3 r en la parte superior de Task se toma como la "Actividad ideal";

En el escenario 3, queremos saltar a Actividad 3, es decir, cls es Actividad 3, que es diferente de actividad real 2 en la parte inferior de la Tarea. Luego juzgamos que las filas de afinidad de pila de Actividad 2 en la parte inferior de la tarea y el destino Activity3 tiene la misma afinidad y usa la Actividad3 superior de la Tarea como "Actividad candidata";

En el escenario 4, no se cumplen todas las condiciones y al final no se puede encontrar ninguna tarea reutilizable. Asigne mAddingToTask a verdadero después de ejecutar getReusableTask()

A partir de esto, podemos explicar el fenómeno de crear una nueva Tarea en [Escenario 4].

(4) Determine si la tarea de destino debe moverse al primer plano

Si hay una Tarea reutilizable, la escena 0123 ejecutará recycleTask(), y varias operaciones se realizarán sucesivamente en este método: setTargetRootTaskIfNeeded(), cumplaActivityFlags().

Primero, el programa ejecutará setTargetRootTaskIfNeeded() para determinar si la tarea de destino debe moverse al primer plano, utilizando mMovedToFront como identificador.

En [Escena 123], la tarea de origen y la tarea de destino son diferentes, differentTopTask es verdadero y, después de una serie de comparaciones de atributos de tareas, se puede concluir que mMovedToFront es verdadero;

En la escena 0, la tarea de origen y la tarea de destino son las mismas, differentTopTask es falso y mMovedToFront sigue siendo inicialmente falso.

A partir de esto, podemos explicar que en [Escena 0], la Tarea no cambia.

(5) Confirme si agregar Actividad a Tarea comparando bandera, Intención, Componente, etc.

Todavía en [Escena 0123], recycleTask() continuará ejecutando cumpliActivityFlags() para confirmar si agregar la actividad a la tarea, utilizando mAddingToTask como identificador.

Este método hará una serie de juicios sobre muchas banderas e información de intención como FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_CLEAR_TASK, FLAG_ACTIVITY_CLEAR_TOP, etc.

En la figura anterior (marcada con 1), primero juzgará si restablecer la Tarea, resetTask, y la condición de juicio es FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Obviamente, el resetTask de la escena 0123 es falso. Continúe ejecutando.

Luego, habrá una variedad de juicios condicionales ejecutados en secuencia.

En [Escena 3], el componente objetivo (mActivityComponent) es B-3 y la actividad real de la tarea objetivo es B-2. Los dos son diferentes y se ingresa el juicio relacionado con resetTask (marca 2).

ResetTask ya era falso antes, por lo que mAddingToTask en [Escenario 3] se establece en verdadero fuera del valor original.

En [Escena 012], las dos actividades comparadas son ambas B-2 (marcadas con 3), y puede ingresar al siguiente nivel de juicio: isSameIntentFilter().

El contenido del juicio en este paso es obvio. El Intento existente de la Actividad2 objetivo se compara con el nuevo Intento. Obviamente, en el Escenario 2, el Intent es naturalmente diferente debido al cambio en el salto setClassName.

Por lo tanto, mAddingToTask en [Scenario 2] se desvía del valor original y se establece en verdadero.

Echa un vistazo al resumen:

[Escena 123] mMovedToFront primero se establece en verdadero, y [Escena 0] ha pasado por muchas pruebas, manteniendo el valor inicial como falso.

——Esto significa que cuando hay tareas reutilizables, [escena 0] no necesita cambiar la tarea al frente; [escena 123] necesita cambiar a la tarea de destino.

[Escena 234] mAddingToTask se establece en verdadero en diferentes etapas, mientras que en [Escena 01], el valor inicial siempre es falso.

——Esto significa que [Escena 234] necesita agregar la Actividad a la Tarea, pero [Escena 01] ya no la necesita.

(6) Iniciar realmente la actividad o devolver directamente el resultado

Cada actividad que se inicie iniciará las llamadas reales de inicio y ciclo de vida a través de una serie de operaciones como resumeFocusedTasksTopActivities().

Nuestra exploración de los escenarios mencionados anteriormente ya tiene la respuesta, y el proceso de seguimiento ya no se preocupará.

4. Reparación de problemas y resolución de problemas restantes

4.1 Corrección de errores

Dado que se han resumido tantas condiciones necesarias anteriormente, solo necesitamos destruir algunas de ellas para reparar los problemas encontrados en el negocio y enumerar brevemente algunas soluciones.

  • Solución 1: Modificar la bandera. Al saltar de B-3 a B-2, agregue FLAG_ACTIVITY_CLEAR_TASK o FLAG_ACTIVITY_CLEAR_TOP, o no configure la bandera directamente. Demostrado que es factible.

  • Solución 2: Modifique el atributo de intención, es decir, [Escena 2]. A-1 salta implícitamente a B-2 a través de la acción, luego B-3 puede saltar a B-2 a través de setClassName o modificando los atributos en la acción. Demostrado que es factible.

  • Opción 3: Eliminar B-2 antes de tiempo. Cuando B-2 salta a B-3, el final deja caer B-2. Cabe señalar que finish() debe ejecutarse antes de startActivity() para evitar la influencia de la información restante de ActivityRecord e Intent en saltos posteriores. Especialmente cuando usa B-2 como el enlace profundo de su propia aplicación para distribuir la actividad, es más digno de vigilancia.

4.2 Cuestiones pendientes

Recuerda algunas de nuestras dudas al principio del artículo, ¿por qué no hay devolución de llamada en NewIntent()?

onNewIntent() será activado por deliverNewIntent(), y deliverNewIntent() solo es llamado por los siguientes dos métodos.

cumpliActivityFlags() es el método en el que nos enfocamos en 3.3.1.5 arriba.Se puede encontrar que todas las condiciones en cumpleActivityFlags() que pueden llamar a deliverNewIntent() se evitan perfectamente.

El método deliverToCurrentTopIfNeeded() se muestra en la siguiente figura.

mLaunchFlags y mLaunchMode no pueden cumplir las condiciones, por lo que dontStart es falso y falta deliverNewIntent().

Hasta ahora, la pregunta de onNewIntent() ha sido respondida.

conclusión V

A través de una serie de suposiciones de escenarios, encontramos muchos fenómenos inesperados:

  1. El documento menciona que FLAG_ACTIVITY_NEW_TASK es equivalente a singleTask, lo cual no es exactamente el caso, solo cuando se combina con otras banderas se puede lograr un efecto similar. La anotación de esta bandera es muy unilateral e incluso puede dar lugar a malentendidos. Un solo factor no puede determinar el rendimiento general.

  2. El documento oficial menciona que START_DELIVERED_TO_TOP pasará el nuevo Intent a la actividad de nivel superior, pero de hecho, no todos los START_DELIVERED_TO_TOP redistribuirán el nuevo Intent.

  3. Para la misma actividad en la parte inferior de la pila, al saltar dos veces a la misma actividad a través de action o setClassName, el segundo salto fallará, pero cuando los dos saltos se realicen de diferentes maneras, tendrá éxito.

  4. Cuando simplemente usa FLAG_ACTIVITY_NEW_TASK, el efecto de saltar al final de la pila es bastante diferente de saltar a otras actividades en la misma pila.

Los problemas encontrados en el negocio, en el análisis final, son causados ​​por una comprensión insuficiente del mecanismo de pila de Android.

Frente a la codificación relacionada con la pila, los desarrolladores deben pensar claramente en la misión de Activty, que lleva a cabo la pila de aplicaciones recién abierta, en la aplicación general. Es necesario evaluar exhaustivamente el historial de tareas, los atributos de las banderas, los atributos del modo de lanzamiento, el contenido de la intención, etc., y refiérase a ellos cuidadosamente. Solo los documentos oficiales pueden evitar trampas de acumulación y lograr resultados ideales y confiables.

Supongo que te gusta

Origin blog.csdn.net/vivo_tech/article/details/130199165
Recomendado
Clasificación