Gradle ビルドキャッシュや KSP キャッシュを消すとき Gradle デーモン止めないと意味がない件

Android 開発をしていると、Git 操作後や依存関係を変更したあとに KSP(Kotlin Symbol Processing)やビルドキャッシュの問題 に遭遇することがあります。


java.lang.IllegalStateException: Storage for [C:\...\symbolLookups\id-to-file.tab] is already registered

この記事では、Gradle デーモンとキャッシュの関係を整理し、「キャッシュを消したのにビルドが直らない」問題 を回避する方法を解説します。

 

🤔 Gradle デーモンとは何か

Gradle はビルドの高速化のために デーモン と呼ばれるバックグラウンドプロセスを使います。

常駐プロセスとして、次回のビルドでキャッシュを再利用

複数プロジェクトやターミナルで同時に動作可能

Android Studio 再起動だけでは停止しない場合がある

デーモン状態の確認方法


./gradlew --status


No Gradle daemons are running

→ デーモン未起動


PID STATUS INFO
3494 IDLE 8.11.1

→ デーモン起動済み、待機中


PID STATUS INFO
3494 BUSY 8.11.1

デーモン停止は以下で可能:


./gradlew --stop

 

🤔 キャッシュを消しても直らない理由

KSP や Gradle のキャッシュを削除しただけでは、既に動作中のデーモンが古いキャッシュ情報を保持している場合があります。

典型的なケース


1. git rebase でコミットを整理

2. KSP が参照する Room の型情報や生成コードが変更

3. Gradle デーモンが古いキャッシュを保持 → ビルド失敗

つまり、キャッシュを消しても、デーモンが古い情報を持っている限り意味がありません。

 

🤔 解決手順まとめ

① Gradle デーモンを止める


./gradlew --stop

すべての Gradle デーモンを終了

次回ビルド時に新しいデーモンが起動

② KSP キャッシュを削除する


rm -rf .ksp/ */build/kspCaches */build/generated/ksp/

KSP が持つ生成キャッシュを完全に削除

再ビルド時に最新のコードから生成される

③ Android Studio のキャッシュをクリア


File → Invalidate Caches / Restart → Invalidate and Restart

IDE 側のキャッシュもクリア

デーモン再起動時に正しい情報を読み込む

④ KSP のインクリメンタルコンパイルを無効化(必要に応じて)

gradle.properties に追加:


ksp.incremental=false

キャッシュ競合によるエラー(例:java.lang.IllegalStateException: Storage for [C:\...\symbolLookups\id-to-file.tab] is already registered)を回避

 

🤔 Git Rebase 後の KSP ビルドフロー


Git Rebase
    ↓
ソースコード変更
    ↓
KSP キャッシュ古いまま
    ↓ (Gradle デーモン生きている場合)
ビルド失敗 → Storage already registered など
    ↓
./gradlew --stop でデーモン停止
    ↓
KSP キャッシュ削除
    ↓
再ビルド
    ↓
成功

 

🤔 まとめ

Gradle デーモンは Android Studio 再起動だけでは必ずしも止まらない

KSP やビルドキャッシュを消すときは必ずデーモンを止める

典型的な手順:


./gradlew --stop

rm -rf .ksp/ */build/kspCaches */build/generated/ksp/

Android Studio キャッシュクリア

ビルド再実行

これで Git 操作後や依存関係変更後のビルド失敗を防げる

Gradle デーモンの挙動を理解しておくと、「キャッシュ消したのに直らない」問題 に悩まされずに済みます。

ビルド高速化の恩恵を受けつつ、必要なときにはデーモンを適切に制御しましょう。

android - java.lang.IllegalStateException: Storage for [C:\...\symbolLookups\id-to-file.tab] is already registered - Stack Overflow


【Jetpack Compose】回転するアニメーションの作り方

これ、おもしろい!!




👉 回転おじさん - Facebook

ちょっと画像をお借りして、

やってみましたが、、、


@Composable
fun Humans() {
  var currentRotation by remember { mutableFloatStateOf(0f) }
  val rotation = remember { Animatable(currentRotation) }

  LaunchedEffect(Unit) {
    rotation.animateTo(
      targetValue = currentRotation + 360f,
      animationSpec = infiniteRepeatable(
        animation = tween(3000, easing = LinearEasing),
        repeatMode = RepeatMode.Restart
      )
    ) {
      currentRotation = rotation.value
    }
  }
  Image(
    modifier = Modifier
      .fillMaxSize()
      .rotate(rotation.value)
      .aspectRatio(1.0f),
    painter = painterResource(R.drawable.humans),
    contentDescription = null
  )
}

ダメでした。。。

なんで、おじさんが走ってくれないのでしょうかー。


【Jetpack Compose】@Composable のライフサイクルをシンプルに使うには DisposableEffect

