diff --git a/CHANGELOG.md b/CHANGELOG.md
index c09c4b7f6..9903df83a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
### Features
+- Drastically improved performance of scope sync when targeting Android ([#2107](https://github.com/getsentry/sentry-unity/pull/2107))
- When running on Android, Windows or Linux, the SDK now links errors and events originating on different layers (managed, native errors) via `trace ID` ([#1997](https://github.com/getsentry/sentry-unity/pull/1997), [#2089](https://github.com/getsentry/sentry-unity/pull/2089))
- The SDK now reports the game's name as part of the app context ([2083](https://github.com/getsentry/sentry-unity/pull/2083))
- The SDK now reports the active scene's name as part of the `Unity Context` ([2084](https://github.com/getsentry/sentry-unity/pull/2084))
diff --git a/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs b/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs
index 199de6498..55e51705f 100644
--- a/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs
+++ b/src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs
@@ -9,96 +9,31 @@ namespace Sentry.Unity.Android;
///
internal class AndroidJavaScopeObserver : ScopeObserver
{
- private readonly IJniExecutor _jniExecutor;
+ private ISentryJava _sentryJava;
- public AndroidJavaScopeObserver(SentryOptions options, IJniExecutor jniExecutor) : base("Android", options)
+ public AndroidJavaScopeObserver(SentryOptions options, ISentryJava sentryJava) : base("Android", options)
{
- _jniExecutor = jniExecutor;
+ _sentryJava = sentryJava;
}
- private static AndroidJavaObject GetSentryJava() => new AndroidJavaClass("io.sentry.Sentry");
+ public override void AddBreadcrumbImpl(Breadcrumb breadcrumb) =>
+ _sentryJava.AddBreadcrumb(breadcrumb);
- private static AndroidJavaObject GetInternalSentryJava() => new AndroidJavaClass("io.sentry.android.core.InternalSentrySdk");
+ public override void SetExtraImpl(string key, string? value) =>
+ _sentryJava.SetExtra(key, value);
- public override void AddBreadcrumbImpl(Breadcrumb breadcrumb)
- {
- _jniExecutor.Run(() =>
- {
- using var sentry = GetSentryJava();
- using var javaBreadcrumb = new AndroidJavaObject("io.sentry.Breadcrumb");
- javaBreadcrumb.Set("message", breadcrumb.Message);
- javaBreadcrumb.Set("type", breadcrumb.Type);
- javaBreadcrumb.Set("category", breadcrumb.Category);
- using var javaLevel = breadcrumb.Level.ToJavaSentryLevel();
- javaBreadcrumb.Set("level", javaLevel);
- sentry.CallStatic("addBreadcrumb", javaBreadcrumb, null);
- });
- }
+ public override void SetTagImpl(string key, string value) =>
+ _sentryJava.SetTag(key, value);
- public override void SetExtraImpl(string key, string? value)
- {
- _jniExecutor.Run(() =>
- {
- using var sentry = GetSentryJava();
- sentry.CallStatic("setExtra", key, value);
- });
- }
- public override void SetTagImpl(string key, string value)
- {
- _jniExecutor.Run(() =>
- {
- using var sentry = GetSentryJava();
- sentry.CallStatic("setTag", key, value);
- });
- }
+ public override void UnsetTagImpl(string key) =>
+ _sentryJava.UnsetTag(key);
- public override void UnsetTagImpl(string key)
- {
- _jniExecutor.Run(() =>
- {
- using var sentry = GetSentryJava();
- sentry.CallStatic("removeTag", key);
- });
- }
+ public override void SetUserImpl(SentryUser user) =>
+ _sentryJava.SetUser(user);
- public override void SetUserImpl(SentryUser user)
- {
- _jniExecutor.Run(() =>
- {
- AndroidJavaObject? javaUser = null;
- try
- {
- javaUser = new AndroidJavaObject("io.sentry.protocol.User");
- javaUser.Set("email", user.Email);
- javaUser.Set("id", user.Id);
- javaUser.Set("username", user.Username);
- javaUser.Set("ipAddress", user.IpAddress);
- using var sentry = GetSentryJava();
- sentry.CallStatic("setUser", javaUser);
- }
- finally
- {
- javaUser?.Dispose();
- }
- });
- }
+ public override void UnsetUserImpl() =>
+ _sentryJava.UnsetUser();
- public override void UnsetUserImpl()
- {
- _jniExecutor.Run(() =>
- {
- using var sentry = GetSentryJava();
- sentry.CallStatic("setUser", null);
- });
- }
-
- public override void SetTraceImpl(SentryId traceId, SpanId spanId)
- {
- _jniExecutor.Run(() =>
- {
- using var sentry = GetInternalSentryJava();
- // We have to explicitly cast to `(Double?)`
- sentry.CallStatic("setTrace", traceId.ToString(), spanId.ToString(), (Double?)null, (Double?)null);
- });
- }
+ public override void SetTraceImpl(SentryId traceId, SpanId spanId) =>
+ _sentryJava.SetTrace(traceId, spanId);
}
diff --git a/src/Sentry.Unity.Android/AndroidOptionConfiguration.cs b/src/Sentry.Unity.Android/AndroidOptionConfiguration.cs
new file mode 100644
index 000000000..794ff8f59
--- /dev/null
+++ b/src/Sentry.Unity.Android/AndroidOptionConfiguration.cs
@@ -0,0 +1,36 @@
+using System;
+using Sentry.Extensibility;
+using UnityEngine;
+
+namespace Sentry.Unity.Android;
+
+internal class AndroidOptionsConfiguration : AndroidJavaProxy
+{
+ private readonly Action _callback;
+ private readonly IDiagnosticLogger? _logger;
+
+ public AndroidOptionsConfiguration(Action callback, IDiagnosticLogger? logger)
+ : base("io.sentry.Sentry$OptionsConfiguration")
+ {
+ _callback = callback;
+ _logger = logger;
+ }
+
+ public override AndroidJavaObject? Invoke(string methodName, AndroidJavaObject[] args)
+ {
+ try
+ {
+ if (methodName != "configure" || args.Length != 1)
+ {
+ throw new Exception($"Invalid invocation: {methodName}({args.Length} args)");
+ }
+
+ _callback(args[0]);
+ }
+ catch (Exception e)
+ {
+ _logger?.LogError(e, "Error invoking '{0}'.", methodName);
+ }
+ return null;
+ }
+}
diff --git a/src/Sentry.Unity.Android/IAndroidJNI.cs b/src/Sentry.Unity.Android/IAndroidJNI.cs
new file mode 100644
index 000000000..e18e74478
--- /dev/null
+++ b/src/Sentry.Unity.Android/IAndroidJNI.cs
@@ -0,0 +1,18 @@
+using UnityEngine;
+
+namespace Sentry.Unity.Android;
+
+public interface IAndroidJNI
+{
+ public void AttachCurrentThread();
+ public void DetachCurrentThread();
+}
+
+public class AndroidJNIAdapter : IAndroidJNI
+{
+ public static readonly AndroidJNIAdapter Instance = new();
+
+ public void AttachCurrentThread() => AndroidJNI.AttachCurrentThread();
+
+ public void DetachCurrentThread() => AndroidJNI.DetachCurrentThread();
+}
diff --git a/src/Sentry.Unity.Android/IJniExecutor.cs b/src/Sentry.Unity.Android/IJniExecutor.cs
deleted file mode 100644
index f465602cd..000000000
--- a/src/Sentry.Unity.Android/IJniExecutor.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System;
-
-namespace Sentry.Unity.Android;
-
-internal interface IJniExecutor : IDisposable
-{
- public TResult? Run(Func jniOperation, TimeSpan? timeout = null);
- public void Run(Action jniOperation, TimeSpan? timeout = null);
-}
diff --git a/src/Sentry.Unity.Android/JniExecutor.cs b/src/Sentry.Unity.Android/JniExecutor.cs
deleted file mode 100644
index 079484b6d..000000000
--- a/src/Sentry.Unity.Android/JniExecutor.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Sentry.Extensibility;
-using UnityEngine;
-
-namespace Sentry.Unity.Android;
-
-internal class JniExecutor : IJniExecutor
-{
- // We're capping out at 16ms - 1 frame at 60 frames per second
- private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMilliseconds(16);
-
- private readonly CancellationTokenSource _shutdownSource;
- private readonly AutoResetEvent _taskEvent;
- private readonly IDiagnosticLogger? _logger;
-
- private Delegate _currentTask = null!; // The current task will always be set together with the task event
-
- private TaskCompletionSource