Skip to content

Commit

Permalink
Modify lrucache to use androidx
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Aug 23, 2023
1 parent 1d39248 commit c7b5c2f
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 56 deletions.
1 change: 1 addition & 0 deletions detekt_custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ datadog:
- "android.view.Window.Callback.onMenuItemSelected(kotlin.Int, android.view.MenuItem)"
- "android.view.View.getChildAt(kotlin.Int)"
- "android.view.View.hashCode()"
- "androidx.collection.LruCache.size()"
# endregion
# region Android Webview APIs
- "android.webkit.ConsoleMessage.MessageLevel.toLogLevel()"
Expand Down
5 changes: 0 additions & 5 deletions features/dd-sdk-android-session-replay/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ plugins {
id("com.github.ben-manes.versions")

// Tests
id("de.mobilej.unmock")
id("org.jetbrains.kotlinx.kover")

// Internal Generation
Expand Down Expand Up @@ -112,10 +111,6 @@ dependencies {
apply(from = "clone_session_replay_schema.gradle.kts")
apply(from = "generate_session_replay_models.gradle.kts")

unMock {
keep("android.util.LruCache")
}

kotlinConfig(jvmBytecodeTarget = JvmTarget.JVM_11)
junitConfig()
javadocConfig()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,70 @@ import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.DrawableContainer
import android.graphics.drawable.LayerDrawable
import android.util.LruCache
import androidx.annotation.VisibleForTesting
import androidx.collection.LruCache
import com.datadog.android.sessionreplay.internal.utils.CacheUtils
import com.datadog.android.sessionreplay.internal.utils.InvocationUtils

internal class Base64LRUCache(
private val cacheUtils: CacheUtils<String, ByteArray> = CacheUtils(),
private val invocationUtils: InvocationUtils = InvocationUtils(),
private var cache: LruCache<String, ByteArray> =
object :
LruCache<String, ByteArray>(MAX_CACHE_MEMORY_SIZE_BYTES) {
override fun sizeOf(key: String?, value: ByteArray): Int {
override fun sizeOf(key: String, value: ByteArray): Int {
return value.size
}
}
) : Cache<Drawable, String>, ComponentCallbacks2 {

override fun onTrimMemory(level: Int) {
val cacheUtils = CacheUtils<String, ByteArray>()
cacheUtils.handleTrimMemory(level, cache)
}

override fun onConfigurationChanged(newConfig: Configuration) {}

override fun onLowMemory() {
cache.evictAll()
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
invocationUtils.safeCallWithErrorLogging(
call = { cache.evictAll() },
failureMessage = FAILURE_MSG_EVICT_CACHE_CONTENTS
)
}

@Synchronized
override fun put(element: Drawable, value: String) {
val key = generateKey(element)
val byteArray = value.toByteArray(Charsets.UTF_8)
cache.put(key, byteArray)

@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
invocationUtils.safeCallWithErrorLogging(
call = { cache.put(key, byteArray) },
failureMessage = FAILURE_MSG_PUT_CACHE
)
}

@Synchronized
override fun get(element: Drawable): String? =
cache.get(generateKey(element))?.let {
String(it)
}
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
invocationUtils.safeCallWithErrorLogging(
call = {
cache.get(generateKey(element))?.let {
String(it)
}
},
failureMessage = FAILURE_MSG_GET_CACHE
)

override fun size(): Int = cache.size()

@Synchronized
override fun clear() {
cache.evictAll()
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
invocationUtils.safeCallWithErrorLogging(
call = { cache.evictAll() },
failureMessage = FAILURE_MSG_EVICT_CACHE_CONTENTS
)
}

@VisibleForTesting
Expand Down Expand Up @@ -95,5 +116,9 @@ internal class Base64LRUCache(
internal companion object {
@Suppress("MagicNumber")
internal val MAX_CACHE_MEMORY_SIZE_BYTES = 4 * 1024 * 1024 // 4MB

private const val FAILURE_MSG_EVICT_CACHE_CONTENTS = "Failed to evict cache entries"
private const val FAILURE_MSG_PUT_CACHE = "Failed to put item in cache"
private const val FAILURE_MSG_GET_CACHE = "Failed to get item from cache"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,49 @@ import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Bitmap.Config
import android.os.Build
import android.util.LruCache
import androidx.annotation.VisibleForTesting
import androidx.collection.LruCache
import com.datadog.android.sessionreplay.internal.utils.CacheUtils
import java.util.concurrent.atomic.AtomicInteger

@Suppress("TooManyFunctions")
internal class BitmapPool(
private val bitmapPoolHelper: BitmapPoolHelper = BitmapPoolHelper(),
private val cacheUtils: CacheUtils<String, Bitmap> = CacheUtils(),
@get:VisibleForTesting internal val bitmapsBySize: HashMap<String, HashSet<Bitmap>> = HashMap(),
@get:VisibleForTesting internal val usedBitmaps: HashSet<Bitmap> = HashSet(),
private var cache: LruCache<String, Bitmap> = object :
LruCache<String, Bitmap>(MAX_CACHE_MEMORY_SIZE_BYTES) {
override fun sizeOf(key: String?, bitmap: Bitmap): Int {
return bitmap.allocationByteCount
override fun sizeOf(key: String, value: Bitmap): Int {
return value.allocationByteCount
}

@Synchronized
override fun entryRemoved(
evicted: Boolean,
key: String?,
oldValue: Bitmap?,
key: String,
oldValue: Bitmap,
newValue: Bitmap?
) {
super.entryRemoved(evicted, key, oldValue, newValue)

if (oldValue != null) {
val dimensionsKey = bitmapPoolHelper.generateKey(oldValue)
val bitmapGroup = bitmapsBySize[dimensionsKey] ?: HashSet()
bitmapPoolHelper.safeCall {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
super.entryRemoved(evicted, key, oldValue, newValue)
}

bitmapPoolHelper.safeCall {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
bitmapGroup.remove(oldValue)
}
val dimensionsKey = bitmapPoolHelper.generateKey(oldValue)
val bitmapGroup = bitmapsBySize[dimensionsKey] ?: HashSet()

bitmapPoolHelper.safeCall {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
usedBitmaps.remove(oldValue)
}
bitmapPoolHelper.safeCall {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
bitmapGroup.remove(oldValue)
}

oldValue.recycle()
bitmapPoolHelper.safeCall {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
usedBitmaps.remove(oldValue)
}

oldValue.recycle()
}
}
) : Cache<String, Bitmap>, ComponentCallbacks2 {
Expand Down Expand Up @@ -82,7 +84,10 @@ internal class BitmapPool(

@Synchronized
override fun clear() {
cache.evictAll()
bitmapPoolHelper.safeCall {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
cache.evictAll()
}
}

@Synchronized
Expand Down Expand Up @@ -141,11 +146,13 @@ internal class BitmapPool(
override fun onConfigurationChanged(newConfig: Configuration) {}

override fun onLowMemory() {
cache.evictAll()
bitmapPoolHelper.safeCall {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
cache.evictAll()
}
}

override fun onTrimMemory(level: Int) {
val cacheUtils = CacheUtils<String, Bitmap>()
cacheUtils.handleTrimMemory(level, cache)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
package com.datadog.android.sessionreplay.internal.recorder.base64

import android.graphics.Bitmap
import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.internal.utils.InvocationUtils

internal class BitmapPoolHelper(
private val logger: InternalLogger = InternalLogger.UNBOUND,
private val invocationUtils: InvocationUtils = InvocationUtils()
) {
internal fun generateKey(bitmap: Bitmap) =
Expand All @@ -22,10 +20,7 @@ internal class BitmapPoolHelper(

internal fun<R> safeCall(call: () -> R): R? =
invocationUtils.safeCallWithErrorLogging(
logger = logger,
call = {
call()
},
call = { call() },
failureMessage = BITMAP_OPERATION_FAILED
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
package com.datadog.android.sessionreplay.internal.utils

import android.content.ComponentCallbacks2
import android.util.LruCache
import androidx.collection.LruCache

internal class CacheUtils<K : Any, V : Any> {
internal class CacheUtils<K : Any, V : Any>(
private val invocationUtils: InvocationUtils = InvocationUtils()
) {
internal fun handleTrimMemory(level: Int, cache: LruCache<K, V>) {
@Suppress("MagicNumber")
val onLowMemorySizeBytes = cache.maxSize() / 2 // 50%
Expand All @@ -19,34 +21,55 @@ internal class CacheUtils<K : Any, V : Any> {

when (level) {
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> {
cache.evictAll()
evictAll(cache)
}

ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
cache.evictAll()
evictAll(cache)
}

ComponentCallbacks2.TRIM_MEMORY_MODERATE -> {
cache.trimToSize(onModerateMemorySizeBytes)
trimToSize(cache, onModerateMemorySizeBytes)
}

ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
cache.evictAll()
evictAll(cache)
}

ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> {
cache.trimToSize(onLowMemorySizeBytes)
trimToSize(cache, onLowMemorySizeBytes)
}

ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> {
cache.trimToSize(onModerateMemorySizeBytes)
trimToSize(cache, onModerateMemorySizeBytes)
}

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {}

else -> {
cache.evictAll()
evictAll(cache)
}
}
}

private fun evictAll(cache: LruCache<K, V>) {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
invocationUtils.safeCallWithErrorLogging(
call = { cache.evictAll() },
failureMessage = FAILURE_MSG_EVICT_CACHE_CONTENTS
)
}

private fun trimToSize(cache: LruCache<K, V>, targetSize: Int) {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
invocationUtils.safeCallWithErrorLogging(
call = { cache.trimToSize(targetSize) },
failureMessage = FAILURE_MSG_TRIM_CACHE
)
}

private companion object {
private const val FAILURE_MSG_EVICT_CACHE_CONTENTS = "Failed to evict cache entries"
private const val FAILURE_MSG_TRIM_CACHE = "Failed to trim cache to size"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import com.datadog.android.api.InternalLogger
internal class InvocationUtils {
@Suppress("SwallowedException", "TooGenericExceptionCaught")
inline fun<R> safeCallWithErrorLogging(
logger: InternalLogger,
// Temporarily use UNBOUND until we handle the loggers
logger: InternalLogger = InternalLogger.UNBOUND,
call: () -> R,
failureMessage: String,
level: InternalLogger.Level = InternalLogger.Level.WARN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.StateListDrawable
import android.util.LruCache
import androidx.collection.LruCache
import com.datadog.android.sessionreplay.forge.ForgeConfigurator
import com.datadog.android.sessionreplay.internal.recorder.base64.Base64LRUCache.Companion.MAX_CACHE_MEMORY_SIZE_BYTES
import fr.xgouchet.elmyr.Forge
Expand Down
Loading

0 comments on commit c7b5c2f

Please sign in to comment.