Skip to content

Commit

Permalink
Refactor singleton caches to class instances
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Jul 31, 2023
1 parent c0a9969 commit f58d3ee
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 205 deletions.
2 changes: 1 addition & 1 deletion features/dd-sdk-android-session-replay/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class com.datadog.android.sessionreplay.internal.recorder.base64.WebPImageCompre
override fun compressBitmap(android.graphics.Bitmap): ByteArray
companion object
abstract class com.datadog.android.sessionreplay.internal.recorder.mapper.BaseWireframeMapper<T: android.view.View, S: com.datadog.android.sessionreplay.model.MobileSegment.Wireframe> : WireframeMapper<T, S>, com.datadog.android.sessionreplay.internal.AsyncImageProcessingCallback
constructor(com.datadog.android.sessionreplay.utils.StringUtils = StringUtils, com.datadog.android.sessionreplay.utils.ViewUtils = ViewUtils, com.datadog.android.sessionreplay.internal.recorder.base64.ImageCompression = WebPImageCompression(), com.datadog.android.sessionreplay.utils.UniqueIdentifierGenerator = UniqueIdentifierGenerator, com.datadog.android.sessionreplay.internal.recorder.base64.Base64Serializer = Base64Serializer.Builder().build())
constructor(com.datadog.android.sessionreplay.utils.StringUtils = StringUtils, com.datadog.android.sessionreplay.utils.ViewUtils = ViewUtils)
protected fun resolveViewId(android.view.View): Long
protected fun colorAndAlphaAsStringHexa(Int, Int): String
protected fun resolveViewGlobalBounds(android.view.View, Float): com.datadog.android.sessionreplay.internal.recorder.GlobalBounds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public final class com/datadog/android/sessionreplay/internal/recorder/SystemInf
}

