Skip to content

[Feat][SDK-347] ANR report #323

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

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions examples/rollbar-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ buildscript {
apply plugin: 'com.android.application'

android {
compileSdkVersion 27
compileSdkVersion 33
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.rollbar.example.android"
minSdkVersion 16
minSdkVersion 21
// FIXME: Pending further discussion
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 27
targetSdkVersion 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
Expand Down
3 changes: 2 additions & 1 deletion examples/rollbar-android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
Expand Down
6 changes: 3 additions & 3 deletions rollbar-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ apply from: "$rootDir/gradle/release.gradle"
apply from: "$rootDir/gradle/android.quality.gradle"

android {
compileSdkVersion 27
compileSdkVersion 33
buildToolsVersion '30.0.3' // Going above here requires bumping the AGP to version 4+

defaultConfig {
minSdkVersion 16
minSdkVersion 21
// FIXME: Pending further discussion
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 27
targetSdkVersion 33
consumerProguardFiles 'proguard-rules.pro'
manifestPlaceholders = [notifierVersion: VERSION_NAME]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.rollbar.android;

import com.rollbar.android.anr.AnrConfiguration;

public class AndroidConfiguration {
private final AnrConfiguration anrConfiguration;

AndroidConfiguration(Builder builder) {
anrConfiguration = builder.anrConfiguration;
}

public AnrConfiguration getAnrConfiguration() {
return anrConfiguration;
}


public static final class Builder {
private AnrConfiguration anrConfiguration;

Builder() {
anrConfiguration = new AnrConfiguration.Builder().build();
}

/**
* The ANR configuration, if this field is null, no ANR would be captured
* @param anrConfiguration the ANR configuration
* @return the builder instance
*/
public Builder setAnrConfiguration(AnrConfiguration anrConfiguration) {
this.anrConfiguration = anrConfiguration;
return this;
}

public AndroidConfiguration build() {
return new AndroidConfiguration(this);
}
}
}
98 changes: 96 additions & 2 deletions rollbar-android/src/main/java/com/rollbar/android/Rollbar.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import android.os.Bundle;

import android.util.Log;

import com.rollbar.android.anr.AnrDetector;
import com.rollbar.android.anr.AnrDetectorFactory;
import com.rollbar.android.anr.AnrException;
import com.rollbar.android.notifier.sender.ConnectionAwareSenderFailureStrategy;
import com.rollbar.android.provider.ClientProvider;
import com.rollbar.api.payload.data.TelemetryType;
Expand All @@ -25,11 +29,14 @@
import com.rollbar.notifier.sender.queue.DiskQueue;
import com.rollbar.notifier.util.ObjectsUtils;

import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -71,6 +78,28 @@ public static Rollbar init(Context context) {
return init(context, null, null);
}

/**
* Initialize the singleton instance of Rollbar.
* Defaults to reading the access token from the manifest, handling uncaught exceptions, and setting
* the environment to production.
*
* @param context Android context to use.
* @param androidConfiguration configuration for Android features.
* @return the managed instance of Rollbar.
*/
public static Rollbar init(Context context, AndroidConfiguration androidConfiguration) {
return init(
context,
null,
DEFAULT_ENVIRONMENT,
DEFAULT_REGISTER_EXCEPTION_HANDLER,
DEFAULT_INCLUDE_LOGCAT,
DEFAULT_CONFIG_PROVIDER,
DEFAULT_SUSPEND_WHEN_NETWORK_IS_UNAVAILABLE,
androidConfiguration
);
}

/**
* Initialize the singleton instance of Rollbar.
*
Expand Down Expand Up @@ -155,18 +184,72 @@ public static Rollbar init(Context context, String accessToken, String environme
public static Rollbar init(Context context, String accessToken, String environment,
boolean registerExceptionHandler, boolean includeLogcat,
ConfigProvider provider, boolean suspendWhenNetworkIsUnavailable) {
return init(
context,
accessToken,
environment,
registerExceptionHandler,
includeLogcat,
provider,
suspendWhenNetworkIsUnavailable,
makeDefaultAndroidConfiguration()
);
}

/**
* Initialize the singleton instance of Rollbar.
*
* @param context Android context to use.
* @param accessToken a Rollbar access token with at least post_client_item scope
* @param environment the environment to set for items
* @param registerExceptionHandler whether or not to handle uncaught exceptions.
* @param includeLogcat whether or not to include logcat output with items
* @param provider a configuration provider that can be used to customize the configuration further.
* @param suspendWhenNetworkIsUnavailable if true, sending occurrences will be suspended while the network is unavailable
* @param androidConfiguration configuration for Android features
* @return the managed instance of Rollbar.
*/
public static Rollbar init(
Context context,
String accessToken,
String environment,
boolean registerExceptionHandler,
boolean includeLogcat,
ConfigProvider provider,
boolean suspendWhenNetworkIsUnavailable,
AndroidConfiguration androidConfiguration
) {
if (isInit()) {
Log.w(TAG, "Rollbar.init() called when it was already initialized.");
// This is likely an activity that was destroyed and recreated, so we need to update it
notifier.updateContext(context);
} else {
notifier = new Rollbar(context, accessToken, environment, registerExceptionHandler,
includeLogcat, provider, DEFAULT_CAPTURE_IP, DEFAULT_MAX_LOGCAT_SIZE,
suspendWhenNetworkIsUnavailable);
includeLogcat, provider, DEFAULT_CAPTURE_IP, DEFAULT_MAX_LOGCAT_SIZE,
suspendWhenNetworkIsUnavailable);
}

if (androidConfiguration != null && !isInit()) {
initAnrDetector(context, androidConfiguration);
}

return notifier;
}

private static void initAnrDetector(
Context context,
AndroidConfiguration androidConfiguration
) {
AnrDetector anrDetector = AnrDetectorFactory.create(
context,
LoggerFactory.getLogger(AnrDetectorFactory.class),
androidConfiguration.getAnrConfiguration(),
error -> reportANR(error));
if (anrDetector != null) {
anrDetector.init();
}
}

private void updateContext(Context context) {
if (this.senderFailureStrategy != null) {
this.senderFailureStrategy.updateContext(context);
Expand Down Expand Up @@ -1086,4 +1169,15 @@ private static void ensureInit(Runnable runnable) {
}
}

private static void reportANR(AnrException error){
Map<String, Object> map = new HashMap<>();
map.put("TYPE", "ANR");
map.put("Threads", error.getThreads());
notifier.log(error, map);
}

private static AndroidConfiguration makeDefaultAndroidConfiguration() {
return new AndroidConfiguration.Builder().build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.rollbar.android.anr;

import com.rollbar.android.anr.watchdog.WatchdogConfiguration;

public class AnrConfiguration {
WatchdogConfiguration watchdogConfiguration;
boolean captureHistoricalAnr;

public AnrConfiguration(Builder builder) {
this.watchdogConfiguration = builder.watchdogConfiguration;
this.captureHistoricalAnr = builder.captureHistoricalAnr;
}

public static final class Builder {
private boolean captureHistoricalAnr = true;
private WatchdogConfiguration watchdogConfiguration = new WatchdogConfiguration.Builder().build();

/**
* The WatchdogConfiguration configuration, if this field is null, no ANR would be captured.
* By default this feature is on, in build versions < 30.
* @param watchdogConfiguration the Watchdog configuration
* @return the builder instance
*/
public Builder setWatchdogConfiguration(WatchdogConfiguration watchdogConfiguration) {
this.watchdogConfiguration = watchdogConfiguration;
return this;
}

/**
* A flag to turn on or off the HistoricalAnr detector implementation.
* This implementation is used if the build version is >= 30
* @param captureHistoricalAnr HistoricalAnrDetector flag
* @return the builder instance
*/
public Builder setCaptureHistoricalAnr(boolean captureHistoricalAnr) {
this.captureHistoricalAnr = captureHistoricalAnr;
return this;
}

public AnrConfiguration build() {
return new AnrConfiguration(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.rollbar.android.anr;

public interface AnrDetector {
void init();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.rollbar.android.anr;

import android.content.Context;
import android.os.Build;

import com.rollbar.android.anr.historical.HistoricalAnrDetector;
import com.rollbar.android.anr.watchdog.WatchdogAnrDetector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AnrDetectorFactory {

public static AnrDetector create(
Context context,
Logger logger,
AnrConfiguration anrConfiguration,
AnrListener anrListener
) {
if (anrConfiguration == null) {
logger.warn("No ANR configuration");
return null;
}
if (context == null) {
logger.warn("No context");
return null;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!anrConfiguration.captureHistoricalAnr) {
logger.warn("Historical ANR capture is off");
return null;
}

logger.debug("Creating HistoricalAnrDetector");
return new HistoricalAnrDetector(context, anrListener, createHistoricalAnrDetectorLogger());
} else {
if (anrConfiguration.watchdogConfiguration == null) {
logger.warn("No Watchdog configuration");
return null;
}

logger.debug("Creating WatchdogAnrDetector");
return new WatchdogAnrDetector(
context,
anrConfiguration.watchdogConfiguration,
anrListener
);
}
}

private static Logger createHistoricalAnrDetectorLogger() {
return LoggerFactory.getLogger(HistoricalAnrDetector.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.rollbar.android.anr;

import com.rollbar.android.anr.historical.stacktrace.RollbarThread;

import java.util.ArrayList;
import java.util.List;

public final class AnrException extends RuntimeException {

private List<RollbarThread> threads = new ArrayList<>();

public AnrException(String message, Thread thread) {
super(message);
setStackTrace(thread.getStackTrace());
}

public AnrException(StackTraceElement[] mainStackTraceElements, List<RollbarThread> threads) {
super("Application Not Responding");
setStackTrace(mainStackTraceElements);
this.threads = threads;
}

public List<RollbarThread> getThreads() {
return threads;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.rollbar.android.anr;

public interface AnrListener {
/**
* Called when an ANR is detected.
*
* @param error The error describing the ANR.
*/
void onAppNotResponding(AnrException error);
}
Loading
Loading