O Android Jetpack Compose determina o escopo da reorganização e otimiza a reorganização

1. Visão Geral

O artigo anterior mencionou que a reorganização do Compose é inteligente. A função Composable irá pular reorganizações desnecessárias tanto quanto possível durante a reorganização e reorganizar apenas a IU que precisa ser alterada. Então, como o Compose decide que a IU precisa mudar? Ou, em outras palavras, como o Compose determina o escopo da reorganização. Se a reorganização ocorrer aleatoriamente, o desempenho da UI ficará em um estado muito instável, às vezes bom e às vezes ruim. E se houver um problema com o código da UI escrito, a reorganização causará confusão de estado, causando erros de exibição da UI. Portanto, somente esclarecendo o escopo da reorganização do Compose podemos evitar melhor as armadilhas da reorganização e otimizar escopos específicos.Portanto, este artigo apresentará como determinar o escopo da reorganização do Compose e otimizar o desempenho da reorganização.

2. Determine o escopo da reorganização Composable

Determinar o escopo da reorganização nos ajuda a entender melhor a otimização do desempenho do ComposeUI. Vejamos primeiro um exemplo:

    @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")
        }
    }

No código acima, ainda usamos o exemplo do contador para verificar o escopo da reorganização. Colocamos Log em todos os locais onde a reorganização pode ocorrer. Quando o botão é clicado, a atualização do status do contador irá acionar a reorganização do CounterDemo. O log é mostrado abaixo:
Insira a descrição da imagem aqui

Pela imagem, podemos ver que Log.d("zhongxj","范围3=>运行")esta linha de Log não é digitada. A razão pela qual esta linha de Log não é digitada exige que entendamos o princípio subjacente da reorganização do Compose:

No Compose, a função Composable processada pelo compilador Compose pode estabelecer automaticamente uma associação durante a leitura do estado. Durante o processo em execução, quando o estado for alterado, o Compose encontrará o bloco de código associado e o marcará como inválido. Antes que o próximo quadro de renderização chegue. , o Compose acionará a reorganização e executará o bloco de código inválido, e o bloco de código inválido será o escopo da próxima reorganização. O código que pode ser marcado como inválido tem dois requisitos: primeiro, o código marcado como inválido deve ser uma função Composable não inline sem valor de retorno e, segundo, deve ser um Lambda sem valor de retorno.

Então, por que os blocos de código envolvidos na reorganização precisam ser funções não inline e sem retorno? Como a função embutida será expandida no site de chamada durante a compilação, ela não poderá encontrar uma entrada de chamada adequada durante a próxima reorganização e só poderá compartilhar o escopo de reorganização do chamador. Para funções que retornam um valor, o valor retornado afetará o chamador, portanto o chamador deve ser contatado para participar da reorganização. Portanto, funções inline com valores de retorno não podem ser usadas como blocos de código inválidos.

E entenda o princípio de reorganização subjacente do Compose,我们就可以清楚的知道了只有受到State变化影响的代码块,才会参与到重组。不依赖State的代码则不参与重组,这就是重组的最小化原则。

Com base no princípio da minimização da reorganização, podemos analisar os resultados de saída em nosso exemplo de contador. Na verdade, depois de olhar o log, descobrimos que Log.d("zhongxj","范围3=>运行")esta linha de log não foi registrada, o que significa que o bloco de código onde esta linha de log está localizado não participou da reorganização e está no escopo do escopo 2., vemos esta linha de código Text(text = "$counter"). É óbvio que esta linha de código depende do estado do contador. Deve-se notar que esta linha de código não significa ler o valor do contador. Significa ler o contador no escopo do escopo 2. O valor é passado para o Texto, então o escopo 2 participará da reorganização e o log será gerado. Neste momento, alguns leitores podem Log.d("zhongxj","范围2=>运行")encontrar que de acordo com o princípio de minimização da reorganização, o escopo mínimo para acesso ao contador deve ser: o escopo do escopo 2, Por que os logs do intervalo 1 também são impressos? Aqui precisamos lembrar o que dissemos antes: a definição de escopo minimizado deve ser uma função combinável não inline ou lambda. O componente Column é uma função de ordem superior declarada in-line:
Insira a descrição da imagem aquiportanto, o conteúdo será expandido internamente no site de chamada, portanto o escopo 1 e o escopo 2 compartilham o escopo da reorganização, portanto o Log.d("zhongxj","范围1=>运行")log é gerado. Suponha que Column seja substituído por um não -inline Composable. , então Log.d("zhongxj","范围1=>运行")não haverá saída, por exemplo, se for substituído por um componente Card, os leitores poderão tentar por conta própria.

Deve-se notar que embora o Button não dependa do contador, a reorganização do escopo 2 irá acionar a rechamada do Button, portanto ele Log.d("zhongxj","onButtonClick:点击按钮")também será gerado, mas seu conteúdo não depende do contador internamente, portanto o log do escopo 3 : Log.d("zhongxj" , "scope 3 => run") não será gerado.

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

3. Otimize o desempenho da reorganização

Após a análise anterior, não entendi que a reorganização do Compose é inteligente e segue o princípio da minimização de escopo, o Composable executado durante a reorganização só participará dessa reorganização quando seus parâmetros mudarem.