なんか、いろんな方法があるんだなあ、と。

👉 Lifecycle を Compose と統合する  |  App architecture  |  Android Developers

👉 ComposeでLifecycleを監視する(2023年9月バージョン) - Kenji Abe - Medium

いろいろ試しながらどこから使っていくのがいいか。を考える。

 

🤔 参考になるコードたち

調べてみると、

DisposableEffect を中心に考えるのがいいだろう、

と思える。


@Composable
fun LifecycleEffect(
  onCreate: () -> Unit = { },
  onStart: () -> Unit = { },
  onResume: () -> Unit = { },
  onPause: () -> Unit = { },
  onStop: () -> Unit = { },
  onDestroy: () -> Unit = { },
  onAny: () -> Unit = { }
) {
  val lifecycleOwner = LocalLifecycleOwner.current
  DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
      when (event) {
        Event.ON_CREATE -> onCreate()
        Event.ON_START -> onStart()
        Event.ON_RESUME -> onResume()
        Event.ON_PAUSE -> onPause()
        Event.ON_STOP -> onStop()
        Event.ON_DESTROY -> onDestroy()
        Event.ON_ANY -> onAny()
      }
    }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose {
      lifecycleOwner.lifecycle.removeObserver(observer)
    }
  }
}

👉 PatchNote-Android/app/src/main/java/com/easyhz/patchnote/core/common/util/Lifecycle.kt at e24ff1d32831b54177348e079412c95b17e3f0f4 · easyhz/PatchNote-Android


@Composable
fun LifecycleEffect(
  lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
  onStart: (suspend () -> Unit)? = null,
  onPause: (suspend () -> Unit)? = null,
  onStop: (suspend () -> Unit)? = null
) {
  val scope = rememberCoroutineScope()
  val currentOnStart by rememberUpdatedState(onStart)
  val currentOnPause by rememberUpdatedState(onPause)
  val currentOnStop by rememberUpdatedState(onStop)

  DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
      scope.launch {
        when (event) {
          Lifecycle.Event.ON_START -> currentOnStart?.invoke()
          Lifecycle.Event.ON_PAUSE -> currentOnPause?.invoke()
          Lifecycle.Event.ON_STOP -> currentOnStop?.invoke()
          else -> {}
        }
      }
    }
    lifecycleOwner.lifecycle.addObserver(observer)

    onDispose {
      lifecycleOwner.lifecycle.removeObserver(observer)
    }
  }
}

👉 itunes/app/src/main/java/com/simgesengun/itunes/ui/launchedEffect/LifecycleEffect.kt at cc63faa4eb78f22a7e2e8b95c78e1ca30f4f11ad · simgesengun/itunes


@Immutable
enum class LifecycleEvent {
  OnStop
}

@Immutable
data class LifecycleHandler(
  val id: String,
  val event: LifecycleEvent,
  val onEvent: () -> Unit
)

@Composable
fun <R : Any> DestinationScope<R>.LifecycleEffect(
  event: LifecycleEvent,
  onEvent: () -> Unit
) {
  val id = remember { randomUUID() }
  DisposableEffect(this, id, event, onEvent) {
    navigator.update {
      it.copy(lifecycleHandlers = it.lifecycleHandlers + LifecycleHandler(
        id = id,
        event = event,
        onEvent = onEvent
      ))
    }
    onDispose {
      navigator.update { dest ->
        dest.copy(lifecycleHandlers = dest.lifecycleHandlers.filter { it.id != id })
      }
    }
  }
}

👉 showcase/common/src/iosMain/kotlin/dev/ahmedmourad/showcase/common/navigation/LifecycleHandler.kt at 83cc58c29acc2dc3bbcc4de0644e79e74826514e · AhmedMourad0/showcase

 

🤔 まとめ

まずは、ここらから記述していくのがいいのではないか。


@Composable
@NonRestartableComposable
fun DisposableLifecycleEffect(
  lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle,
  onResume: () -> Unit,
  onPause: () -> Unit,
) {
  DisposableEffect(lifecycle) {
    val observer = LifecycleEventObserver { _, event ->
      when (event) {
        Lifecycle.Event.ON_RESUME -> onResume()
        Lifecycle.Event.ON_PAUSE -> onPause()
        else -> { }
      }
    }
    lifecycle.addObserver(observer)
    onDispose {
      lifecycle.removeObserver(observer)
    }
  }
}

👉 florisboard/app/src/main/kotlin/dev/patrickgold/florisboard/lib/compose/DisposableLifecycleEffect.kt at 35fd70ce6d2de05cf8be44fcd9a16b3b6d537453 · florisboard/florisboard

記述位置は Screen。

@NonRestartableComposable の意味を考えながら書く。

フォアグラウンド、バックグラウンドの切り分けにも使えるか。