From 742ec1cd6fed16a83b92c2b372ad791460a7fe3e Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Mon, 31 Aug 2026 13:11:14 +0300 Subject: [PATCH] perf(Android): recycle replaced shadow bitmaps --- .../views/bottomtabs/ShadowLayout.kt | 11 ++++- .../views/bottomtabs/ShadowLayoutTest.kt | 41 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 android/src/test/java/com/reactnativenavigation/views/bottomtabs/ShadowLayoutTest.kt diff --git a/android/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt b/android/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt index 9d48a48fd1..fd86230f5c 100644 --- a/android/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt +++ b/android/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt @@ -36,8 +36,8 @@ open class ShadowLayout(context: Context) : FrameLayout(context) { override fun onDetachedFromWindow() { super.onDetachedFromWindow() - bitmap?.recycle() - bitmap = null + recycleShadowBitmap() + invalidateShadow = true } var isShadowed: Boolean = false @@ -108,6 +108,7 @@ open class ShadowLayout(context: Context) : FrameLayout(context) { if (isShadowed) { if (invalidateShadow) { if (bounds.width() != 0 && bounds.height() != 0) { + recycleShadowBitmap() bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888).also { mainCanvas.setBitmap(it) invalidateShadow = false @@ -132,4 +133,10 @@ open class ShadowLayout(context: Context) : FrameLayout(context) { super.dispatchDraw(canvas) } + + private fun recycleShadowBitmap() { + mainCanvas.setBitmap(null) + bitmap?.recycle() + bitmap = null + } } diff --git a/android/src/test/java/com/reactnativenavigation/views/bottomtabs/ShadowLayoutTest.kt b/android/src/test/java/com/reactnativenavigation/views/bottomtabs/ShadowLayoutTest.kt new file mode 100644 index 0000000000..d7d169fe0e --- /dev/null +++ b/android/src/test/java/com/reactnativenavigation/views/bottomtabs/ShadowLayoutTest.kt @@ -0,0 +1,41 @@ +package com.reactnativenavigation.views.bottomtabs + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.view.View +import com.reactnativenavigation.BaseTest +import org.junit.Assert.assertNotSame +import org.junit.Assert.assertTrue +import org.junit.Test + +class ShadowLayoutTest : BaseTest() { + @Test + fun regeneratingShadowRecyclesPreviousBitmap() { + val view = ShadowLayout(newActivity()).apply { isShadowed = true } + val output = Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888) + + measureAndDraw(view, output) + val firstShadow = shadowBitmap(view) + + view.requestLayout() + measureAndDraw(view, output) + val secondShadow = shadowBitmap(view) + + assertNotSame(firstShadow, secondShadow) + assertTrue(firstShadow.isRecycled) + output.recycle() + } + + private fun measureAndDraw(view: ShadowLayout, output: Bitmap) { + val size = View.MeasureSpec.makeMeasureSpec(40, View.MeasureSpec.EXACTLY) + view.measure(size, size) + view.layout(0, 0, 40, 40) + view.draw(Canvas(output)) + } + + private fun shadowBitmap(view: ShadowLayout): Bitmap { + val field = ShadowLayout::class.java.getDeclaredField("bitmap") + field.isAccessible = true + return field.get(view) as Bitmap + } +}