Android Jetpack Compose determina el alcance de la reorganización y optimiza la reorganización

1. Información general

El artículo anterior mencionó que la reorganización de Compose es inteligente: la función Composable omitirá la reorganización innecesaria tanto como sea posible al reorganizar y solo reorganizará la interfaz de usuario que debe cambiarse. Entonces, ¿cómo decide Compose que es necesario cambiar la interfaz de usuario? O en otras palabras, ¿cómo determina Compose el alcance de la reorganización? Si la reorganización ocurre aleatoriamente, el rendimiento de la interfaz de usuario será muy inestable, a veces bueno y otras veces malo. Y si hay un problema con el código de la interfaz de usuario escrito, la reorganización provocará confusión en el estado y provocará errores de visualización de la interfaz de usuario. Por lo tanto, solo aclarando el alcance de la reorganización de Compose podremos evitar mejor los obstáculos de la reorganización y optimizar para alcances específicos. Por lo tanto, este artículo presentará cómo determinar el alcance de la reorganización de Compose y optimizar el rendimiento de la reorganización.

2. Determinar el alcance de la reorganización Composable

Determinar el alcance de la reorganización nos ayuda a comprender mejor la optimización del rendimiento de ComposeUI. Veamos primero un ejemplo:

    @Composable
    fun CounterDemo(){
    
    
        Log.d("zhongxj","范围1=>运行")
        var counter by remember {
    
     mutableStateOf(0) }
        Column {
    
    
            Log.d("zhongxj","范围2=>运行")
            Button(onClick = {
    
    
                Log.d("zhongxj","onButtonClick:点击按钮")
                counter ++
            }){
    
    
                Log.d("zhongxj","范围3=>运行")
                Text(text = "+")
            }

            Text(text = "$counter")
        }
    }

En el código anterior, todavía usamos el ejemplo del contador para verificar el alcance de la reorganización. Colocamos Iniciar sesión en cada lugar donde pueda ocurrir la reorganización. Cuando se hace clic en el botón, la actualización del estado del contador activará la reorganización de CounterDemo. El registro es como se muestra a continuación:
Insertar descripción de la imagen aquí

En la imagen, podemos ver que Log.d("zhongxj","范围3=>运行")esta línea de Registro no está escrita. La razón por la cual esta línea de Registro no está escrita requiere que comprendamos el principio subyacente de la reorganización de Compose:

En Compose, la función Composable procesada por el compilador Compose puede establecer automáticamente una asociación mientras lee el estado. Durante el proceso de ejecución, cuando el estado cambia, Compose encontrará el bloque de código asociado y lo marcará como no válido. Antes de que llegue el siguiente cuadro de renderizado. , Compose activará la reorganización y ejecutará el bloque de código no válido, y el bloque de código no válido es el alcance de la siguiente reorganización. El código que se puede marcar como no válido tiene dos requisitos: primero, el código marcado como no válido debe ser una función Composable no en línea sin valor de retorno y, segundo, debe ser un Lambda sin valor de retorno.

Entonces, ¿por qué los bloques de código involucrados en la reorganización tienen que ser funciones no en línea y sin retorno? Debido a que la función en línea se expandirá en el sitio de la llamada durante la compilación, no puede encontrar una entrada de llamada adecuada durante la próxima reorganización y solo puede compartir el alcance de la reorganización de la persona que llama. Para las funciones que devuelven un valor, el valor devuelto afectará a la persona que llama, por lo que se debe contactar a la persona que llama para participar en la reorganización. Por lo tanto, las funciones en línea con valores de retorno no se pueden utilizar como bloques de código no válidos.

Y comprenda el principio de reorganización subyacente de Compose,我们就可以清楚的知道了只有受到State变化影响的代码块,才会参与到重组。不依赖State的代码则不参与重组,这就是重组的最小化原则。

