解析jetpack合成ジャンプ問題-metrics


以前、複数のモジュールからCompose Compilerのないモジュールクラスを取得して使用した場合、Skippableは必要に応じていないことを指摘しました.
データを使用してこれらの情報を簡単に表示し、分析する方法について説明します.

Metricsの作成


合成を適用するモジュールを構築します.ランプに次のコードを追加します.

kotlinOptions {
	freeCompilerArgs += ["-P",
						"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${rootProject.file(".").absolutePath}/compose-metrics"]
	freeCompilerArgs += ["-P", 
    					"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${rootProject.file(".").absolutePath}/compose-reports"]
}
以前に配置されたアイテムでバージョンを変更しました.
次に、コンストラクションを回転すると、次のフォルダとファイルが作成されます.
project
│
└───compose-metrics
│   │   app_debug-module.json
└───compose-reports
    │   app_debug-classes.txt
    │   app_debug-composables.csv
    │   app_debug-composables.txt

Files

app_debug-module.json

{
 "skippableComposables": 6,
 "restartableComposables": 7,
 "readonlyComposables": 0,
 "totalComposables": 7,
 ...
}
まず目立つ書類を見てみましょう.これらのファイルを理解するには、次の概念を理解する必要があります.
  • SkippableComposables
  • RestartableComposables
  • この2つの組合せをこのブログのcode snippetによって理解する.

    Restartable Composables


    再起動可能プロファイルとは、Snapshotオブジェクトの状態が変更されたときに再実行できる構成可能な関数の数です.
    SnapshotはJetpack Componentで使用されるState Class Holderです.
    Jetpack ComponentはSnapshotシステムを用いて状態変化を検出する.
    正しい内容は下記のこのブログをご覧ください.
    複合を生成するComponent Compilerによって生成されるコードを以下に示します.
    @Composable
    fun Counter() {
    	var count by remember { mutableStateOf(0) }
    	Button(
    		onClick = { count += 1 }
    	) {
    		Text(text = "Count : $count")
    	}
    }
    上記の簡単なカウンタコードは、次のようにCompose Compilerによって変換されます.
    @Composable
    public static final void Counter(@Nullable Composer $composer, final int $changed) {
    
    	$composer = $composer.startRestartGroup(741257999);
        
        ...
        
        if ($changed == 0 && $composer.getSkipping()) {
        	$composer.skipToGroupEnd();
        
        	...
    
    	} else {
        	$composer.startReplaceableGroup(-492369756);
            
            ...
            
            $composer.endReplaceableGroup();
            ButtonKt.Button(
            	...,
                $composer,
                ...
            )
        }
        
        ScopeUpdateScope var14 = $composer.endRestartGroup();
        if (var14 != null) {
        	var14.updateScope((Function2)(new Function2() {
                public final void invoke(@Nullable Composer $composer, int $force) {
                   SimpleComposeSampleKt.Counter($composer, $changed | 1);
                }
             }));
         }
    }
    変換されたコードでComposerのendRestartGroupから返された値が空でない場合は、ステータスが変更されたかどうかを検出してリカバリが実行されます.
    通常,この観測はCorposable関数では単独で記述しない.
    Compose CompilerではSnapshotシステムはオートファインダーです.
    Snapshot
    簡単なMutablesnapshotを使用した例を次に示します.
    MutablesnapshotのパラメータはreadObserverとwriteObserverであり,この2つのファイバによって状態の変化を検出する.
    fun main() {
    	val state = mutableStateOf("first")
    	val snapShot = Snapshot.takeMutableSnapshot(
    		readObserver = { println("The State is Read : $it") },
    		writeObserver = { println("The State is OverWritten : $it") }
    	)
    	snapShot.enter {
    		state.value
    		state.value = "second"
    	}
    	snapShot.apply()
    }
    以上の関数の出力は次のとおりです.
    The State is Read : MutableState(value=first)@189568618
    The State is OverWritten : MutableState(value=second)@189568618
    これにより、Snapshotシステムは、状態の変化を検出し、リカバリ例のコードなどのリカバリを行うことができる.
    さらに理解するために、コードを通じて入ります.
  • 以降補充計画
  • var14.updateScope((Function2)(new Function2() {
    	public final void invoke(@Nullable Composer $composer, int $force) {
        	SimpleComposeSampleKt.Counter($composer, $changed | 1);
        }
    }));
    
    override fun updateScope(block: (Composer, Int) -> Unit) { this.block = block }
    
    fun compose(composer: Composer) {
    	block?.invoke(composer, 1) ?: error("Invalid restart scope")
    }

    Skippable Composables


    構成可能関数のスキップとは、構成可能関数のパラメータが以前と同じである場合にskipが発生する可能性があることを意味し、これは構成可能関数の数を意味する.
    同様に、簡単なコードを見てみましょう.
    @Composable 
    fun Google(number: Int) {
     Address(
       number=number,
       street="Amphitheatre Pkwy",
       city="Mountain View",
       state="CA"
       zip="94043"
     )
    }
    以上のコードの変換コード.
    @Composable
    public static final void Google(final int number, @Nullable Composer $composer, final int $changed) {
    	$composer = $composer.startRestartGroup(-1476322213);
         
         ...
          
        int $dirty = $changed;
        if (($changed & 14) == 0) {
            $dirty = $changed | ($composer.changed(number) ? 4 : 2);
        }
    
    	if (($dirty & 11) == 2 && $composer.getSkipping()) {
            $composer.skipToGroupEnd();
        } else {
            Address(
            	number,
             	LiveLiterals$SimpleComposeSampleKt.INSTANCE.String$arg-1$call-Address$fun-Google(), 
                LiveLiterals$SimpleComposeSampleKt.INSTANCE.String$arg-2$call-Address$fun-Google(), 
                LiveLiterals$SimpleComposeSampleKt.INSTANCE.String$arg-3$call-Address$fun-Google(), 
                LiveLiterals$SimpleComposeSampleKt.INSTANCE.String$arg-4$call-Address$fun-Google(),
                $composer,
                14 & $dirty
            );
        }
          
          ...
     
     }
    スキップ可能な部分のコードだけを見せます
    $changedという静的intデータのビット演算により、パラメータの値が変更されたかどうかを決定し、スキップできるかどうかを決定します.
    これらのパラメータの値は、Corpose CompilerによってSlot Table、$Corposerにデータが格納されます.changedからslotテーブルからデータを取り出します.
    slotテーブルから抽出したデータを比較して、スキップできるかどうかを確認します.

    Metrics File with Project Code


    メトリックをもう一度確認しましょう.

    app_debug-module.json

    {
     "skippableComposables": 6,
     "restartableComposables": 7,
     "readonlyComposables": 0,
     "totalComposables": 7,
     ...
    }

    app_debug-composables.txt

    restartable scheme("[androidx.compose.ui.UiComposable]") fun UserProfile(
      unstable user: User
    )
    restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun WrappedUserProfile(
      stable userUiState: UserUiState
    )
    restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun OtherContent(
      stable otherState: Int
    )

    app_debug-composables.csv


    namecomposableskippablerestartableUserProfile101WrappedUserProfile111OtherContent111
    生成されたデータのコードを表示するには、次の手順に従います.
    class MainActivity : ComponentActivity() {
    	override fun onCreate(savedInstanceState: Bundle?) {
    		super.onCreate(savedInstanceState)
    
    		setContent {
    			val viewModel: MainViewModel = viewModel()
    			val otherState by viewModel.otherState.collectAsState()
    			val userState by viewModel.wrappedUser.collectAsState()
    			val user by viewModel.user.collectAsState()
    
    			Column(
    				modifier = Modifier.fillMaxSize(),
    				verticalArrangement = Arrangement.SpaceBetween
    			) {
    				UserProfile(user)
    				WrappedUserProfile(userUiState = userState)
    				Button(viewModel::onButtonClick) {
    					Text(text = "Set User")
    				}
    				OtherContent(otherState = otherState)
    				Button(viewModel::onOtherAction) {
    					Text(text = "Other Action")
    				}
    			}
    		}
    	}
    }
    
    @Composable
    fun UserProfile(user: User) {
    	println("User Profile Composable")
    	Text(text = user.name)
    }
    
    @Composable
    fun WrappedUserProfile(userUiState: UserUiState) {
    	println("Wrapped User Profile Composable")
    	Text(text = userUiState.user.name)
    }
    
    @Composable
    fun OtherContent(otherState: Int) {
    	println("Other Content Composable")
    	Text(text = otherState.toString())
    }
    Composeの予想通りにSkipまたはRecompositionが発生したかどうかを確認することを目的としています.そのため、全体的なデータを表示するには、app_debug-module.jsonを表示し、各構成可能な関数の逆コンパイル結果を表示します.app_debug-composables.txtapp_debug-composables.csv.
    app debug-composiblesは、コードのmetricsを分析するために使用されます.txtを表示します.
    restartable scheme("[androidx.compose.ui.UiComposable]") fun UserProfile(
      unstable user: User
    )
    restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun WrappedUserProfile(
      stable userUiState: UserUiState
    )
    restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun OtherContent(
      stable otherState: Int
    )
    プレゼンテーションレイヤで構成されたWrappedユーザー、OtherStateをパラメータとして渡すWrappedUserProfile、およびOtherContextには通常スキップ機能があり、ドメインレイヤで構成されたユーザーモデルを直接使用するUSerProfileにはスキップ機能がありません.
    したがって、正しい動作を確認するには、@Stableまたは@Immutable Annotation(前のシリーズなど)を貼り付ける必要があります.
    プロジェクトコードは下にあります.
    https://github.com/TaehoonLeee/multi-module-compose-model-test

    References

  • https://github.com/androidx/androidx/blob/androidx-main/compose/compiler/design/compiler-metrics.md
  • https://chris.banes.dev/composable-metrics/
  • https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd
  • https://qiita.com/takahirom/items/6907e810d3661e19cfcf
  • https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn