diff --git a/CHANGELOG.md b/CHANGELOG.md index c90455ab48..f45bb5deeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,16 @@ - Disable `ScreenshotIntegration`, `WidgetsBindingIntegration` and `SentryWidget` in multi-view apps #2366 ([#2366](https://github.com/getsentry/sentry-dart/pull/2366)) +### Enhancements + +- Use `loadDebugImagesForAddresses` API for Android ([#2706](https://github.com/getsentry/sentry-dart/pull/2706)) + - This reduces the envelope size and data transferred across method channels + - If debug images received by `loadDebugImagesForAddresses` are empty, the SDK loads all debug images as fallback + ### Fixes - Reference to `SentryWidgetsFlutterBinding` in warning message in `FramesTrackingIntegration` ([#2704](https://github.com/getsentry/sentry-dart/pull/2704)) -- Replay video interuption if a `navigation` breadcrumb is missing `to` route info ([#2720](https://github.com/getsentry/sentry-dart/pull/2720)) +- Replay video interruption if a `navigation` breadcrumb is missing `to` route info ([#2720](https://github.com/getsentry/sentry-dart/pull/2720)) ### Deprecations diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 3634d151bd..3cd7a4bb1c 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -85,7 +85,7 @@ class SentryFlutterPlugin : when (call.method) { "initNativeSdk" -> initNativeSdk(call, result) "captureEnvelope" -> captureEnvelope(call, result) - "loadImageList" -> loadImageList(result) + "loadImageList" -> loadImageList(call, result) "closeNativeSdk" -> closeNativeSdk(result) "fetchNativeAppStart" -> fetchNativeAppStart(result) "beginNativeFrames" -> beginNativeFrames(result) @@ -483,31 +483,43 @@ class SentryFlutterPlugin : result.error("3", "Envelope is null or empty", null) } - private fun loadImageList(result: Result) { + private fun loadImageList( + call: MethodCall, + result: Result, + ) { val options = HubAdapter.getInstance().options as SentryAndroidOptions - val newDebugImages = mutableListOf>() - val debugImages: List? = options.debugImagesLoader.loadDebugImages() - - debugImages?.let { - it.forEach { image -> - val item = mutableMapOf() - - item["image_addr"] = image.imageAddr - item["image_size"] = image.imageSize - item["code_file"] = image.codeFile - item["type"] = image.type - item["debug_id"] = image.debugId - item["code_id"] = image.codeId - item["debug_file"] = image.debugFile - - newDebugImages.add(item) + val addresses = call.arguments() as List? ?: listOf() + val debugImages = + if (addresses.isEmpty()) { + options.debugImagesLoader + .loadDebugImages() + ?.toList() + .serialize() + } else { + options.debugImagesLoader + .loadDebugImagesForAddresses(addresses.toSet()) + ?.ifEmpty { options.debugImagesLoader.loadDebugImages() } + ?.toList() + .serialize() } - } - result.success(newDebugImages) + result.success(debugImages) } + private fun List?.serialize() = this?.map { it.serialize() } + + private fun DebugImage.serialize() = + mapOf( + "image_addr" to imageAddr, + "image_size" to imageSize, + "code_file" to codeFile, + "type" to type, + "debug_id" to debugId, + "code_id" to codeId, + "debug_file" to debugFile, + ) + private fun closeNativeSdk(result: Result) { HubAdapter.getInstance().close() framesTracker?.stop() diff --git a/flutter/example/integration_test/integration_test.dart b/flutter/example/integration_test/integration_test.dart index 6fd6916b7d..8259d02cac 100644 --- a/flutter/example/integration_test/integration_test.dart +++ b/flutter/example/integration_test/integration_test.dart @@ -167,6 +167,37 @@ void main() { await transaction.finish(); }); + testWidgets('setup sentry and test loading debug image', (tester) async { + await restoreFlutterOnErrorAfter(() async { + await setupSentryAndApp(tester); + }); + + // By default it should load all debug images + final allDebugImages = await SentryFlutter.native + ?.loadDebugImages(SentryStackTrace(frames: const [])); + // Typically loading all images results in a larger numbers + expect(allDebugImages!.length > 100, isTrue); + + // We can take any other random image for testing + final expectedImage = allDebugImages.first; + expect(expectedImage.imageAddr, isNotNull); + final imageAddr = + int.parse(expectedImage.imageAddr!.replaceAll('0x', ''), radix: 16); + + // Use the base image address and increase by offset + // so the instructionAddress will be within the range of the image address + final imageOffset = (expectedImage.imageSize! / 2).toInt(); + final instructionAddr = '0x${(imageAddr + imageOffset).toRadixString(16)}'; + final sentryFrame = SentryStackFrame(instructionAddr: instructionAddr); + + final debugImageByStacktrace = await SentryFlutter.native + ?.loadDebugImages(SentryStackTrace(frames: [sentryFrame])); + expect(debugImageByStacktrace!.length, 1); + expect(debugImageByStacktrace.first.imageAddr, isNotNull); + expect(debugImageByStacktrace.first.imageAddr, isNotEmpty); + expect(debugImageByStacktrace.first.imageAddr, expectedImage.imageAddr); + }); + group('e2e', () { var output = find.byKey(const Key('output')); late Fixture fixture;