Android Jetpack Compose détermine la portée de la réorganisation et optimise la réorganisation

1. Vue d'ensemble

L'article précédent mentionnait que la réorganisation de Compose est intelligente.La fonction Composable évitera autant que possible les réorganisations inutiles lors de la réorganisation et ne réorganisera que l'interface utilisateur qui doit être modifiée. Alors, comment Compose décide-t-il que l’interface utilisateur doit changer ? Ou en d’autres termes, comment Compose détermine-t-il l’étendue de la réorganisation. Si la réorganisation se produit de manière aléatoire, les performances de l'interface utilisateur seront dans un état très instable, parfois bonnes et parfois mauvaises. Et s'il y a un problème avec le code de l'interface utilisateur écrit, la réorganisation entraînera une confusion d'état, provoquant des erreurs d'affichage de l'interface utilisateur. Par conséquent, ce n'est qu'en clarifiant la portée de la réorganisation de Compose que nous pourrons mieux éviter les pièges de la réorganisation et optimiser des portées spécifiques.Par conséquent, cet article présentera comment déterminer la portée de la réorganisation de Compose et optimiser les performances de la réorganisation.

2. Déterminer le périmètre de la réorganisation Composable

Déterminer l'étendue de la réorganisation nous aide à mieux comprendre l'optimisation des performances de ComposeUI. Regardons d'abord un exemple :

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

Dans le code ci-dessus, nous utilisons toujours l'exemple de compteur pour vérifier la portée de la réorganisation. Nous mettons Log à chaque endroit où une réorganisation peut avoir lieu. Lorsque vous cliquez sur le bouton, la mise à jour de l'état du compteur déclenchera la réorganisation de CounterDemo. Le journal est comme indiqué ci-dessous :
Insérer la description de l'image ici

Sur l'image, nous pouvons voir que Log.d("zhongxj","范围3=>运行")cette ligne de Log n'est pas saisie. La raison pour laquelle cette ligne de Log n'est pas saisie nous oblige à comprendre le principe sous-jacent de la réorganisation de Compose :

Dans Compose, la fonction Composable traitée par le compilateur Compose peut établir automatiquement une association lors de la lecture de l'état. Pendant le processus en cours, lorsque l'état change, Compose trouvera le bloc de code associé et le marquera comme invalide. Avant l'arrivée de la prochaine image de rendu , Compose déclenchera la réorganisation et exécutera le bloc de code invalide, et le bloc de code invalide est la portée de la prochaine réorganisation. Le code qui peut être marqué comme Invalide a deux exigences. Premièrement, le code marqué comme Invalid doit être une fonction Composable non-inline sans valeur de retour, et deuxièmement, il doit être un Lambda sans valeur de retour.

Alors pourquoi les blocs de code impliqués dans la réorganisation doivent-ils être des fonctions non-inline et non-retour ? Étant donné que la fonction en ligne sera étendue au site d'appel lors de la compilation, elle ne pourra pas trouver d'entrée d'appel appropriée lors de la prochaine réorganisation et ne pourra partager que la portée de réorganisation de l'appelant. Pour les fonctions qui renvoient une valeur, la valeur de retour affectera l'appelant, celui-ci doit donc être contacté pour participer à la réorganisation. Par conséquent, les fonctions en ligne avec des valeurs de retour ne peuvent pas être utilisées comme blocs de code invalides.

Et comprendre le principe de réorganisation sous-jacent de Compose,我们就可以清楚的知道了只有受到State变化影响的代码块,才会参与到重组。不依赖State的代码则不参与重组,这就是重组的最小化原则。