Con base en el principio de minimización de la reorganización, podemos analizar los resultados de salida en nuestro ejemplo de contador. De hecho, después de mirar el registro, encontramos que Log.d("zhongxj","范围3=>运行")esta línea de registro no se registró, lo que significa que el bloque de código donde esta línea de registro se encuentra que no participó en la reorganización y está en el alcance del alcance 2. , vemos esta línea de código Text(text = "$counter"). Es obvio que esta línea de código depende del estado del contador. Cabe señalar que esta línea de código no significa leer el valor del contador. Significa leer el contador en el alcance del alcance 2. El valor se pasa al Texto, por lo que el alcance 2 participará en la reorganización y se generará el registro. En este momento, algunos lectores pueden Log.d("zhongxj","范围2=>运行")encontrar que de acuerdo con el principio de minimización de la reorganización, el alcance mínimo para acceder al contador debe ser: el alcance del alcance 2. ¿Por qué se imprimen también los registros en el rango 1? Aquí debemos recordar lo que dijimos antes: la definición de alcance minimizado debe ser una función componible no en línea o lambda. El componente Columna es una función de orden superior declarada en línea:
Insertar descripción de la imagen aquípor lo tanto, el contenido se expandirá internamente en el sitio de la llamada, por lo que el alcance 1 y el alcance 2 comparten el alcance de la reorganización, por lo que se Log.d("zhongxj","范围1=>运行")genera el registro. Supongamos que la Columna se reemplaza con una función no -inline Composable. , entonces Log.d("zhongxj","范围1=>运行")no habrá salida, por ejemplo, si se reemplaza con un componente de Tarjeta, los lectores pueden probarlo ellos mismos.

Cabe señalar que aunque el botón no depende del contador, la reorganización del alcance 2 activará la recuperación del botón, por lo que Log.d("zhongxj","onButtonClick:点击按钮")también se generará, pero su contenido no depende del contador internamente, por lo que el registro del alcance 3 : Log.d("zhongxj", "scope 3 => run") no se generará.

补充说明: Composable 函数观察State变化并触发重组是在被称为”快照“的系统中完成的,所谓”快照“就是将被访问的状态像拍照一样保存下来,当状态变化时,通知相关的Composable应用的最新状态。”快照“有利于对状态管理进行线程隔离,在多线程场景下的重组有重要的应用

3. Optimizar el desempeño de la reorganización.

Luego del análisis anterior, no entendí que la reorganización de Compose es inteligente y sigue el principio de minimización de alcance, el Composable ejecutado durante la reorganización solo participará en esta reorganización cuando sus parámetros cambien.

Compose generará un árbol de vista después de la ejecución, y cada Composable corresponde a un nodo en el árbol, por lo que Composable 智能重组的本质其实是从树上寻找对应位置的节点并与之进行比较,如果节点未发生变化则不用更新.

Además, cabe señalar que el proceso de construcción real del árbol de vista es relativamente complicado: durante la ejecución de Composable, el estado de composición generado primero se almacena en SlotTable y luego el marco genera el árbol LayoutNode basado en SlotTable y completa la representación final de la interfaz. Por tanto, es prudente decir que la lógica de comparación de Composable ocurre en SlotTable.

3.1 Índice de posición componible

Durante el proceso de reorganización, los nodos en Composición pueden completar varios cambios, como agregar, eliminar, mover y actualizar. El compilador de Compose generará una clave de índice para Composable según la posición de llamada del código y la almacenará en Composición. Composable la pasará durante ejecución La comparación con la clave puede indicar qué operación se debe realizar actualmente. Por ejemplo, el siguiente código de muestra:

    Box {
    
    
            if (state) {
    
    
                val str = remember(Unit) {
    
     "call_site_1" }
                Text(text = str) // Text_of_call_site_1
            } else {
    
    
                val str = remember(Unit) {
    
     "call_site_2" }
                Text(text = str) // Text_of_call_site_2
            }
        }

Como se muestra en el código anterior: cuando Composable encuentra declaraciones condicionales como if/else, insertará un código similar a startXXXGroup e identificará el aumento o disminución de nodos agregando una clave de índice. El código anterior mostrará diferentes textos según diferentes estados. El compilador creará índices para las ramas if y else respectivamente. Cuando el estado cambia de verdadero a falso, Box se reorganiza. A través del juicio de la clave, se puede ver que el código en else debe insertarse en la ejecución lógica y los nodos generados en el if deben eliminarse.

Suponiendo que no existe un índice de posición en el momento de la compilación y que solo se basa en la comparación del tiempo de ejecución, cuando se ejecuta por primera vez recordar (Unidad), la cadena almacenada en el árbol actual aún se devolverá debido a razones de almacenamiento en caché, es decir, call_site_1, y luego se ejecutará en Text_of_call_site_1, se encuentra que es el mismo que el str almacenado en el árbol actual. Los tipos de nodos son los mismos y el parámetro str no ha cambiado. Por lo tanto, se juzgará que no es necesaria ninguna reorganización y el texto no puede estar actualizado.

