寫在開頭
公司前端使用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
是不是你要的都有了,有任何問題隨時評論或者私信就行了。