Skip to content

Commit 8743eb1

Browse files
committedNov 28, 2024··
RUM-6195: Allow ResourcesLruCache to work with any key
1 parent 4590058 commit 8743eb1

File tree

8 files changed

+63
-61
lines changed

8 files changed

+63
-61
lines changed
 

‎features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/BitmapCachesManager.kt

+8-4
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,19 @@ internal class BitmapCachesManager(
5151
isBitmapPoolRegisteredForCallbacks = true
5252
}
5353

54-
internal fun putInResourceCache(drawable: Drawable, resourceId: String) {
55-
resourcesLRUCache.put(drawable, resourceId.toByteArray(Charsets.UTF_8))
54+
internal fun putInResourceCache(key: String, resourceId: String) {
55+
resourcesLRUCache.put(key, resourceId.toByteArray(Charsets.UTF_8))
5656
}
5757

58-
internal fun getFromResourceCache(drawable: Drawable): String? {
59-
val resourceId = resourcesLRUCache.get(drawable) ?: return null
58+
internal fun getFromResourceCache(key: String): String? {
59+
val resourceId = resourcesLRUCache.get(key) ?: return null
6060
return String(resourceId, Charsets.UTF_8)
6161
}
6262

63+
internal fun generateResourceKeyFromDrawable(drawable: Drawable): String? {
64+
return (resourcesLRUCache as? ResourcesLRUCache)?.generateKeyFromDrawable(drawable)
65+
}
66+
6367
internal fun putInBitmapPool(bitmap: Bitmap) {
6468
bitmapPool.put(bitmap)
6569
}

‎features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/BitmapPool.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ internal class BitmapPool(
9191
}
9292

9393
@Synchronized
94-
override fun get(element: String): Bitmap? {
95-
val bitmapsWithReqDimensions = bitmapsBySize[element] ?: return null
94+
override fun get(key: String): Bitmap? {
95+
val bitmapsWithReqDimensions = bitmapsBySize[key] ?: return null
9696

9797
// find the first unused bitmap, mark it as used and return it
9898
return bitmapsWithReqDimensions.find {

‎features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/Cache.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ package com.datadog.android.sessionreplay.internal.recorder.resources
99
internal interface Cache<K : Any, V : Any> {
1010

1111
fun put(value: V) {}
12-
fun put(element: K, value: V) {}
13-
fun get(element: K): V? = null
12+
fun put(key: String, value: V) {}
13+
fun get(key: String): V? = null
1414
fun size(): Int
1515
fun clear()
1616

‎features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/ResourceResolver.kt

+6-2
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@ internal class ResourceResolver(
233233
bitmapCachesManager.putInBitmapPool(bitmap)
234234
}
235235

236-
bitmapCachesManager.putInResourceCache(drawable, resourceId)
236+
val key = bitmapCachesManager.generateResourceKeyFromDrawable(drawable) ?: return
237+
bitmapCachesManager.putInResourceCache(key, resourceId)
237238
}
238239

239240
@WorkerThread
@@ -321,7 +322,10 @@ internal class ResourceResolver(
321322

322323
private fun tryToGetResourceFromCache(
323324
drawable: Drawable
324-
): String? = bitmapCachesManager.getFromResourceCache(drawable)
325+
): String? {
326+
val key = bitmapCachesManager.generateResourceKeyFromDrawable(drawable) ?: return null
327+
return bitmapCachesManager.getFromResourceCache(key)
328+
}
325329

326330
private fun shouldUseDrawableBitmap(drawable: BitmapDrawable): Boolean {
327331
return drawable.bitmap != null &&

‎features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/ResourcesLRUCache.kt

+5-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import android.graphics.drawable.AnimationDrawable
1212
import android.graphics.drawable.Drawable
1313
import android.graphics.drawable.DrawableContainer
1414
import android.graphics.drawable.LayerDrawable
15-
import androidx.annotation.VisibleForTesting
1615
import androidx.collection.LruCache
1716
import com.datadog.android.sessionreplay.internal.recorder.safeGetDrawable
1817
import com.datadog.android.sessionreplay.internal.utils.CacheUtils
@@ -45,9 +44,7 @@ internal class ResourcesLRUCache(
4544
}
4645

4746
@Synchronized
48-
override fun put(element: Drawable, value: ByteArray) {
49-
val key = generateKey(element)
50-
47+
override fun put(key: String, value: ByteArray) {
5148
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
5249
invocationUtils.safeCallWithErrorLogging(
5350
call = { cache.put(key, value) },
@@ -56,11 +53,11 @@ internal class ResourcesLRUCache(
5653
}
5754

5855
@Synchronized
59-
override fun get(element: Drawable): ByteArray? =
56+
override fun get(key: String): ByteArray? =
6057
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
6158
invocationUtils.safeCallWithErrorLogging(
6259
call = {
63-
cache.get(generateKey(element))
60+
cache.get(key)
6461
},
6562
failureMessage = FAILURE_MSG_GET_CACHE
6663
)
@@ -76,9 +73,8 @@ internal class ResourcesLRUCache(
7673
)
7774
}
7875

79-
@VisibleForTesting
80-
internal fun generateKey(drawable: Drawable): String =
81-
generatePrefix(drawable) + System.identityHashCode(drawable)
76+
internal fun generateKeyFromDrawable(element: Drawable): String =
77+
generatePrefix(element) + System.identityHashCode(element)
8278

8379
private fun generatePrefix(drawable: Drawable): String {
8480
return when (drawable) {

‎features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/BitmapCachesManagerTest.kt

+12-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.Extensions
2424
import org.mockito.Mock
2525
import org.mockito.junit.jupiter.MockitoExtension
2626
import org.mockito.junit.jupiter.MockitoSettings
27+
import org.mockito.kotlin.any
2728
import org.mockito.kotlin.verify
2829
import org.mockito.kotlin.whenever
2930
import org.mockito.quality.Strictness
@@ -58,8 +59,13 @@ internal class BitmapCachesManagerTest {
5859
@StringForgery
5960
lateinit var fakeResourceId: String
6061

62+
@StringForgery
63+
lateinit var fakeResourceKey: String
64+
6165
@BeforeEach
6266
fun `set up`() {
67+
whenever(mockResourcesCache.generateKeyFromDrawable(any())).thenReturn(fakeResourceId)
68+
6369
testedCachesManager = createBitmapCachesManager(
6470
bitmapPool = mockBitmapPool,
6571
resourcesLRUCache = mockResourcesCache,
@@ -103,20 +109,20 @@ internal class BitmapCachesManagerTest {
103109
@Test
104110
fun `M put in resource cache W putInResourceCache`() {
105111
// When
106-
testedCachesManager.putInResourceCache(mockDrawable, fakeResourceId)
112+
testedCachesManager.putInResourceCache(fakeResourceKey, fakeResourceId)
107113

108114
// Then
109-
verify(mockResourcesCache).put(mockDrawable, fakeResourceId.toByteArray(Charsets.UTF_8))
115+
verify(mockResourcesCache).put(fakeResourceKey, fakeResourceId.toByteArray(Charsets.UTF_8))
110116
}
111117

112118
@Test
113119
fun `M get resource from resource cache W getFromResourceCache { resource exists in cache }`() {
114120
// Given
115121
val fakeCacheData = fakeResourceId.toByteArray(Charsets.UTF_8)
116-
whenever(mockResourcesCache.get(mockDrawable)).thenReturn(fakeCacheData)
122+
whenever(mockResourcesCache.get(fakeResourceKey)).thenReturn(fakeCacheData)
117123

118124
// When
119-
val result = testedCachesManager.getFromResourceCache(mockDrawable)
125+
val result = testedCachesManager.getFromResourceCache(fakeResourceKey)
120126

121127
// Then
122128
assertThat(result).isEqualTo(fakeResourceId)
@@ -125,10 +131,10 @@ internal class BitmapCachesManagerTest {
125131
@Test
126132
fun `M get null from resource cache W getFromResourceCache { resource not in cache }`() {
127133
// When
128-
val result = testedCachesManager.getFromResourceCache(mockDrawable)
134+
val result = testedCachesManager.getFromResourceCache(fakeResourceKey)
129135

130136
// Then
131-
verify(mockResourcesCache).get(mockDrawable)
137+
verify(mockResourcesCache).get(fakeResourceKey)
132138
assertThat(result).isNull()
133139
}
134140

‎features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/ResourceResolverTest.kt

+16-12
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import android.content.res.Resources
1111
import android.graphics.Bitmap
1212
import android.graphics.drawable.BitmapDrawable
1313
import android.graphics.drawable.Drawable
14-
import android.graphics.drawable.Drawable.ConstantState
1514
import android.graphics.drawable.LayerDrawable
1615
import android.graphics.drawable.StateListDrawable
1716
import android.util.DisplayMetrics
@@ -108,16 +107,16 @@ internal class ResourceResolverTest {
108107
@Mock
109108
lateinit var mockResources: Resources
110109

111-
@Mock
112-
lateinit var mockBitmapConstantState: ConstantState
113-
114110
private var fakeBitmapWidth: Int = 1
115111

116112
private var fakeBitmapHeight: Int = 1
117113

118114
@Forgery
119115
lateinit var fakeApplicationid: UUID
120116

117+
@StringForgery
118+
lateinit var fakeResourceKey: String
119+
121120
@StringForgery
122121
lateinit var fakeResourceId: String
123122

@@ -128,6 +127,7 @@ internal class ResourceResolverTest {
128127
whenever(mockDrawableCopier.copy(eq(mockBitmapDrawable), any())).thenReturn(
129128
mockBitmapDrawable
130129
)
130+
whenever(mockBitmapCachesManager.generateResourceKeyFromDrawable(mockDrawable)).thenReturn(fakeResourceKey)
131131
whenever(mockDrawableCopier.copy(eq(mockDrawable), any())).thenReturn(mockDrawable)
132132
fakeImageCompressionByteArray = forge.aString().toByteArray()
133133

@@ -175,7 +175,7 @@ internal class ResourceResolverTest {
175175
@Test
176176
fun `M get data from cache W resolveResourceId() { cache hit with resourceId }`() {
177177
// Given
178-
whenever(mockBitmapCachesManager.getFromResourceCache(mockDrawable)).thenReturn(fakeResourceId)
178+
whenever(mockBitmapCachesManager.getFromResourceCache(fakeResourceKey)).thenReturn(fakeResourceId)
179179

180180
whenever(mockWebPImageCompression.compressBitmap(any()))
181181
.thenReturn(fakeImageCompressionByteArray)
@@ -210,7 +210,7 @@ internal class ResourceResolverTest {
210210

211211
val emptyByteArray = ByteArray(0)
212212

213-
whenever(mockBitmapCachesManager.getFromResourceCache(mockBitmapDrawable))
213+
whenever(mockBitmapCachesManager.getFromResourceCache(fakeResourceKey))
214214
.thenReturn(null)
215215

216216
whenever(mockWebPImageCompression.compressBitmap(any()))
@@ -321,7 +321,7 @@ internal class ResourceResolverTest {
321321
@Test
322322
fun `M calculate resourceId W resolveResourceId() { cache miss }`() {
323323
// Given
324-
whenever(mockResourcesLRUCache.get(mockDrawable)).thenReturn(null)
324+
whenever(mockResourcesLRUCache.get(fakeResourceKey)).thenReturn(null)
325325

326326
// When
327327
testedResourceResolver.resolveResourceId(
@@ -351,7 +351,7 @@ internal class ResourceResolverTest {
351351
@Test
352352
fun `M return failure W resolveResourceId { createBitmapOfApproxSizeFromDrawable failed }`() {
353353
// Given
354-
whenever(mockResourcesLRUCache.get(mockDrawable)).thenReturn(null)
354+
whenever(mockResourcesLRUCache.get(fakeResourceKey)).thenReturn(null)
355355
whenever(
356356
mockDrawableUtils.createBitmapOfApproxSizeFromDrawable(
357357
resources = any(),
@@ -670,7 +670,7 @@ internal class ResourceResolverTest {
670670
@Test
671671
fun `M cache bitmap W resolveResourceId() { from BitmapDrawable with null bitmap }`() {
672672
// Given
673-
whenever(mockBitmapCachesManager.getFromResourceCache(mockBitmapDrawable))
673+
whenever(mockBitmapCachesManager.getFromResourceCache(fakeResourceKey))
674674
.thenReturn(null)
675675
whenever(mockBitmapDrawable.bitmap).thenReturn(null)
676676

@@ -718,12 +718,16 @@ internal class ResourceResolverTest {
718718
@Mock mockFirstDrawable: Drawable,
719719
@Mock mockSecondDrawable: Drawable,
720720
@StringForgery fakeFirstResourceId: String,
721-
@StringForgery fakeSecondResourceId: String
721+
@StringForgery fakeSecondResourceId: String,
722+
@StringForgery fakeFirstKey: String,
723+
@StringForgery fakeSecondKey: String
722724
) {
723725
// Given
724-
whenever(mockBitmapCachesManager.getFromResourceCache(mockFirstDrawable))
726+
whenever(mockBitmapCachesManager.generateResourceKeyFromDrawable(mockFirstDrawable)).thenReturn(fakeFirstKey)
727+
whenever(mockBitmapCachesManager.generateResourceKeyFromDrawable(mockSecondDrawable)).thenReturn(fakeSecondKey)
728+
whenever(mockBitmapCachesManager.getFromResourceCache(fakeFirstKey))
725729
.thenReturn(fakeFirstResourceId)
726-
whenever(mockBitmapCachesManager.getFromResourceCache(mockSecondDrawable))
730+
whenever(mockBitmapCachesManager.getFromResourceCache(fakeSecondKey))
727731
.thenReturn(fakeSecondResourceId)
728732

729733
val countDownLatch = CountDownLatch(2)

‎features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/ResourcesLRUCacheTest.kt

+12-24
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ internal class ResourcesLRUCacheTest {
4747
@Mock
4848
lateinit var mockInvocationUtils: InvocationUtils
4949

50+
@StringForgery
51+
lateinit var fakeResourceKey: String
52+
5053
val argumentCaptor = argumentCaptor<String>()
5154

5255
@BeforeEach
@@ -61,7 +64,7 @@ internal class ResourcesLRUCacheTest {
6164
@Test
6265
fun `M return null W get() { item not in cache }`() {
6366
// When
64-
val cacheItem = testedCache.get(mockDrawable)
67+
val cacheItem = testedCache.get(fakeResourceKey)
6568

6669
// Then
6770
assertThat(cacheItem).isNull()
@@ -73,57 +76,47 @@ internal class ResourcesLRUCacheTest {
7376
) {
7477
// Given
7578
val fakeResourceIdByteArray = fakeResourceId.toByteArray(Charsets.UTF_8)
76-
testedCache.put(mockDrawable, fakeResourceIdByteArray)
79+
testedCache.put(fakeResourceKey, fakeResourceIdByteArray)
7780

7881
// When
79-
val cacheItem = testedCache.get(mockDrawable)
82+
val cacheItem = testedCache.get(fakeResourceKey)
8083

8184
// Then
8285
assertThat(cacheItem).isEqualTo(fakeResourceIdByteArray)
8386
}
8487

8588
@Test
86-
fun `M not generate prefix W put() { animationDrawable }`(
87-
@StringForgery fakeResourceId: String
88-
) {
89+
fun `M not generate prefix W put() { animationDrawable }`() {
8990
// Given
90-
val fakeResourceIdByteArray = fakeResourceId.toByteArray(Charsets.UTF_8)
9191
val mockAnimationDrawable: AnimationDrawable = mock()
9292

9393
// When
94-
testedCache.put(mockAnimationDrawable, fakeResourceIdByteArray)
94+
val key = testedCache.generateKeyFromDrawable(mockAnimationDrawable)
9595

9696
// Then
97-
val key = testedCache.generateKey(mockAnimationDrawable)
9897
assertThat(key).doesNotContain("-")
9998
}
10099

101100
@Test
102101
fun `M generate key prefix with state W put() { drawableContainer }`(
103-
@StringForgery fakeResourceId: String,
104102
forge: Forge
105103
) {
106104
// Given
107-
val fakeResourceIdByteArray = fakeResourceId.toByteArray(Charsets.UTF_8)
108105
val mockStatelistDrawable: StateListDrawable = mock()
109106
val fakeStateArray = intArrayOf(forge.aPositiveInt())
110107
val expectedPrefix = fakeStateArray[0].toString() + "-"
111108
whenever(mockStatelistDrawable.state).thenReturn(fakeStateArray)
112109

113110
// When
114-
testedCache.put(mockStatelistDrawable, fakeResourceIdByteArray)
111+
val key = testedCache.generateKeyFromDrawable(mockStatelistDrawable)
115112

116113
// Then
117-
val key = testedCache.generateKey(mockStatelistDrawable)
118114
assertThat(key).startsWith(expectedPrefix)
119115
}
120116

121117
@Test
122-
fun `M generate key prefix with layer hash W put() { layerDrawable }`(
123-
@StringForgery fakeResourceId: String
124-
) {
118+
fun `M generate key prefix with layer hash W put() { layerDrawable }`() {
125119
// Given
126-
val fakeResourceIdByteArray = fakeResourceId.toByteArray(Charsets.UTF_8)
127120
val mockRippleDrawable: RippleDrawable = mock()
128121
val mockBackgroundLayer: Drawable = mock()
129122
val mockForegroundLayer: Drawable = mock()
@@ -134,36 +127,31 @@ internal class ResourcesLRUCacheTest {
134127
.thenReturn(mockForegroundLayer)
135128
whenever(mockRippleDrawable.numberOfLayers).thenReturn(2)
136129

137-
testedCache.put(mockRippleDrawable, fakeResourceIdByteArray)
138-
139130
val expectedPrefix = System.identityHashCode(mockBackgroundLayer).toString() + "-" +
140131
System.identityHashCode(mockForegroundLayer).toString() + "-"
141132
val expectedHash = System.identityHashCode(mockRippleDrawable).toString()
142133

143134
// When
144-
val key = testedCache.generateKey(mockRippleDrawable)
135+
val key = testedCache.generateKeyFromDrawable(mockRippleDrawable)
145136

146137
// Then
147138
assertThat(key).isEqualTo(expectedPrefix + expectedHash)
148139
}
149140

150141
@Test
151142
fun `M not generate key prefix W put() { layerDrawable with only one layer }`(
152-
@StringForgery fakeResourceId: String,
153143
@Mock mockRippleDrawable: RippleDrawable,
154144
@Mock mockBackgroundLayer: Drawable
155145
) {
156146
// Given
157-
val fakeResourceIdByteArray = fakeResourceId.toByteArray(Charsets.UTF_8)
158147
whenever(mockRippleDrawable.numberOfLayers).thenReturn(1)
159148
whenever(mockRippleDrawable.safeGetDrawable(0)).thenReturn(mockBackgroundLayer)
160-
testedCache.put(mockRippleDrawable, fakeResourceIdByteArray)
161149

162150
val expectedPrefix = System.identityHashCode(mockBackgroundLayer).toString() + "-"
163151
val drawableHash = System.identityHashCode(mockRippleDrawable).toString()
164152

165153
// When
166-
val key = testedCache.generateKey(mockRippleDrawable)
154+
val key = testedCache.generateKeyFromDrawable(mockRippleDrawable)
167155

168156
// Then
169157
assertThat(key).isEqualTo(expectedPrefix + drawableHash)

0 commit comments

Comments
 (0)
Please sign in to comment.