Entonces, para resumir: la indexación componible en tiempo de compilación es la base para garantizar que su reorganización se pueda realizar de manera inteligente y correcta. Este índice se determina en función de dónde se llama a Composable en el código estático. Sin embargo, en algunos escenarios, Composable no se puede indexar a través de ubicaciones de código estático. En este caso, debemos agregar índices manualmente para facilitar la comparación durante la reorganización.

3.2 Agregar información de índice a través de clave

Supongamos que ahora necesitamos dar una lista de películas y luego mostrar información general sobre las películas. El código es el siguiente:

@Composable
    fun MoviesScreen(movies:List<Movie>){
    
    
        Column {
    
     
            for (movie in movies){
    
    
                // showMoveCardInfo 无法在编译期间进行索引,只能根据运行时的index进行索引
                showMoveCardInfo(movie)
            }
        }
    }

Como se muestra en el código anterior, la información de la película se muestra según el nombre de la película. En este momento, no se puede indexar según la posición en el código. Solo se puede indexar según el índice en tiempo de ejecución. En este caso, el índice cambiará según la cantidad de elementos, lo que hará imposible una comparación precisa. En este caso, cuando se produce la reorganización, los datos recién insertados se compararán con los primeros datos anteriores, los primeros datos anteriores se compararán con los segundos datos y luego los segundos datos anteriores se tratarán como nuevos datos insertados. El resultado es que todos los elementos se reorganizarán, pero el comportamiento que esperamos es que solo sea necesario reorganizar los datos recién insertados y otros datos sin cambios no deben reorganizarse, por lo que podemos usar el método clave para agregar manualmente un índice a Composable en tiempo de ejecución., De la siguiente manera:

@Composable
    fun MoviesScreen(movies:List<Movie>){
    
    
        Column {
    
     
            for (movie in movies){
    
    
               key(movie.id){
    
     // 使用movie的唯一ID作为Composable的索引
                showMoveCardInfo(movie)
                }
            }
        }
    }

Utilice el ID de la película para pasarlo a Composable como índice único. Cuando se insertan nuevos datos, el índice del objeto anterior no se altera y aún puede desempeñar un papel de anclaje durante la comparación, por lo que otros elementos que no han cambiado no necesitan hacerlo. participar en la reorganización.

3.3 Utilice la anotación @Stable para optimizar la reorganización

Composable determina si se reorganizará en función de los resultados de la comparación de los parámetros, es decir, solo cuando los objetos de parámetros que participan en la comparación son estables y son iguales y devuelven verdadero, se consideran iguales. Los tipos básicos comunes en las expresiones String y Lambda de Kotlin (Boolean, Int, Long, Float, Char) pueden considerarse estables porque todos son tipos inmutables. Por tanto, los resultados de sus comparaciones de parámetros son creíbles. Pero si el parámetro es de tipo variable, el resultado de la comparación no será fiable.

data class Mutabledata(var data:String)
    @Composable
    fun MutableDemo(){
    
    
        var mutable = remember {
    
     Mutabledata("walt") }

        var state by remember {
    
     mutableStateOf(false) }
        if(state){
    
    
            mutable.data = "zxj"
        }

        Button(onClick = {
    
    state = true}){
    
    
           showText(mutable)
        }
    }
    @Composable
    fun ShowText(mutable:MutableData){
    
    
     Text(text = mutable.data) // 会随着state的变化而变化
    }

En el código anterior, MutableData es un objeto inestable porque tiene datos variables de tipo Var. Cuando se hace clic en el botón para cambiar el estado, el mutable modificará los datos. Para ShowText, el parámetro mutable apunta al mismo valor antes y después. el estado cambia Un objeto, por lo que simplemente confiar en iguales para juzgar pensará que los parámetros no han cambiado, pero de hecho la prueba encontró que la función ShowText se ha reorganizado, por lo que el tipo de parámetro Mutabledata es inestable y el resultado igual es no confiable.

Por lo tanto, para algunas clases de colección que no se consideran estables de forma predeterminada, como interfaz o lista, si puede garantizar su estabilidad en tiempo de ejecución, puede agregarles anotaciones @State y el compilador tratará estos tipos como tipos estables, por lo tanto Desempeñar el papel de reorganización y mejorar el desempeño. El código se ve así:

@Stable
interface UiState<T>{
    
    
    val value:T?
    val exception:Throwable?
    val hasError:Boolean
        get() = exception != null
}

注意: 被添加为@Statble的普通父类、密封类、接口等其派生子类也会被认为时稳定的

Supongo que te gusta

Origin blog.csdn.net/zxj2589/article/details/133131917
Recomendado
Clasificación