← ホームへ戻る

シニアAndroidエンジニアが深掘りすべき「パフォーマンス計測」の聖域: Compose、Baseline Profiles、そして実測の真実

はじめに

「アプリが少しカクつく」「スクロールが重い気がする」。こうした感覚的な課題に対し、エンジニアとしてどう向き合うか。シニアレベルに求められるのは、勘に頼ったリファクタリングではなく、客観的な数値とトレースに基づいた「計測の設計」です。

Jetpack Composeの導入によって、AndroidのUI開発は宣言的で簡潔になりました。しかし、その裏側にあるレンダリングパイプラインやコンパイラの挙動を理解せずに書くと、かえって原因不明のJank(画面のガクつき)を招くことがあります。本記事では、2026年現在のAndroid開発において、シニアエンジニアが押さえておくべきパフォーマンス計測の急所を解説します。

1. 「Recomposition数」の罠と、型の安定性(Stability)

Composeのデバッグ中にまず見るのが「Recomposition Count」でしょう。しかし、数が多いこと自体が必ずしも悪ではありません。真の地雷は、「スキップ可能(Skippable)なはずのComposableが、スキップされていない」ことにあります。

型の安定性を疑え

外部ライブラリのクラスや、Listなどの標準コレクションを引数に取ると、Composeコンパイラがその型を「Unstable(不安定)」と判断し、再描画のスキップができなくなることがあります。

// アンチパターン: 外部の型をそのまま使うとUnstableとみなされることがある
data class UserState(
    val name: String,
    val tags: List<String> // Listは標準で不安定な型
)

@Composable
fun UserProfile(user: UserState) {
    // userが変わっていなくても、親が再描画されるとここも再描画される
    Text(user.name)
}

プロの対策

  • @Stable@Immutable アノテーションを適切に付与する。
  • kotlinx.collections.immutable を導入して、永続コレクションを使用する。
  • Composeコンパイラのレポート(composeCompiler { metricsDestination = ... })を出力し、どのComposableがスキップ不可能なのかを定量的に特定する。

「なんとなくリメンバーする」のではなく、コンパイラがどう型を評価しているかを把握するのがシニアの仕事です。

2. 実測の王道:System Trace と Perfetto

Android StudioのProfilerも優秀ですが、複雑なマルチスレッドの競合や、OSレイヤー(SurfaceFlingerなど)との同期ズレを追うには、System Trace (Perfetto) が不可欠です。

ここに注目する

  • Main ThreadのJank: 他の作業(ディスクI/O、重い計算)にメインスレッドが奪われていないか。
  • RenderThreadの遅延: GPUへの命令発行が16.6msを超えていないか。
  • Binderトランザクションの詰まり: IPC(プロセス間通信)が原因でUIがブロックされていないか。

ログに tag="Choreographer", msg="Skipped N frames!" が出ている時は、既に手遅れです。Perfettoを開き、どのスレッドのどの関数がVsyncを逃したのか、時間軸に沿って「視覚化」してください。

3. Baseline Profiles は「先制攻撃」の最適化

2026年現在、パフォーマンス最適化は「起きてから直す」ものから「リリース前に仕込んでおく」ものへと進化しました。その中核が Baseline Profiles です。

通常、AndroidアプリはARTによるJITコンパイルで徐々に最適化されますが、インストール直後やアップデート直後は、未コンパイルのコードが走るため非常に重くなります。

導入による劇的な変化

  • アプリ起動時間の短縮(30%以上の改善も珍しくない)。
  • 初回のスクロール体験向上(事前のAOTコンパイルによるJank防止)。
// Baseline Profile 生成用のテストコード
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
    @get:Rule
    val baselineRule = BaselineProfileRule()

    @Test
    fun generate() = baselineRule.collect(
        packageName = "com.example.myapp",
        includeInStartupProfile = true
    ) {
        // ユーザーの主要なジャーニーを記述
        startActivityAndWait()
        scrollList()
        goToDetails()
    }
}

この生成スクリプトをCI/CDに組み込み、リリースバイナリに常に最新のプロファイルを同梱させる仕組みを構築しましょう。

まとめ:パフォーマンスは「機能」である

「動くこと」が開発者の責任なら、「心地よく動き続けること」はエンジニアの誇りです。Androidにおけるパフォーマンス最適化の勘所は以下の3点に集約されます。

  1. 推論を捨てろ: コンパイラレポートやProfilerで、再描画の真因を特定する。
  2. 層を越えろ: Kotlinのコードだけでなく、System TraceでOSレベルの挙動を追う。
  3. 自動化しろ: Baseline ProfilesをCIに載せ、予防的な最適化を仕組み化する。

技術は日々進化しますが、「実測に基づき、ボトルネックを科学的に潰す」という姿勢こそ、エンジニアが持つべき最も普遍的な知識です。

次にカクつきを感じた時、あなたはコードを書き換える前に、まずProfilerの記録ボタンを押せるでしょうか。その一歩が、プロフェッショナルへの境界線です。

🤘

メタルで聴く

この記事をメタルサウンドで
Engine of Silence, Pulse of Light
Technical Progressive Metal
微細なJank(ガクつき)を削ぎ落とし、極限の滑らかさを追求するエンジニアリングのストイックさが、超絶技巧と緻密な構成を併せ持つTechnical Progressive Metalと共鳴するため。
🎵 Lyrics
フレームの隙間に 潜む魔物 推測の壁を 打ち破れ ミリ秒の審判 逃げ道はない 実測の光で 闇を抜けろ 最適化は 終わらない咆哮 ユーザーの指先に 自由を刻め コードの裏側 鼓動を感じて 静寂を生むための 旋律を