Basé sur le principe de minimisation de la réorganisation, nous pouvons analyser les résultats de sortie dans notre contre-exemple. En fait, après avoir regardé le journal, nous avons constaté que Log.d("zhongxj","范围3=>运行")cette ligne de journal n'était pas journalisée, ce qui signifie que le bloc de code où cette ligne de journal se trouve n'a pas participé à la réorganisation et est dans le périmètre du scope 2. , on voit cette ligne de code Text(text = "$counter"). Il est évident que cette ligne de code s'appuie sur l'état du compteur. A noter que cette ligne de code ne signifie pas pour lire la valeur du compteur. Cela signifie lire le compteur dans la portée de la portée 2. La valeur est passée dans le texte, donc la portée 2 participera à la réorganisation et le journal sera affiché. À ce stade, certains lecteurs peuvent Log.d("zhongxj","范围2=>运行")trouver que selon le principe de minimisation de la réorganisation, la portée minimale d'accès au compteur doit être : la portée de la portée 2, Pourquoi les journaux de la plage 1 sont-ils également imprimés ? Ici, nous devons rappeler ce que nous avons dit précédemment : la définition de la portée minimisée doit être une fonction composable non-inline ou lambda. Le composant Column est une fonction d'ordre supérieur déclarée en ligne :
Insérer la description de l'image icile contenu sera donc développé en interne sur le site d'appel, donc les portées 1 et 2 partagent la portée de la réorganisation, donc le Log.d("zhongxj","范围1=>运行")journal est généré. Supposons que Column soit remplacé par un non -inline Composable. , alors Log.d("zhongxj","范围1=>运行")il n'y aura aucune sortie, par exemple, s'il est remplacé par un composant Card, les lecteurs peuvent l'essayer eux-mêmes.

Il est à noter que bien que Button ne dépend pas du compteur, la réorganisation du scope 2 déclenchera le rappel de Button, il sera donc Log.d("zhongxj","onButtonClick:点击按钮")également sorti, mais son contenu ne dépend pas du compteur en interne, donc le log du scope 3 : Log.d("zhongxj" , "scope 3 => run") ne sera pas affiché.

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

3. Optimiser la performance de la réorganisation

Après l'analyse précédente, je n'ai pas compris que la réorganisation de Compose est intelligente et suit le principe de minimisation de portée. Le Composable exécuté lors de la réorganisation ne participera à cette réorganisation que lorsque ses paramètres changent.

Compose générera une arborescence de vues après exécution, et chaque Composable correspond à un nœud sur l'arborescence, donc Composable 智能重组的本质其实是从树上寻找对应位置的节点并与之进行比较,如果节点未发生变化则不用更新.

De plus, il convient de noter que le processus de construction réel de l'arborescence de vue est relativement compliqué. Lors de l'exécution de Composable, l'état de composition généré est d'abord stocké dans le SlotTable, puis le framework génère l'arborescence LayoutNode basée sur le SlotTable et termine le rendu final de l’interface. Il est donc prudent de dire que la logique de comparaison de Composable se produit dans SlotTable.

3.1 Indice de position composable

Au cours du processus de réorganisation, les nœuds sur Composition peuvent effectuer diverses modifications telles que l'ajout, la suppression, le déplacement et la mise à jour. Le compilateur Compose générera une clé d'index pour Composable en fonction de la position d'appel du code et la stockera dans Composition. Composable la transmettra pendant La comparaison avec Key peut indiquer quelle opération doit être effectuée actuellement. Par exemple, l'exemple de code suivant :

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

Comme le montre le code ci-dessus : lorsque Composable rencontre des instructions conditionnelles telles que if/else, il insérera un code similaire à startXXXGroup et identifiera l'augmentation ou la diminution des nœuds en ajoutant une clé d'index. Le code ci-dessus affichera différents textes en fonction de différents états. Le compilateur créera des index pour les branches if et else respectivement. Lorsque l'état passe de vrai à faux, la boîte est réorganisée. Grâce au jugement de la clé, on peut voir que le code dans else doit être inséré dans l'exécution logique, et les nœuds générés dans le if doivent être supprimés.

