クラッシュが起きた時
Jetpack Compose + Google Maps でルート地図にスタート・ゴールのカスタムマーカーを実装していました。マーカーのアイコンは Bitmap に Canvas で描画し BitmapDescriptorFactory.fromBitmap() に渡す方式です。
@Composable
private fun rememberStartMarkerIcon(): BitmapDescriptor? {
val density = LocalDensity.current
return remember(density) {
val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
val canvas = android.graphics.Canvas(bitmap)
// Canvas に旗を描画
canvas.drawPath(flagPath, flagPaint)
canvas.drawLine(poleX, 0f, poleX, heightPx.toFloat(), polePaint)
BitmapDescriptorFactory.fromBitmap(bitmap).also { bitmap.recycle() }
}
}
描画が終わった Bitmap はもう不要だと思い、also { bitmap.recycle() } で即解放しました。
これで動作確認のためにマップ画面を開いた瞬間にクラッシュしました。
Fatal Exception: java.lang.RuntimeException
Canvas: trying to use a recycled bitmap android.graphics.Bitmap@XXXXXXXX
recycle() の行は疑わなかったため、原因特定に時間がかかりましたが、いろいろ調べた結果原因は、fromBitmap() は Bitmap をコピーしないという事でした。
BitmapDescriptorFactory.fromBitmap(bitmap) は渡した Bitmap の参照を内部に保持するだけで、その場でピクセルデータをコピーするわけではありません。実際にピクセルデータが読まれるのは、Google Maps がマーカーを画面に描画するときです。
recycle() の直後には落ちず、マップ初回描画時に落ちるので一見原因が見えにくいのが厄介でした。
解決法
解決しるのは簡単で、recycle() を消すだけ
具体的には、
BitmapDescriptorFactory.fromBitmap(bitmap).also { bitmap.recycle() }
を
BitmapDescriptorFactory.fromBitmap(bitmap)
変更したらクラッシュしなくなりました。
理由は、 remember ブロック内であれば、マーカーが不要になると参照が切れて自動で解放されるそうです。