O Compose irá gerar uma árvore de visualização após a execução, e cada Composable corresponde a um nó na árvore, portanto Composable 智能重组的本质其实是从树上寻找对应位置的节点并与之进行比较,如果节点未发生变化则不用更新.

Além disso, deve-se notar que o processo real de construção da árvore de visualização é relativamente complicado. Durante a execução do Composable, o estado Composition gerado é primeiro armazenado no SlotTable e, em seguida, a estrutura gera a árvore LayoutNode com base no SlotTable e completa a renderização final da interface. Portanto é prudente dizer que a lógica de comparação do Composable ocorre no SlotTable.

3.1 Índice de posição combinável

Durante o processo de reorganização, os nós no Composition podem concluir várias alterações, como adicionar, excluir, mover e atualizar. O compilador do Compose gerará uma chave de índice para o Composable com base na posição de chamada do código e a armazenará no Composition. O Composable a transmitirá durante execução. A comparação com Key pode dizer qual operação deve ser executada atualmente. Por exemplo, o seguinte código de exemplo:

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

Conforme mostrado no código acima: Quando Composable encontra instruções condicionais como if/else, ele inserirá um código semelhante a startXXXGroup e identificará o aumento ou diminuição de nós adicionando uma chave de índice. O código acima exibirá diferentes textos de acordo com diferentes estados. O compilador criará índices para as ramificações if e else respectivamente. Quando o estado muda de verdadeiro para falso, a Caixa é reorganizada. Através do julgamento da chave, pode-se perceber que o código no else precisa ser inserido na execução lógica, e os nós gerados no if precisam ser removidos.

Supondo que não haja índice de posição no tempo de compilação e dependa apenas da comparação em tempo de execução, ao executar pela primeira vez Remember(Unit), o str armazenado na árvore atual ainda será retornado por motivos de cache, ou seja, call_site_1, e então executado para Text_of_call_site_1, verifica-se que é igual ao str armazenado na árvore atual. Os tipos de nós são os mesmos e o parâmetro str não foi alterado. Portanto, será julgado que nenhuma reorganização é necessária e o texto não pode ser atualizado.

Então, resumindo: A indexação composable em tempo de compilação é a base para garantir que sua reorganização possa ser realizada de forma inteligente e correta. Esse índice é determinado com base em onde Composable é chamado no código estático. No entanto, em alguns cenários, o Composable não pode ser indexado através de locais de código estático. Neste caso, precisamos adicionar índices manualmente para facilitar a comparação durante a reorganização.

3.2 Adicionar informações de índice por meio de chave

Suponha que agora precisamos fornecer uma lista de filmes e depois exibir informações gerais sobre os filmes. O código é o seguinte:

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

Conforme mostrado no código acima, as informações do filme são exibidas com base no nome do filme. Neste momento, elas não podem ser indexadas com base na posição no código. Só podem ser indexadas com base no índice em tempo de execução. Nesse caso, o índice mudará de acordo com a quantidade de itens, impossibilitando uma comparação precisa. Neste caso, quando ocorrer a reorganização, os dados recém-inseridos serão comparados com os primeiros dados anteriores, os primeiros dados anteriores serão comparados com os segundos dados e, em seguida, os segundos dados anteriores serão tratados como Novos dados inseridos. O resultado é que todos os itens serão reorganizados, mas o comportamento que esperamos é que apenas os dados recém-inseridos precisem ser reorganizados, e outros dados inalterados não devem ser reorganizados, então podemos usar o método key para adicionar manualmente um índice ao Composable em tempo de execução. ,Como segue:

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

Use o ID do filme a ser passado para Composable como o índice exclusivo. Quando novos dados são inseridos, o índice do objeto anterior não é interrompido e ainda pode desempenhar um papel de ancoragem durante a comparação, portanto, outros itens que não foram alterados não precisam ser alterados. participar da reorganização.

3.3 Use a anotação @Stable para otimizar a reorganização

Composable determina se deve ser reorganizado com base nos resultados da comparação dos parâmetros.Ou seja, somente quando os objetos de parâmetro participantes da comparação são estáveis ​​e iguais retornam verdadeiros, eles são considerados iguais. Os tipos básicos comuns em expressões Kotlin (Boolean, Int, Long, Float, Char) String e Lambda podem ser considerados estáveis ​​porque são todos tipos imutáveis. Portanto, os resultados das suas comparações de parâmetros são credíveis. Mas se o parâmetro for do tipo variável, o resultado da comparação não será confiável.

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的变化而变化
    }

No código acima, MutableData é um objeto instável porque possui dados variáveis ​​​​do tipo Var. Quando o botão é clicado para alterar o estado, o mutável modificará os dados. Para ShowText, o parâmetro mutable aponta para o mesmo valor antes e depois o estado muda. Um objeto, então apenas confiar em equals para julgar vai pensar que os parâmetros não mudaram, mas na verdade o teste descobriu que a função ShowText foi reorganizada, então o tipo de parâmetro Mutabledata é instável e o resultado igual é não é confiável.

Portanto, para algumas classes de coleção que não são consideradas estáveis ​​por padrão, como interface ou lista, se você puder garantir sua estabilidade em tempo de execução, poderá adicionar anotações @State a elas, e o compilador tratará esses tipos como tipos estáveis, portanto Desempenhar o papel de reorganização e melhorar o desempenho. O código fica assim:

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

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

Acho que você gosta

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