En supposant qu'il n'y a pas d'index de position au moment de la compilation et qu'il repose uniquement sur la comparaison à l'exécution, lors de la première exécution de Remember(Unit), la chaîne stockée dans l'arborescence actuelle sera toujours renvoyée pour des raisons de mise en cache, c'est-à-dire call_site_1, puis exécutée vers Text_of_call_site_1, on constate qu'il est identique à la str stockée dans l'arborescence actuelle. Les types de nœuds sont les mêmes et le paramètre str n'a pas changé. Par conséquent, il sera jugé qu'aucune réorganisation n'est nécessaire et que le texte ne peut pas être mis à jour.

Donc, pour résumer : l'indexation Composable au moment de la compilation est la base pour garantir que sa réorganisation peut être effectuée de manière intelligente et correcte. Cet index est déterminé en fonction de l'endroit où Composable est appelé dans le code statique. Cependant, dans certains scénarios, Composable ne peut pas être indexé via des emplacements de code statiques. Dans ce cas, nous devons ajouter manuellement des index pour faciliter la comparaison lors de la réorganisation.

3.2 Ajouter des informations d'index via Key

Supposons que nous devions maintenant donner une liste de films, puis afficher des informations générales sur les films. Le code est le suivant :

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

Comme le montre le code ci-dessus, les informations sur le film sont affichées en fonction du nom du film. Pour le moment, elles ne peuvent pas être indexées en fonction de la position dans le code. Elles ne peuvent être indexées qu'en fonction de l'index au moment de l'exécution. Dans ce cas, l’indice changera en fonction du nombre d’éléments, ce qui rendra impossible une comparaison précise. Dans ce cas, lors de la réorganisation, les données nouvellement insérées seront comparées aux premières données précédentes, les premières données précédentes seront comparées aux secondes données, puis les secondes données précédentes seront traitées comme de nouvelles données insérées. Le résultat est que tous les éléments seront réorganisés, mais le comportement que nous attendons est que seules les données nouvellement insérées doivent être réorganisées et que les autres données inchangées ne doivent pas être réorganisées, nous pouvons donc utiliser la méthode key pour ajouter manuellement un index à Composable à d'exécution. ,Comme suit :

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

Utilisez l'ID du film à transmettre à Composable comme index unique. Lorsque de nouvelles données sont insérées, l'index de l'objet précédent n'est pas perturbé et peut toujours jouer un rôle d'ancrage lors de la comparaison, de sorte que les autres éléments qui n'ont pas changé n'ont pas besoin de le faire. participer à la réorganisation.

3.3 Utiliser l'annotation @Stable pour optimiser la réorganisation

Composable détermine s'il faut réorganiser en fonction des résultats de la comparaison des paramètres. C'est-à-dire que ce n'est que lorsque les objets paramètres participant à la comparaison sont stables et que égal renvoie vrai, ils sont considérés comme égaux. Les types de base courants dans les expressions Kotlin (Boolean, Int, Long, Float, Char) String et Lambda peuvent être considérés comme stables car ce sont tous des types immuables. Les résultats de leurs comparaisons de paramètres sont donc crédibles. Mais si le paramètre est de type variable, le résultat de la comparaison ne sera pas 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的变化而变化
    }

Dans le code ci-dessus, MutableData est un objet instable car il possède une variable de type Var. Lorsque vous cliquez sur le bouton pour changer d'état, le mutable modifiera les données. Pour ShowText, le paramètre mutable pointe vers la même valeur avant et après. l'état change. Un objet, donc se fier simplement à égal pour juger pensera que les paramètres n'ont pas changé, mais en fait le test a révélé que la fonction ShowText a été réorganisée, donc le type de paramètre Mutabledata est instable, et le résultat égal est pas digne de confiance.

Par conséquent, pour certaines classes de collection qui ne sont pas considérées comme stables par défaut, telles que l'interface ou la liste, si vous pouvez garantir leur stabilité au moment de l'exécution, vous pouvez leur ajouter l'annotation @State, et le compilateur traitera ces types comme des types stables, Jouer ainsi le rôle de réorganisation et améliorer les performances. Le code ressemble à ceci :

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

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

Je suppose que tu aimes

Origine blog.csdn.net/zxj2589/article/details/133131917
conseillé
Classement