Skip to content

Commit

Permalink
Check at runtime if AndroidX.Core is available (#1718)
Browse files Browse the repository at this point in the history
  • Loading branch information
marandaneto authored Sep 15, 2021
1 parent 28ed730 commit 87e9f5f
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

* Fix: Check at runtime if AndroidX.Core is available (#1718)
* Feat: Add "data" to spans (#1717)
* Fix: Should not capture unfinished transaction (#1719)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,52 @@

final class ActivityFramesTracker {

private final @NotNull FrameMetricsAggregator frameMetricsAggregator;
private @Nullable FrameMetricsAggregator frameMetricsAggregator = null;
private boolean androidXAvailable = true;

ActivityFramesTracker() {
this(new FrameMetricsAggregator());
private final @NotNull Map<SentryId, Map<String, @NotNull MeasurementValue>>
activityMeasurements = new ConcurrentHashMap<>();

ActivityFramesTracker(final @NotNull LoadClass loadClass) {
androidXAvailable = checkAndroidXAvailability(loadClass);
if (androidXAvailable) {
frameMetricsAggregator = new FrameMetricsAggregator();
}
}

@TestOnly
ActivityFramesTracker(final @NotNull FrameMetricsAggregator frameMetricsAggregator) {
ActivityFramesTracker(final @Nullable FrameMetricsAggregator frameMetricsAggregator) {
this.frameMetricsAggregator = frameMetricsAggregator;
}

private final @NotNull Map<SentryId, Map<String, @NotNull MeasurementValue>>
activityMeasurements = new ConcurrentHashMap<>();
private static boolean checkAndroidXAvailability(final @NotNull LoadClass loadClass) {
try {
loadClass.loadClass("androidx.core.app.FrameMetricsAggregator");
return true;
} catch (ClassNotFoundException ignored) {
// androidx.core isn't available.
return false;
}
}

private boolean isFrameMetricsAggregatorAvailable() {
return androidXAvailable && frameMetricsAggregator != null;
}

@SuppressWarnings("NullAway")
void addActivity(final @NotNull Activity activity) {
if (!isFrameMetricsAggregatorAvailable()) {
return;
}
frameMetricsAggregator.add(activity);
}

@SuppressWarnings("NullAway")
void setMetrics(final @NotNull Activity activity, final @NotNull SentryId sentryId) {
if (!isFrameMetricsAggregatorAvailable()) {
return;
}

int totalFrames = 0;
int slowFrames = 0;
int frozenFrames = 0;
Expand Down Expand Up @@ -75,14 +102,21 @@ void setMetrics(final @NotNull Activity activity, final @NotNull SentryId sentry

@Nullable
Map<String, @NotNull MeasurementValue> takeMetrics(final @NotNull SentryId sentryId) {
if (!isFrameMetricsAggregatorAvailable()) {
return null;
}

final Map<String, @NotNull MeasurementValue> stringMeasurementValueMap =
activityMeasurements.get(sentryId);
activityMeasurements.remove(sentryId);
return stringMeasurementValueMap;
}

@SuppressWarnings("NullAway")
void stop() {
frameMetricsAggregator.stop();
if (isFrameMetricsAggregatorAvailable()) {
frameMetricsAggregator.stop();
}
activityMeasurements.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ static void init(
* @param context the Application context
* @param logger the ILogger interface
* @param buildInfoProvider the IBuildInfoProvider interface
* @param loadClass the ILoadClass interface
* @param loadClass the LoadClass wrapper
*/
static void init(
final @NotNull SentryAndroidOptions options,
@NotNull Context context,
final @NotNull ILogger logger,
final @NotNull IBuildInfoProvider buildInfoProvider,
final @NotNull ILoadClass loadClass) {
final @NotNull LoadClass loadClass) {
Objects.requireNonNull(context, "The context is required.");

// it returns null if ContextImpl, so let's check for nullability
Expand All @@ -100,7 +100,7 @@ static void init(
ManifestMetadataReader.applyMetadata(context, options);
initializeCacheDirs(context, options);

final ActivityFramesTracker activityFramesTracker = new ActivityFramesTracker();
final ActivityFramesTracker activityFramesTracker = new ActivityFramesTracker(loadClass);
installDefaultIntegrations(
context, options, buildInfoProvider, loadClass, activityFramesTracker);

Expand All @@ -116,7 +116,7 @@ private static void installDefaultIntegrations(
final @NotNull Context context,
final @NotNull SentryOptions options,
final @NotNull IBuildInfoProvider buildInfoProvider,
final @NotNull ILoadClass loadClass,
final @NotNull LoadClass loadClass,
final @NotNull ActivityFramesTracker activityFramesTracker) {

options.addIntegration(
Expand Down Expand Up @@ -224,7 +224,7 @@ private static boolean isNdkAvailable(final @NotNull IBuildInfoProvider buildInf
private static @Nullable Class<?> loadNdkIfAvailable(
final @NotNull SentryOptions options,
final @NotNull IBuildInfoProvider buildInfoProvider,
final @NotNull ILoadClass loadClass) {
final @NotNull LoadClass loadClass) {
if (isNdkAvailable(buildInfoProvider)) {
try {
return loadClass.loadClass(SENTRY_NDK_CLASS_NAME);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

import org.jetbrains.annotations.NotNull;

final class LoadClass implements ILoadClass {
@Override
/** An Adapter for making Class.forName testable */
final class LoadClass {

/**
* Try to load a class via reflection
*
* @param clazz the full class name
* @return a Class<?>
* @throws ClassNotFoundException if class is not found
*/
public @NotNull Class<?> loadClass(@NotNull String clazz) throws ClassNotFoundException {
return Class.forName(clazz);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ActivityFramesTrackerTest {
val aggregator = mock<FrameMetricsAggregator>()
val activity = mock<Activity>()
val sentryId = SentryId()
val loadClass = mock<LoadClass>()

fun getSut(): ActivityFramesTracker {
return ActivityFramesTracker(aggregator)
Expand Down Expand Up @@ -113,6 +114,38 @@ class ActivityFramesTrackerTest {
assertNull(metrics)
}

@Test
fun `addActivity does not throw if no AndroidX`() {
whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException())
val sut = ActivityFramesTracker(fixture.loadClass)

sut.addActivity(fixture.activity)
}

@Test
fun `setMetrics does not throw if no AndroidX`() {
whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException())
val sut = ActivityFramesTracker(fixture.loadClass)

sut.setMetrics(fixture.activity, fixture.sentryId)
}

@Test
fun `stop does not throw if no AndroidX`() {
whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException())
val sut = ActivityFramesTracker(fixture.loadClass)

sut.stop()
}

@Test
fun `takeMetrics returns null if no AndroidX`() {
whenever(fixture.loadClass.loadClass(any())).thenThrow(ClassNotFoundException())
val sut = ActivityFramesTracker(fixture.loadClass)

assertNull(sut.takeMetrics(fixture.sentryId))
}

private fun getArray(frameTime: Int = 1, numFrames: Int = 1): Array<SparseIntArray?> {
val totalArray = SparseIntArray()
totalArray.put(frameTime, numFrames)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.sentry.MainEventProcessor
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.android.core.NdkIntegration.SENTRY_NDK_CLASS_NAME
import java.io.File
import java.lang.RuntimeException
import kotlin.test.BeforeTest
Expand Down Expand Up @@ -332,15 +333,15 @@ class AndroidOptionsInitializerTest {
return buildInfo
}

private fun createClassMock(clazz: Class<*> = SentryNdk::class.java): ILoadClass {
val loadClassMock = mock<ILoadClass>()
private fun createClassMock(clazz: Class<*> = SentryNdk::class.java): LoadClass {
val loadClassMock = mock<LoadClass>()
whenever(loadClassMock.loadClass(any())).thenReturn(clazz)
return loadClassMock
}

private fun createClassMockThrows(ex: Throwable): ILoadClass {
val loadClassMock = mock<ILoadClass>()
whenever(loadClassMock.loadClass(any())).thenThrow(ex)
private fun createClassMockThrows(ex: Throwable): LoadClass {
val loadClassMock = mock<LoadClass>()
whenever(loadClassMock.loadClass(eq(SENTRY_NDK_CLASS_NAME))).thenThrow(ex)
return loadClassMock
}
}
1 change: 1 addition & 0 deletions sentry-samples/sentry-samples-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ dependencies {
// implementation(projects.sentryAndroid) {
// exclude(group = "androidx.lifecycle", module = "lifecycle-process")
// exclude(group = "androidx.lifecycle", module = "lifecycle-common-java8")
// exclude(group = "androidx.core", module = "core")
// }

implementation(Config.Libs.appCompat)
Expand Down

0 comments on commit 87e9f5f

Please sign in to comment.