From 7b15a6ca81712f20778fae4b06ad9dcfe975e747 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 5 Jun 2026 09:04:03 +0200 Subject: [PATCH] perf(android): Replace reflective OptionsContainer with direct subclass Replace OptionsContainer.create(SentryAndroidOptions.class) which uses getDeclaredConstructor().newInstance() with a direct SentryAndroidOptionsContainer subclass that returns new SentryAndroidOptions() without reflection. Make OptionsContainer non-final (@Open) with a protected no-arg constructor so Android can subclass it. --- .../io/sentry/android/core/SentryAndroid.java | 3 +-- .../core/SentryAndroidOptionsContainer.java | 16 ++++++++++++++++ sentry/api/sentry.api | 3 ++- .../main/java/io/sentry/OptionsContainer.java | 18 +++++++++++++++--- 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptionsContainer.java diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index 0d249f73790..f27259fd635 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -9,7 +9,6 @@ import io.sentry.IScopes; import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; -import io.sentry.OptionsContainer; import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.SentryOptions; @@ -98,7 +97,7 @@ public static void init( @NotNull Sentry.OptionsConfiguration configuration) { try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { Sentry.init( - OptionsContainer.create(SentryAndroidOptions.class), + new SentryAndroidOptionsContainer(), options -> { final io.sentry.util.LoadClass classLoader = new io.sentry.util.LoadClass(); final boolean isTimberUpstreamAvailable = diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptionsContainer.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptionsContainer.java new file mode 100644 index 00000000000..678f7ab29b2 --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptionsContainer.java @@ -0,0 +1,16 @@ +package io.sentry.android.core; + +import io.sentry.OptionsContainer; +import org.jetbrains.annotations.NotNull; + +/** + * Direct OptionsContainer for SentryAndroidOptions that avoids reflective + * getDeclaredConstructor().newInstance() on the Android startup path. + */ +final class SentryAndroidOptionsContainer extends OptionsContainer { + + @Override + public @NotNull SentryAndroidOptions createInstance() { + return new SentryAndroidOptions(); + } +} diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 4757be4894a..45268a9a894 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2068,7 +2068,8 @@ public abstract interface class io/sentry/ObjectWriter { public abstract fun value (Z)Lio/sentry/ObjectWriter; } -public final class io/sentry/OptionsContainer { +public class io/sentry/OptionsContainer { + protected fun ()V public static fun create (Ljava/lang/Class;)Lio/sentry/OptionsContainer; public fun createInstance ()Ljava/lang/Object; } diff --git a/sentry/src/main/java/io/sentry/OptionsContainer.java b/sentry/src/main/java/io/sentry/OptionsContainer.java index 52032880aaf..b29aef2e000 100644 --- a/sentry/src/main/java/io/sentry/OptionsContainer.java +++ b/sentry/src/main/java/io/sentry/OptionsContainer.java @@ -1,28 +1,40 @@ package io.sentry; +import com.jakewharton.nopen.annotation.Open; +import io.sentry.util.Objects; import java.lang.reflect.InvocationTargetException; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; @ApiStatus.Internal -public final class OptionsContainer { +@Open +public class OptionsContainer { public @NotNull static OptionsContainer create(final @NotNull Class clazz) { return new OptionsContainer<>(clazz); } - private final @NotNull Class clazz; + private final @Nullable Class clazz; private OptionsContainer(final @NotNull Class clazz) { super(); this.clazz = clazz; } + /** Constructor for subclasses that create the instance directly without reflection. */ + protected OptionsContainer() { + super(); + this.clazz = null; + } + public @NotNull T createInstance() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - return clazz.getDeclaredConstructor().newInstance(); + return Objects.requireNonNull(clazz, "OptionsContainer clazz is required") + .getDeclaredConstructor() + .newInstance(); } }