diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ec56c00c..32cf654261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Fix potential ANRs due to NDK System.loadLibrary calls ([#3670](https://github.com/getsentry/sentry-java/pull/3670)) - Fix slow `Log` calls on app startup ([#3793](https://github.com/getsentry/sentry-java/pull/3793)) - Fix slow Integration name parsing ([#3794](https://github.com/getsentry/sentry-java/pull/3794)) +- Session Replay: Reduce startup and capture overhead ([#3799](https://github.com/getsentry/sentry-java/pull/3799)) ## 7.15.0 diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt index c95b72088a..1a6f3fed37 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/DefaultReplayBreadcrumbConverter.kt @@ -12,14 +12,16 @@ import kotlin.LazyThreadSafetyMode.NONE public open class DefaultReplayBreadcrumbConverter : ReplayBreadcrumbConverter { internal companion object { private val snakecasePattern by lazy(NONE) { "_[a-z]".toRegex() } - private val supportedNetworkData = setOf( - "status_code", - "method", - "response_content_length", - "request_content_length", - "http.response_content_length", - "http.request_content_length" - ) + private val supportedNetworkData by lazy(NONE) { + setOf( + "status_code", + "method", + "response_content_length", + "request_content_length", + "http.response_content_length", + "http.request_content_length" + ) + } } private var lastConnectivityState: String? = null diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt index 8f823fa17c..54f92a8958 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt @@ -36,6 +36,7 @@ import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference +import kotlin.LazyThreadSafetyMode.NONE import kotlin.math.roundToInt @TargetApi(26) @@ -51,15 +52,19 @@ internal class ScreenshotRecorder( } private var rootView: WeakReference? = null private val pendingViewHierarchy = AtomicReference() - private val maskingPaint = Paint() - private val singlePixelBitmap: Bitmap = Bitmap.createBitmap( - 1, - 1, - Bitmap.Config.ARGB_8888 - ) - private val singlePixelBitmapCanvas: Canvas = Canvas(singlePixelBitmap) - private val prescaledMatrix = Matrix().apply { - preScale(config.scaleFactorX, config.scaleFactorY) + private val maskingPaint by lazy(NONE) { Paint() } + private val singlePixelBitmap: Bitmap by lazy(NONE) { + Bitmap.createBitmap( + 1, + 1, + Bitmap.Config.ARGB_8888 + ) + } + private val singlePixelBitmapCanvas: Canvas by lazy(NONE) { Canvas(singlePixelBitmap) } + private val prescaledMatrix by lazy(NONE) { + Matrix().apply { + preScale(config.scaleFactorX, config.scaleFactorY) + } } private val contentChanged = AtomicBoolean(false) private val isCapturing = AtomicBoolean(true) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt index fcd2d11293..4b37eafc78 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt @@ -209,10 +209,6 @@ internal abstract class BaseCaptureStrategy( } } - init { - runInBackground { onChange(propertyName, initialValue, initialValue) } - } - override fun getValue(thisRef: Any?, property: KProperty<*>): T? = value.get() override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt index 888528f769..5640fbc96f 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.graphics.isUnspecified import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.findRootCoordinates -import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.node.LayoutNode import androidx.compose.ui.node.Owner import androidx.compose.ui.semantics.SemanticsActions @@ -87,12 +86,13 @@ internal object ComposeViewHierarchyNode { (semantics == null || !semantics.contains(SemanticsProperties.InvisibleToUser)) && visibleRect.height() > 0 && visibleRect.width() > 0 val isEditable = semantics?.contains(SemanticsActions.SetText) == true - val positionInWindow = node.coordinates.positionInWindow() return when { semantics?.contains(SemanticsProperties.Text) == true || isEditable -> { val shouldMask = isVisible && node.shouldMask(isImage = false, options) parent?.setImportantForCaptureToAncestors(true) + // TODO: if we get reports that it's slow, we can drop this, and just mask + // TODO: the whole view instead of per-line val textLayoutResults = mutableListOf() semantics?.getOrNull(SemanticsActions.GetTextLayoutResult) ?.action @@ -108,8 +108,8 @@ internal object ComposeViewHierarchyNode { TextViewHierarchyNode( layout = if (textLayoutResults.isNotEmpty() && !isEditable) ComposeTextLayout(textLayoutResults.first(), hasFillModifier) else null, dominantColor = textColor?.toArgb()?.toOpaque(), - x = positionInWindow.x, - y = positionInWindow.y, + x = visibleRect.left.toFloat(), + y = visibleRect.top.toFloat(), width = node.width, height = node.height, elevation = (parent?.elevation ?: 0f), @@ -128,8 +128,8 @@ internal object ComposeViewHierarchyNode { parent?.setImportantForCaptureToAncestors(true) ImageViewHierarchyNode( - x = positionInWindow.x, - y = positionInWindow.y, + x = visibleRect.left.toFloat(), + y = visibleRect.top.toFloat(), width = node.width, height = node.height, elevation = (parent?.elevation ?: 0f), @@ -147,8 +147,8 @@ internal object ComposeViewHierarchyNode { // TODO: traverse the ViewHierarchyNode here again. For now we can recommend // TODO: using custom modifiers to obscure the entire node if it's sensitive GenericViewHierarchyNode( - x = positionInWindow.x, - y = positionInWindow.y, + x = visibleRect.left.toFloat(), + y = visibleRect.top.toFloat(), width = node.width, height = node.height, elevation = (parent?.elevation ?: 0f), diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt index ef05ecb029..03cb37ad3e 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt @@ -239,8 +239,8 @@ sealed class ViewHierarchyNode( private fun Class<*>.isAssignableFrom(set: Set): Boolean { var cls: Class<*>? = this while (cls != null) { - val canonicalName = cls.canonicalName - if (canonicalName != null && set.contains(canonicalName)) { + val canonicalName = cls.name + if (set.contains(canonicalName)) { return true } cls = cls.superclass