写在开头
公司前端使用sentry进行bug的统计与分析,为了方便工具统一,无奈我们客户端也要集成sentry,无奈国内使用较少,资料匮乏,唯一有的博客还和google翻译的一模一样,针对点不明确。在集成的过程中踩了点坑,分享需要的朋友。
sentry介绍
Sentry通过在应用程序的运行时中使用SDK捕获数据。和国内bugly和友盟差不多,使用过程中较好的一点针对上传到后台的bug有很详细的关于机型,当时内存等等的详细统计,使用感觉还算良好。
学习链接只推荐官网文档了。其他博客就不推荐了,看我的保证接入成功,并有你想要的功能。
sentry官网文档
sentry集成
1.app / build.gradle中使用Gradle(Android Studio):
implementation 'io.sentry:sentry-android:1.7.27'
implementation 'org.slf4j:slf4j-nop:1.7.25'
2.写一个自己的SentryManager
SelfSentryClientFactory为自定义的AndroidSentryClientFactory,至于他的作用,后续会讲解
public class SentryManager {
//初始化sentry
public static void init(Context context, String sentryDsn) {
Sentry.init(sentryDsn, new SelfSentryClientFactory(context));
}
//主动发送Throwable消息
public static void sendSentryExcepiton(Throwable throwable) {
Sentry.capture(throwable);
}
//主动发送Event消息
public static void sendSentryExcepiton(Event event) {
Sentry.capture(event);
}
//主动发送EventBuilder消息
public static void sendSentryExcepiton(EventBuilder throwable) {
Sentry.capture(throwable);
}
//组合EventBuilder消息并发送 可以通过logger区分
public static void sendSentryExcepiton(String message, String logger, Throwable throwable) {
sendSentryExcepiton(new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR).withLogger(logger).withSentryInterface(new ExceptionInterface(throwable)));
}
}
3.然后在application中掉用初始化就行了,初始化需要的一个DSN字符串。在您的sentry后台获取一下。
然后你就可以模拟一个崩溃,稍等一会sentry就可以统计到了。
sentry自定义上传参数
思路上就是看sentry-android库中人家怎么添加的参数,然后集成重写就行了。过程中你会发现我们不仅要重写AndroidSentryClientFactory和AndroidEventBuilderHelper两个类还要把ANRWatchDog和ApplicationNotResponding两个类在你的项目中重写写一份,因为在库中这俩类是私有的,但是我们在项目中又要引用。
下面看下ApplicationNotResponding和AndroidEventBuilderHelper的继承类。我做了详细的注释。我保留了所有的参数, CustomMap为我们自定义的参数map。
public class SelfEventBuilderHelper extends AndroidEventBuilderHelper {
private Context ctx;
public SelfEventBuilderHelper(Context ctx) {
super(ctx);
this.ctx = ctx;
}
protected Map<String, Map<String, Object>> getContexts() {
Map<String, Map<String, Object>> contexts = new HashMap<>();
Map<String, Object> deviceMap = new HashMap<>();
Map<String, Object> osMap = new HashMap<>();
Map<String, Object> appMap = new HashMap<>();
Map<String, Object> CustomMap = new HashMap<>();
contexts.put("os", osMap);
contexts.put("device", deviceMap);
contexts.put("app", appMap);
contexts.put("custom", CustomMap);
// Device
deviceMap.put("manufacturer", Build.MANUFACTURER);//产品/硬件的制造商
deviceMap.put("brand", Build.BRAND);//与产品/硬件相关联的消费者可见的品牌(如果有的话)
deviceMap.put("model", Build.MODEL);//最终产品的最终用户可见名称
deviceMap.put("family", getFamily());//"Nexus 6P" -> "Nexus" 返回设备的姓
deviceMap.put("model_id", Build.ID);//可以是变更列表号,也可以是“M4-rc20”这样的标签
deviceMap.put("battery_level", getBatteryLevel(ctx));//获取设备当前电池电量(占总电量的百分比)如果未知则为空
deviceMap.put("orientation", getOrientation(ctx));//获取设备当前的屏幕方向 如果未知则为空
deviceMap.put("simulator", isEmulator());//是否是模拟器
deviceMap.put("arch", Build.CPU_ABI);//本机代码的指令集的名称(CPU类型+ ABI约定)
deviceMap.put("storage_size", getTotalInternalStorage());//获取内部存储的总量,以字节为单位
deviceMap.put("free_storage", getUnusedInternalStorage());//获取未使用的内部存储数量,以字节为单位
deviceMap.put("external_storage_size", getTotalExternalStorage());//获取外部存储的总量,以字节为单位,如果没有外部存储,则为null
deviceMap.put("external_free_storage", getUnusedExternalStorage());//获取未使用外部存储的总量,以字节为单位,如果没有外部存储,则为null
deviceMap.put("charging", isCharging(ctx));//检查设备当前是否处于充电状态,如果未知则为空
deviceMap.put("online", isConnected(ctx));//是否具有internet访问
//当前屏幕尺寸 density dpi
DisplayMetrics displayMetrics = getDisplayMetrics(ctx);
if (displayMetrics != null) {
int largestSide = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
int smallestSide = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
String resolution = Integer.toString(largestSide) + "x" + Integer.toString(smallestSide);
deviceMap.put("screen_resolution", resolution);
deviceMap.put("screen_density", displayMetrics.density);
deviceMap.put("screen_dpi", displayMetrics.densityDpi);
}
//当前内存信息
ActivityManager.MemoryInfo memInfo = getMemInfo(ctx);
if (memInfo != null) {
deviceMap.put("free_memory", memInfo.availMem);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
deviceMap.put("memory_size", memInfo.totalMem);
}
deviceMap.put("low_memory", memInfo.lowMemory);
}
// Operating System 操作系统相关
osMap.put("name", "Android");
osMap.put("version", Build.VERSION.RELEASE);//用户可见的版本字符串
osMap.put("build", Build.DISPLAY);//用于向用户显示的构建ID字符串
osMap.put("kernel_version", getKernelVersion());//获取设备的当前内核版本
osMap.put("rooted", isRooted());//是否root
// App
PackageInfo packageInfo = getPackageInfo(ctx);
if (packageInfo != null) {
appMap.put("app_version", packageInfo.versionName);
appMap.put("app_build", packageInfo.versionCode);
appMap.put("app_identifier", packageInfo.packageName);
}
appMap.put("app_name", getApplicationName(ctx));
appMap.put("app_start_time", stringifyDate(new Date()));
CustomMap.put("test", “test”);
return contexts;
}
}
public class SelfSentryClientFactory extends AndroidSentryClientFactory {
public static final String TAG = SelfSentryClientFactory.class.getName();
private static volatile ANRWatchDog anrWatchDog;
private Context ctx;
public SelfSentryClientFactory(Context ctx) {
super(ctx);
this.ctx = ctx.getApplicationContext();
if (this.ctx == null) {
this.ctx = ctx;
}
}
@Override
public SentryClient createSentryClient(Dsn dsn) {
if (!checkPermission(Manifest.permission.INTERNET)) {
Log.e(TAG, Manifest.permission.INTERNET + " is required to connect to the Sentry server,"
+ " please add it to your AndroidManifest.xml");
}
Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "'");
String protocol = dsn.getProtocol();
if (protocol.equalsIgnoreCase("noop")) {
Log.w(TAG, "*** Couldn't find a suitable DSN, Sentry operations will do nothing!"
+ " See documentation: https://docs.sentry.io/clients/java/modules/android/ ***");
} else if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) {
String async = Lookup.lookup(DefaultSentryClientFactory.ASYNC_OPTION, dsn);
if (async != null && async.equalsIgnoreCase("false")) {
throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '"
+ DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your options.");
}
throw new IllegalArgumentException("Only 'http' or 'https' connections are supported in"
+ " Sentry Android, but received: " + protocol);
}
SentryClient sentryClient = super.createSentryClient(dsn);
sentryClient.addBuilderHelper(new SelfEventBuilderHelper(ctx));
boolean enableAnrTracking = "true".equalsIgnoreCase(Lookup.lookup("anr.enable", dsn));
Log.d(TAG, "ANR is='" + String.valueOf(enableAnrTracking) + "'");
if (enableAnrTracking && anrWatchDog == null) {
String timeIntervalMsConfig = Lookup.lookup("anr.timeoutIntervalMs", dsn);
int timeoutIntervalMs = timeIntervalMsConfig != null
? Integer.parseInt(timeIntervalMsConfig)
: 5000;
Log.d(TAG, "ANR timeoutIntervalMs is='" + String.valueOf(timeoutIntervalMs) + "'");
anrWatchDog = new ANRWatchDog(timeoutIntervalMs, new ANRWatchDog.ANRListener() {
@Override
public void onAppNotResponding(ApplicationNotResponding error) {
Log.d(TAG, "ANR triggered='" + error.getMessage() + "'");
EventBuilder builder = new EventBuilder();
builder.withTag("thread_state", error.getState().toString());
ExceptionMechanism mechanism = new ExceptionMechanism("anr", false);
Throwable throwable = new ExceptionMechanismThrowable(mechanism, error);
builder.withSentryInterface(new ExceptionInterface(throwable));
Sentry.capture(builder);
}
});
anrWatchDog.start();
}
return sentryClient;
}
private boolean checkPermission(String permission) {
int res = ctx.checkCallingOrSelfPermission(permission);
return (res == PackageManager.PERMISSION_GRANTED);
}
}
sentry自动上传mapping文件
1.project/build.gradle
classpath 'io.sentry:sentry-android-gradle-plugin:1.7.27'
2.app / build.gradle
apply plugin: 'io.sentry.android.gradle'
sentry {
// 禁用或启用ProGuard的自动配置
autoProguardConfig true
// 启用或禁用自动上传映射文件
autoUpload true
}
3.建立sentry.properties文件
defaults.project=your-project
defaults.org=your-org
auth.token=YOUR_AUTH_TOKEN
4.重点来了,然后跑项目。
你会发现,会报错token认证失败。为什么呢?因为在执行assembleRelease,执行插件代码的时候,sentry会自动链接他自己的服务器,而不是你的企业的sentry服务器,所以您要指定一下服务器url。命令如下:
sentry-cli --url 你的服务器Url login
注意,空格一定要有,空格标识这是传给sentry服务器的两个参数。然后输入你项目的token就行了。再次打包Ok!!!
文末总结
1.bug上传
2.自定义参数
3.mapping自动上传帮助定位bug
是不是你要的都有了,有任何问题随时评论或者私信就行了。