public final class com/datadog/android/sessionreplay/internal/recorder/base64/Base64Serializer {
public synthetic fun <init> (Ljava/util/concurrent/ExecutorService;Lcom/datadog/android/sessionreplay/internal/utils/DrawableUtils;Lcom/datadog/android/sessionreplay/internal/utils/Base64Utils;Lcom/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression;Lcom/datadog/android/sessionreplay/internal/recorder/base64/Cache;Lcom/datadog/android/sessionreplay/internal/recorder/base64/Cache;Lcom/datadog/android/api/InternalLogger;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/util/concurrent/ExecutorService;Lcom/datadog/android/sessionreplay/internal/utils/DrawableUtils;Lcom/datadog/android/sessionreplay/internal/utils/Base64Utils;Lcom/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression;Lcom/datadog/android/sessionreplay/internal/recorder/base64/Cache;Lcom/datadog/android/sessionreplay/internal/recorder/base64/BitmapPool;Lcom/datadog/android/api/InternalLogger;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public abstract interface class com/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression {
Expand All @@ -108,12 +108,12 @@ public final class com/datadog/android/sessionreplay/internal/recorder/base64/We
public abstract class com/datadog/android/sessionreplay/internal/recorder/mapper/BaseWireframeMapper : com/datadog/android/sessionreplay/internal/AsyncImageProcessingCallback, com/datadog/android/sessionreplay/internal/recorder/mapper/WireframeMapper {
public static final field Companion Lcom/datadog/android/sessionreplay/internal/recorder/mapper/BaseWireframeMapper$Companion;
public fun <init> ()V
public fun <init> (Lcom/datadog/android/sessionreplay/utils/StringUtils;Lcom/datadog/android/sessionreplay/utils/ViewUtils;Lcom/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression;Lcom/datadog/android/sessionreplay/utils/UniqueIdentifierGenerator;Lcom/datadog/android/sessionreplay/internal/recorder/base64/Base64Serializer;)V
public synthetic fun <init> (Lcom/datadog/android/sessionreplay/utils/StringUtils;Lcom/datadog/android/sessionreplay/utils/ViewUtils;Lcom/datadog/android/sessionreplay/internal/recorder/base64/ImageCompression;Lcom/datadog/android/sessionreplay/utils/UniqueIdentifierGenerator;Lcom/datadog/android/sessionreplay/internal/recorder/base64/Base64Serializer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lcom/datadog/android/sessionreplay/utils/StringUtils;Lcom/datadog/android/sessionreplay/utils/ViewUtils;)V
public synthetic fun <init> (Lcom/datadog/android/sessionreplay/utils/StringUtils;Lcom/datadog/android/sessionreplay/utils/ViewUtils;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
protected final fun colorAndAlphaAsStringHexa (II)Ljava/lang/String;
public fun finishProcessingImage ()V
protected final fun getWebPMimeType ()Ljava/lang/String;
protected final fun handleBitmap (Landroid/content/Context;Landroid/util/DisplayMetrics;Landroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe$ImageWireframe;)V
protected final fun handleBitmap (Landroid/content/Context;Landroid/util/DisplayMetrics;Landroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe$ImageWireframe;)Lkotlin/Unit;
protected final fun resolveChildDrawableUniqueIdentifier (Landroid/view/View;)Ljava/lang/Long;
protected final fun resolveShapeStyleAndBorder (Landroid/graphics/drawable/Drawable;F)Lkotlin/Pair;
protected final fun resolveViewGlobalBounds (Landroid/view/View;F)Lcom/datadog/android/sessionreplay/internal/recorder/GlobalBounds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import android.widget.SeekBar
import android.widget.TextView
import android.widget.Toolbar
import androidx.appcompat.widget.SwitchCompat
import com.datadog.android.sessionreplay.internal.recorder.base64.Base64LRUCache
import com.datadog.android.sessionreplay.internal.recorder.base64.Base64Serializer
import com.datadog.android.sessionreplay.internal.recorder.base64.BitmapPool
import com.datadog.android.sessionreplay.internal.recorder.mapper.BasePickerMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.ButtonMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckBoxMapper
Expand Down Expand Up @@ -77,9 +80,11 @@ enum class SessionReplayPrivacy {

@Suppress("LongMethod")
internal fun mappers(): List<MapperTypeWrapper> {
val base64Serializer = buildBase64Serializer()

val viewWireframeMapper = ViewWireframeMapper()
val unsupportedViewMapper = UnsupportedViewMapper()
val imageButtonMapper = ImageButtonMapper()
val imageButtonMapper = ImageButtonMapper(base64Serializer = base64Serializer)
val imageMapper: ViewScreenshotWireframeMapper
val textMapper: TextViewMapper
val buttonMapper: ButtonMapper
Expand Down Expand Up @@ -161,6 +166,12 @@ enum class SessionReplayPrivacy {
return mappersList
}

private fun buildBase64Serializer(): Base64Serializer {
val bitmapPool = BitmapPool()
val base64LRUCache = Base64LRUCache()
return Base64Serializer.Builder().build(bitmapPool = bitmapPool, base64LRUCache = base64LRUCache)
}

private fun getMaskSeekBarMapper(): MaskSeekBarWireframeMapper? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
MaskSeekBarWireframeMapper()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ import android.util.LruCache
import androidx.annotation.VisibleForTesting
import com.datadog.android.sessionreplay.internal.utils.CacheUtils

internal object Base64LRUCache : Cache<Drawable, String>, ComponentCallbacks2 {
@Suppress("MagicNumber")
private val MAX_CACHE_MEMORY_SIZE_BYTES = 4 * 1024 * 1024 // 4MB

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

init {
if (cache == null) {
cache = object :
LruCache<String, ByteArray>(MAX_CACHE_MEMORY_SIZE_BYTES) {
override fun sizeOf(key: String?, value: ByteArray): Int {
return value.size
}
}
}
}

Expand All @@ -35,36 +39,31 @@ internal object Base64LRUCache : Cache<Drawable, String>, ComponentCallbacks2 {
override fun onConfigurationChanged(newConfig: Configuration) {}

override fun onLowMemory() {
cache.evictAll()
}

@VisibleForTesting
internal fun setBackingCache(cache: LruCache<String, ByteArray>) {
this.cache = cache
cache?.evictAll()
}

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

@Synchronized
override fun get(element: Drawable): String? =
cache.get(generateKey(element))?.let {
cache?.get(generateKey(element))?.let {
String(it)
}

@Synchronized
override fun size(): Int = cache.size()
override fun size(): Int = cache?.size() ?: 0

@Synchronized
override fun clear() {
cache.evictAll()
cache?.evictAll()
}

private fun generateKey(drawable: Drawable): String =
@VisibleForTesting
internal fun generateKey(drawable: Drawable): String =
generatePrefix(drawable) + System.identityHashCode(drawable)

private fun generatePrefix(drawable: Drawable): String {
Expand Down Expand Up @@ -97,4 +96,9 @@ internal object Base64LRUCache : Cache<Drawable, String>, ComponentCallbacks2 {
""
}
}

internal companion object {
@Suppress("MagicNumber")
internal val MAX_CACHE_MEMORY_SIZE_BYTES = 4 * 1024 * 1024 // 4MB
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class Base64Serializer private constructor(
private val drawableUtils: DrawableUtils,
private val base64Utils: Base64Utils,
private val webPImageCompression: ImageCompression,
private val base64LruCache: Cache<Drawable, String>,
private val bitmapPool: Cache<String, Bitmap>,
private val base64LRUCache: Cache<Drawable, String>?,
private val bitmapPool: BitmapPool?,
private val logger: InternalLogger
) {
private var asyncImageProcessingCallback: AsyncImageProcessingCallback? = null
Expand All @@ -51,7 +51,7 @@ class Base64Serializer private constructor(

asyncImageProcessingCallback?.startProcessingImage()

val cachedBase64 = base64LruCache.get(drawable)
val cachedBase64 = base64LRUCache?.get(drawable)
if (cachedBase64 != null) {
finalizeRecordedDataItem(cachedBase64, imageWireframe, asyncImageProcessingCallback)
return
Expand Down Expand Up @@ -106,8 +106,8 @@ class Base64Serializer private constructor(
private fun registerCacheForCallbacks(applicationContext: Context) {
if (isCacheRegisteredForCallbacks) return

if (base64LruCache is ComponentCallbacks2) {
applicationContext.registerComponentCallbacks(base64LruCache)
if (base64LRUCache is ComponentCallbacks2) {
applicationContext.registerComponentCallbacks(base64LRUCache)
isCacheRegisteredForCallbacks = true
} else {
// Temporarily use UNBOUND logger
Expand All @@ -130,10 +130,10 @@ class Base64Serializer private constructor(

if (base64Result.isNotEmpty()) {
// if we got a base64 string then cache it
base64LruCache.put(drawable, base64Result)
base64LRUCache?.put(drawable, base64Result)
}

bitmapPool.put(bitmap)
bitmapPool?.put(bitmap)

return base64Result
}
Expand Down Expand Up @@ -168,32 +168,31 @@ class Base64Serializer private constructor(
// region builder
internal class Builder {
internal fun build(
// Temporarily use UNBOUND until we handle the loggers
logger: InternalLogger = InternalLogger.UNBOUND,
threadPoolExecutor: ExecutorService = THREADPOOL_EXECUTOR,
drawableUtils: DrawableUtils = DrawableUtils(),
bitmapPool: BitmapPool? = null,
base64LRUCache: Cache<Drawable, String>? = null,
drawableUtils: DrawableUtils = DrawableUtils(bitmapPool = bitmapPool),
base64Utils: Base64Utils = Base64Utils(),
webPImageCompression: ImageCompression = WebPImageCompression(),
base64LruCache: Cache<Drawable, String> = Base64LRUCache,
bitmapPool: Cache<String, Bitmap> = BitmapPool,
// Temporarily use UNBOUND until we handle the loggers
logger: InternalLogger = InternalLogger.UNBOUND
webPImageCompression: ImageCompression = WebPImageCompression()
) =
Base64Serializer(
logger = logger,
threadPoolExecutor = threadPoolExecutor,
bitmapPool = bitmapPool,
base64LRUCache = base64LRUCache,
drawableUtils = drawableUtils,
base64Utils = base64Utils,
webPImageCompression = webPImageCompression,
base64LruCache = base64LruCache,
bitmapPool = bitmapPool,
logger = logger
webPImageCompression = webPImageCompression
)

private companion object {
private const val THREAD_POOL_MAX_KEEP_ALIVE_MS = 5000L
private const val CORE_DEFAULT_POOL_SIZE = 1
private const val MAX_THREAD_COUNT = 10

// all parameters are non-negative and queue is not null
@Suppress("UnsafeThirdPartyFunctionCall")
@Suppress("UnsafeThirdPartyFunctionCall") // all parameters are non-negative and queue is not null
private val THREADPOOL_EXECUTOR = ThreadPoolExecutor(
CORE_DEFAULT_POOL_SIZE,
MAX_THREAD_COUNT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,65 +19,51 @@ import com.datadog.android.sessionreplay.internal.utils.InvocationUtils
import java.util.concurrent.atomic.AtomicInteger

@Suppress("TooManyFunctions")
internal object BitmapPool : Cache<String, Bitmap>, ComponentCallbacks2 {
private const val BITMAP_OPERATION_FAILED = "operation failed for bitmap pool"

@VisibleForTesting
@Suppress("MagicNumber")
internal val MAX_CACHE_MEMORY_SIZE_BYTES = 4 * 1024 * 1024 // 4MB

private var bitmapsBySize = HashMap<String, HashSet<Bitmap>>()
private var usedBitmaps = HashSet<Bitmap>()
private val logger = InternalLogger.UNBOUND
private val invocationUtils = InvocationUtils()
private var bitmapIndex = AtomicInteger(0)

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 entryRemoved(
evicted: Boolean,
key: String?,
oldValue: Bitmap?,
newValue: Bitmap?
) {
super.entryRemoved(evicted, key, oldValue, newValue)

if (oldValue != null) {
val dimensionsKey = generateKey(oldValue)
val bitmapGroup = bitmapsBySize[dimensionsKey] ?: HashSet()

invocationUtils.safeCallWithErrorLogging(
logger = logger,
call = {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
bitmapGroup.remove(oldValue)
},
failureMessage = BITMAP_OPERATION_FAILED
)
markBitmapAsFree(oldValue)
oldValue.recycle()
internal class BitmapPool(
private val bitmapsBySize: HashMap<String, HashSet<Bitmap>> = HashMap(),
private val usedBitmaps: HashSet<Bitmap> = HashSet(),
private var cache: LruCache<String, Bitmap>? = null,
private val invocationUtils: InvocationUtils = InvocationUtils()
) : Cache<String, Bitmap>, ComponentCallbacks2 {

init {
if (cache == null) {
cache = object :
LruCache<String, Bitmap>(MAX_CACHE_MEMORY_SIZE_BYTES) {
override fun sizeOf(key: String?, bitmap: Bitmap): Int {
return bitmap.allocationByteCount
}

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

if (oldValue != null) {
val dimensionsKey = generateKey(oldValue)
val bitmapGroup = bitmapsBySize[dimensionsKey] ?: HashSet()

invocationUtils.safeCallWithErrorLogging(
logger = logger,
call = {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
bitmapGroup.remove(oldValue)
},
failureMessage = BITMAP_OPERATION_FAILED
)
markBitmapAsFree(oldValue)
oldValue.recycle()
}
}
}
}
}

@VisibleForTesting
internal fun setBitmapsBySize(bitmaps: HashMap<String, HashSet<Bitmap>>) {
this.bitmapsBySize = bitmaps
}

@VisibleForTesting
internal fun setBackingCache(cache: LruCache<String, Bitmap>) {
this.cache = cache
}

@VisibleForTesting
internal fun setUsedBitmaps(usedBitmaps: HashSet<Bitmap>) {
this.usedBitmaps = usedBitmaps
}
private val logger = InternalLogger.UNBOUND
private var bitmapIndex = AtomicInteger(0)

@Synchronized
override fun put(value: Bitmap) {
Expand All @@ -104,10 +90,12 @@ internal object BitmapPool : Cache<String, Bitmap>, ComponentCallbacks2 {
markBitmapAsFree(value)
}

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

@Synchronized
override fun clear() = cache.evictAll()
override fun clear() {
cache?.evictAll()
}

@Synchronized
override fun get(element: String): Bitmap? {
Expand Down Expand Up @@ -152,7 +140,7 @@ internal object BitmapPool : Cache<String, Bitmap>, ComponentCallbacks2 {
private fun addBitmapToPool(key: String, bitmap: Bitmap) {
val cacheIndex = bitmapIndex.incrementAndGet()
val cacheKey = "$key-$cacheIndex"
cache.put(cacheKey, bitmap)
cache?.put(cacheKey, bitmap)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
Expand Down Expand Up @@ -181,11 +169,19 @@ internal object BitmapPool : Cache<String, Bitmap>, ComponentCallbacks2 {
override fun onConfigurationChanged(newConfig: Configuration) {}

override fun onLowMemory() {
cache.evictAll()
cache?.evictAll()
}

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

internal companion object {
private const val BITMAP_OPERATION_FAILED = "operation failed for bitmap pool"

@VisibleForTesting
@Suppress("MagicNumber")
internal val MAX_CACHE_MEMORY_SIZE_BYTES = 4 * 1024 * 1024 // 4MB
}
}
Loading

0 comments on commit f58d3ee

Please sign in to comment.