@@ -29,6 +29,7 @@ import android.view.Surface
2929import android.view.View
3030import androidx.annotation.RequiresApi
3131import io.sentry.SentryLevel
32+ import io.sentry.SentryLevel.DEBUG
3233import io.sentry.SentryOptions
3334import io.sentry.android.replay.ExecutorProvider
3435import io.sentry.android.replay.ScreenshotRecorderCallback
@@ -71,15 +72,14 @@ internal class CanvasStrategy(
7172 @SuppressLint(" NewApi" )
7273 private val pictureRenderTask = Runnable {
7374 if (isClosed.get()) {
74- options.logger.log(
75- SentryLevel .DEBUG ,
76- " Canvas Strategy already closed, skipping picture render" ,
77- )
75+ options.logger.log(DEBUG , " Canvas Strategy already closed, skipping picture render" )
7876 return @Runnable
7977 }
8078 val picture = unprocessedPictureRef.getAndSet(null ) ? : return @Runnable
8179
8280 try {
81+ // It's safe to access the surface because the render task,
82+ // as well as surface release are executed on the same single threaded executor
8383 // Draw picture to the Surface for PixelCopy
8484 val surfaceCanvas = surface.lockHardwareCanvas()
8585 try {
@@ -96,30 +96,45 @@ internal class CanvasStrategy(
9696 }
9797 }
9898 }
99-
100- // Trigger PixelCopy capture
10199 PixelCopy .request(
102100 surface,
103101 screenshot!! ,
104102 { result ->
105- if (result == PixelCopy .SUCCESS ) {
106- lastCaptureSuccessful.set(true )
107- val bitmap = screenshot
108- if (bitmap != null && ! bitmap.isRecycled) {
109- screenshotRecorderCallback?.onScreenshotRecorded(bitmap)
110- }
111- } else {
112- options.logger.log(
113- SentryLevel .ERROR ,
114- " Canvas Strategy: PixelCopy failed with code $result " ,
115- )
116- lastCaptureSuccessful.set(false )
103+ if (isClosed.get()) {
104+ options.logger.log(DEBUG , " CanvasStrategy is closed, ignoring capture result" )
105+ return @request
117106 }
107+ executor
108+ .getExecutor()
109+ .submit(
110+ ReplayRunnable (" screenshot_recorder.mask" ) {
111+ if (isClosed.get()) {
112+ options.logger.log(DEBUG , " CanvasStrategy is closed, ignoring capture result" )
113+ return @ReplayRunnable
114+ }
115+ if (result == PixelCopy .SUCCESS ) {
116+ lastCaptureSuccessful.set(true )
117+ val bitmap = screenshot
118+ if (bitmap != null ) {
119+ synchronized(bitmap) {
120+ if (! bitmap.isRecycled)
121+ screenshotRecorderCallback?.onScreenshotRecorded(bitmap)
122+ }
123+ }
124+ } else {
125+ options.logger.log(
126+ SentryLevel .ERROR ,
127+ " Canvas Strategy: PixelCopy failed with code $result " ,
128+ )
129+ lastCaptureSuccessful.set(false )
130+ }
131+ }
132+ )
118133 },
119- executor.getBackgroundHandler() ,
134+ executor.getMainLooperHandler().handler ,
120135 )
121136 } catch (t: Throwable ) {
122- options.logger.log(SentryLevel .ERROR , " Canvas Strategy: picture render failed" )
137+ options.logger.log(SentryLevel .ERROR , " Canvas Strategy: picture render failed" , t )
123138 lastCaptureSuccessful.set(false )
124139 }
125140 }
@@ -139,10 +154,7 @@ internal class CanvasStrategy(
139154
140155 if (! isClosed.get()) {
141156 unprocessedPictureRef.set(picture)
142- // use the same handler for PixelCopy and pictureRenderTask
143- executor
144- .getBackgroundHandler()
145- .post(ReplayRunnable (" screenshot_recorder.canvas" , pictureRenderTask))
157+ executor.getExecutor().submit(ReplayRunnable (" screenshot_recorder.canvas" , pictureRenderTask))
146158 }
147159 }
148160
0 commit comments