Skip to content

Commit 4110ac1

Browse files
buenaflormarkushi
andauthored
Refactor loadContexts and loadDebugImages to use JNI and FFI (#3224)
* First draft * Pin Android SDK to 8.18.0 * Update * Update * Add assert * Update * Update * Remove method channels * Update sentry-native to 0.10.0 and regenerate bindings * Update * Update * Update * Update * Update impl * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Make functions nullable * Enable integration tests on iOS * Lint * Update CHANGELOG * Lint * Lint * Try to fix headers * Fix tests * Update * Add macos links * Use non-nulls * Update * Update * Update * Update * Update * Update * Update * Add test for utf8 decoding * Update * Update SentryFlutterFFI to SentryFlutterPlugin * Update macOS file links * Fix swiftlint * Update ffi-cocoa.yaml * Set applicationContext to null in onDetachedFromEngine * Build ios example without code sign * Update macOS file links * Update * Update ffi-cocoa.yaml --------- Co-authored-by: markushi <[email protected]>
1 parent fef6078 commit 4110ac1

File tree

18 files changed

+1234
-289
lines changed

18 files changed

+1234
-289
lines changed

.github/workflows/flutter_test.yml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ env:
2020
SENTRY_AUTH_TOKEN_E2E: ${{ secrets.SENTRY_AUTH_TOKEN_E2E }}
2121

2222
jobs:
23-
test-android:
23+
android:
2424
runs-on: ubuntu-latest
2525
timeout-minutes: 30
2626
defaults:
@@ -146,22 +146,29 @@ jobs:
146146
echo "name=${device}" >> "$GITHUB_OUTPUT"
147147
148148
- name: run integration test
149-
# Disable flutter integration tests for iOS for now (https://github.com/getsentry/sentry-dart/issues/1605#issuecomment-1695809346)
150-
if: ${{ matrix.target != 'ios' }}
151149
run: |
152150
flutter test -d "${{ steps.device.outputs.name }}" integration_test/all.dart --dart-define SENTRY_AUTH_TOKEN_E2E=$SENTRY_AUTH_TOKEN_E2E --verbose
151+
152+
# We can only run this in macos. iOS simulator doesn't support profile mode.
153+
- name: run integration test in profile mode
154+
if: ${{ matrix.target == 'macos' }}
155+
run: |
153156
flutter drive --driver=integration_test/test_driver/driver.dart --target=integration_test/sentry_widgets_flutter_binding_test.dart --profile -d "${{ steps.device.outputs.name }}"
154157
155158
- name: run native test
156159
# We only have the native unit test package in the iOS xcodeproj at the moment.
157160
# Should be OK because it will likely be removed after switching to FFI (see https://github.com/getsentry/sentry-dart/issues/1444).
158161
if: ${{ matrix.target != 'macos' }}
159162
working-directory: packages/flutter/example/${{ matrix.target }}
160-
run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=$DEVICE_PLATFORM" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO
163+
# For some reason running native unit tests directly after Flutter integration tests fails
164+
# running flutter build ios before works: https://stackoverflow.com/a/77487525/22813624
165+
run: |
166+
flutter build ios --no-codesign
167+
xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=$DEVICE_PLATFORM" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO
161168
env:
162169
DEVICE_PLATFORM: ${{ steps.device.outputs.platform }}
163170

164-
test-web:
171+
web:
165172
runs-on: ubuntu-latest
166173
timeout-minutes: 30
167174
defaults:

CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@
1818

1919
- Add `DioException` response data to error breadcrumb ([#3164](https://github.com/getsentry/sentry-dart/pull/3164))
2020
- Bumped `dio` min verion to `5.2.0`
21-
- Support additional request data types in `dio` package ([#3170](https://github.com/getsentry/sentry-dart/pull/3170))
22-
- Add support for `json`, `UInt8List`, `num`, `bool`, `FormData`, `Multipart` request data.
2321
- Use FFI/JNI for `captureEnvelope` on iOS and Android ([#3115](https://github.com/getsentry/sentry-dart/pull/3115))
2422
- Log a warning when dropping envelope items ([#3165](https://github.com/getsentry/sentry-dart/pull/3165))
2523
- Call options.log for structured logs ([#3187](https://github.com/getsentry/sentry-dart/pull/3187))
2624
- Remove async usage from `FlutterErrorIntegration` ([#3202](https://github.com/getsentry/sentry-dart/pull/3202))
2725
- Tag all spans during app start with start type info ([#3190](https://github.com/getsentry/sentry-dart/pull/3190))
28-
- Improve `SentryLogBatcher` flush logic ([#3211](https://github.com/getsentry/sentry-dart/pull/3187))
26+
- Refactor `loadContexts` and `loadDebugImages` to use JNI and FFI ([#3224](https://github.com/getsentry/sentry-dart/pull/3224))
2927

3028
### Dependencies
3129

packages/dart/lib/src/protocol/sentry_device.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ class SentryDevice {
214214
: null,
215215
processorCount: json['processor_count'],
216216
cpuDescription: json['cpu_description'],
217-
processorFrequency: json['processor_frequency'],
217+
processorFrequency: (json['processor_frequency'] is num)
218+
? (json['processor_frequency'] as num).toDouble()
219+
: null,
218220
deviceType: json['device_type'],
219221
batteryStatus: json['battery_status'],
220222
deviceUniqueIdentifier: json['device_unique_identifier'],

packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import io.sentry.protocol.DebugImage
2929
import io.sentry.protocol.SentryId
3030
import io.sentry.protocol.User
3131
import io.sentry.transport.CurrentDateProvider
32+
import org.json.JSONObject
33+
import org.json.JSONArray
3234
import java.lang.ref.WeakReference
3335
import kotlin.math.roundToInt
3436

@@ -50,6 +52,7 @@ class SentryFlutterPlugin :
5052
pluginRegistrationTime = System.currentTimeMillis()
5153

5254
context = flutterPluginBinding.applicationContext
55+
applicationContext = context
5356
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter")
5457
channel.setMethodCallHandler(this)
5558

@@ -63,7 +66,6 @@ class SentryFlutterPlugin :
6366
) {
6467
when (call.method) {
6568
"initNativeSdk" -> initNativeSdk(call, result)
66-
"loadImageList" -> loadImageList(call, result)
6769
"closeNativeSdk" -> closeNativeSdk(result)
6870
"fetchNativeAppStart" -> fetchNativeAppStart(result)
6971
"setContexts" -> setContexts(call.argument("key"), call.argument("value"), result)
@@ -75,7 +77,6 @@ class SentryFlutterPlugin :
7577
"removeExtra" -> removeExtra(call.argument("key"), result)
7678
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
7779
"removeTag" -> removeTag(call.argument("key"), result)
78-
"loadContexts" -> loadContexts(result)
7980
"displayRefreshRate" -> displayRefreshRate(result)
8081
"nativeCrash" -> crash()
8182
"setReplayConfig" -> setReplayConfig(call, result)
@@ -90,6 +91,7 @@ class SentryFlutterPlugin :
9091
}
9192

9293
channel.setMethodCallHandler(null)
94+
applicationContext = null
9395
}
9496

9597
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
@@ -367,42 +369,6 @@ class SentryFlutterPlugin :
367369

368370
result.success("")
369371
}
370-
private fun loadImageList(
371-
call: MethodCall,
372-
result: Result,
373-
) {
374-
val options = HubAdapter.getInstance().options as SentryAndroidOptions
375-
376-
val addresses = call.arguments() as List<String>? ?: listOf()
377-
val debugImages =
378-
if (addresses.isEmpty()) {
379-
options.debugImagesLoader
380-
.loadDebugImages()
381-
?.toList()
382-
.serialize()
383-
} else {
384-
options.debugImagesLoader
385-
.loadDebugImagesForAddresses(addresses.toSet())
386-
?.ifEmpty { options.debugImagesLoader.loadDebugImages() }
387-
?.toList()
388-
.serialize()
389-
}
390-
391-
result.success(debugImages)
392-
}
393-
394-
private fun List<DebugImage>?.serialize() = this?.map { it.serialize() }
395-
396-
private fun DebugImage.serialize() =
397-
mapOf(
398-
"image_addr" to imageAddr,
399-
"image_size" to imageSize,
400-
"code_file" to codeFile,
401-
"type" to type,
402-
"debug_id" to debugId,
403-
"code_id" to codeId,
404-
"debug_file" to debugFile,
405-
)
406372

407373
private fun closeNativeSdk(result: Result) {
408374
HubAdapter.getInstance().close()
@@ -414,11 +380,70 @@ class SentryFlutterPlugin :
414380
@SuppressLint("StaticFieldLeak")
415381
private var replay: ReplayIntegration? = null
416382

383+
@SuppressLint("StaticFieldLeak")
384+
private var applicationContext: Context? = null
385+
417386
private const val NATIVE_CRASH_WAIT_TIME = 500L
418387

419388
@JvmStatic
420389
fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay
421390

391+
@JvmStatic
392+
fun getApplicationContext(): Context? = applicationContext
393+
394+
@JvmStatic
395+
fun loadContextsAsBytes(): ByteArray? {
396+
val options = HubAdapter.getInstance().options
397+
val context = getApplicationContext()
398+
if (options !is SentryAndroidOptions || context == null) {
399+
return null
400+
}
401+
val currentScope = InternalSentrySdk.getCurrentScope()
402+
val serializedScope =
403+
InternalSentrySdk.serializeScope(
404+
context,
405+
options,
406+
currentScope,
407+
)
408+
val json = JSONObject(serializedScope).toString()
409+
return json.toByteArray(Charsets.UTF_8)
410+
}
411+
412+
@JvmStatic
413+
fun loadDebugImagesAsBytes(addresses: Set<String>): ByteArray? {
414+
val options = HubAdapter.getInstance().options as SentryAndroidOptions
415+
416+
val debugImages =
417+
if (addresses.isEmpty()) {
418+
options.debugImagesLoader
419+
.loadDebugImages()
420+
?.toList()
421+
.serialize()
422+
} else {
423+
options.debugImagesLoader
424+
.loadDebugImagesForAddresses(addresses)
425+
?.ifEmpty { options.debugImagesLoader.loadDebugImages() }
426+
?.toList()
427+
.serialize()
428+
}
429+
430+
val json = JSONArray(debugImages).toString()
431+
return json.toByteArray(Charsets.UTF_8)
432+
}
433+
434+
private fun List<DebugImage>?.serialize() = this?.map { it.serialize() }
435+
436+
private fun DebugImage.serialize() =
437+
mapOf(
438+
"image_addr" to imageAddr,
439+
"image_size" to imageSize,
440+
"code_file" to codeFile,
441+
"type" to type,
442+
"debug_id" to debugId,
443+
"code_id" to codeId,
444+
"debug_file" to debugFile,
445+
)
446+
422447
private fun crash() {
423448
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
424449
val mainThread = Looper.getMainLooper().thread
@@ -436,22 +461,6 @@ class SentryFlutterPlugin :
436461
}
437462
}
438463

439-
private fun loadContexts(result: Result) {
440-
val options = HubAdapter.getInstance().options
441-
if (options !is SentryAndroidOptions) {
442-
result.success(null)
443-
return
444-
}
445-
val currentScope = InternalSentrySdk.getCurrentScope()
446-
val serializedScope =
447-
InternalSentrySdk.serializeScope(
448-
context,
449-
options,
450-
currentScope,
451-
)
452-
result.success(serializedScope)
453-
}
454-
455464
private fun setReplayConfig(
456465
call: MethodCall,
457466
result: Result,

0 commit comments

Comments
 (0)