Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use JNI for Android integration #2670

Merged
merged 27 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/coverage/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ runs:
with:
path: './${{ inputs.directory }}/coverage/lcov.info'
min_coverage: ${{ inputs.min-coverage }}
exclude: 'lib/src/native/cocoa/binding.dart'
exclude: 'lib/src/native/**/binding.dart lib/src/native/java/android_replay_recorder.dart'
1 change: 1 addition & 0 deletions .github/file-filters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ high_risk_code: &high_risk_code
- "flutter/ios/Classes/SentryFlutterPluginApple.swift"
- "flutter/lib/src/screenshot/recorder.dart"
- "flutter/lib/src/screenshot/widget_filter.dart"
- "flutter/lib/src/native/java/android_replay_recorder.dart"
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased 9.0.0

### Breaking changes

- Remove `SentryDisplayWidget` and manual TTID implementation ([#2668](https://github.com/getsentry/sentry-dart/pull/2668))
- Increase minimum SDK version requirements to Dart v3.5.0 and Flutter v3.24.0 ([#2643](https://github.com/getsentry/sentry-dart/pull/2643))
- Remove screenshot option `attachScreenshotOnlyWhenResumed` ([#2664](https://github.com/getsentry/sentry-dart/pull/2664))
Expand All @@ -10,6 +12,10 @@
- Remove user segment ([#2687](https://github.com/getsentry/sentry-dart/pull/2687))
- Remove `options.autoAppStart` and `setAppStartEnd` ([#2680](https://github.com/getsentry/sentry-dart/pull/2680))

### Enhancements

- Replay: improve iOS native interop performance by using JNI ([#2670](https://github.com/getsentry/sentry-dart/pull/2670))

### Dependencies

- Bump Android SDK from v7.20.1 to v8.1.0 ([#2650](https://github.com/getsentry/sentry-dart/pull/2650))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.flutter

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
Expand Down Expand Up @@ -35,7 +36,6 @@ import io.sentry.protocol.DebugImage
import io.sentry.protocol.SentryId
import io.sentry.protocol.User
import io.sentry.transport.CurrentDateProvider
import java.io.File
import java.lang.ref.WeakReference
import kotlin.math.roundToInt

Expand All @@ -49,7 +49,6 @@ class SentryFlutterPlugin :
private lateinit var channel: MethodChannel
private lateinit var context: Context
private lateinit var sentryFlutter: SentryFlutter
private lateinit var replay: ReplayIntegration

// Note: initial config because we don't yet have the numbers of the actual Flutter widget.
// See how SentryFlutterReplayRecorder.start() handles it. New settings will be set by setReplayConfig() method below.
Expand Down Expand Up @@ -103,7 +102,6 @@ class SentryFlutterPlugin :
"displayRefreshRate" -> displayRefreshRate(result)
"nativeCrash" -> crash()
"setReplayConfig" -> setReplayConfig(call, result)
"addReplayScreenshot" -> addReplayScreenshot(call.argument("path"), call.argument("timestamp"), result)
"captureReplay" -> captureReplay(call.argument("isCrash"), result)
else -> result.notImplemented()
}
Expand Down Expand Up @@ -164,15 +162,13 @@ class SentryFlutterPlugin :
private fun setupReplay(options: SentryAndroidOptions) {
// Replace the default ReplayIntegration with a Flutter-specific recorder.
options.integrations.removeAll { it is ReplayIntegration }
val cacheDirPath = options.cacheDirPath
val replayOptions = options.sessionReplay
val isReplayEnabled = replayOptions.isSessionReplayEnabled || replayOptions.isSessionReplayForErrorsEnabled
if (cacheDirPath != null && isReplayEnabled) {
if (replayOptions.isSessionReplayEnabled || replayOptions.isSessionReplayForErrorsEnabled) {
replay =
ReplayIntegration(
context,
context.applicationContext,
dateProvider = CurrentDateProvider.getInstance(),
recorderProvider = { SentryFlutterReplayRecorder(channel, replay) },
recorderProvider = { SentryFlutterReplayRecorder(channel, replay!!) },
recorderConfigProvider = {
Log.i(
"Sentry",
Expand All @@ -187,8 +183,8 @@ class SentryFlutterPlugin :
},
replayCacheProvider = null,
)
replay.breadcrumbConverter = SentryFlutterReplayBreadcrumbConverter()
options.addIntegration(replay)
replay!!.breadcrumbConverter = SentryFlutterReplayBreadcrumbConverter()
options.addIntegration(replay!!)
options.setReplayController(replay)
} else {
options.setReplayController(null)
Expand Down Expand Up @@ -517,8 +513,13 @@ class SentryFlutterPlugin :
}

companion object {
@SuppressLint("StaticFieldLeak")
private var replay: ReplayIntegration? = null

private const val NATIVE_CRASH_WAIT_TIME = 500L

@JvmStatic fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay

private fun crash() {
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
val mainThread = Looper.getMainLooper().thread
Expand Down Expand Up @@ -552,19 +553,6 @@ class SentryFlutterPlugin :
result.success(serializedScope)
}

private fun addReplayScreenshot(
path: String?,
timestamp: Long?,
result: Result,
) {
if (path == null || timestamp == null) {
result.error("5", "Arguments are null", null)
return
}
replay.onScreenshotRecorded(File(path), timestamp)
result.success("")
}

private fun setReplayConfig(
call: MethodCall,
result: Result,
Expand Down Expand Up @@ -614,7 +602,7 @@ class SentryFlutterPlugin :
replayConfig.bitRate,
),
)
replay.onConfigurationChanged(Configuration())
replay!!.onConfigurationChanged(Configuration())
result.success("")
}

Expand All @@ -626,7 +614,7 @@ class SentryFlutterPlugin :
result.error("5", "Arguments are null", null)
return
}
replay.captureReplay(isCrash)
result.success(replay.getReplayId().toString())
replay!!.captureReplay(isCrash)
result.success(replay!!.getReplayId().toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,11 @@ internal class SentryFlutterReplayRecorder(
return
}

val cacheDirPath = integration.replayCacheDir?.absolutePath
if (cacheDirPath == null) {
Log.w("Sentry", "Replay cache directory is null, can't start replay recorder.")
return
}
Handler(Looper.getMainLooper()).post {
try {
channel.invokeMethod(
"ReplayRecorder.start",
mapOf(
"directory" to cacheDirPath,
"width" to recorderConfig.recordingWidth,
"height" to recorderConfig.recordingHeight,
"frameRate" to recorderConfig.frameRate,
Expand Down
18 changes: 18 additions & 0 deletions flutter/ffi-jni.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
android_sdk_config:
add_gradle_deps: true
android_example: 'example/'

# summarizer:
# backend: asm

output:
dart:
path: lib/src/native/java/binding.dart
structure: single_file

log_level: all

classes:
- io.sentry.android.replay.ReplayIntegration
- io.sentry.flutter.SentryFlutterPlugin
- android.graphics.Bitmap
Loading
Loading