介绍
腾讯Bugly,为移动开发者提供专业的异常上报和运营统计,帮助开发者快速发现并解决异常,同时掌握产品运营动态,及时跟进用户反馈。
应用集成SDK后,即可在Web站点查看应用上报的崩溃数据和联网数据。
功能
- 异常概览:查看今日实时统计、崩溃趋势、崩溃排行和TOP20崩溃问题等信息
- 崩溃分析/卡顿分析/错误分析:查看上报问题的列表
- 问题详情:查看上报问题的详细信息
- 高级搜索:通过各种条件快速查找需要定位分析的异常
- 运营统计:查看应用的联网统计信息
平台术语
- 异常:App在运行过程中发生的崩溃、卡顿、ANR、错误,统称为异常。
- 崩溃:用户在使用App过程中发生一次闪退,计为一次崩溃。
- 卡顿:用户在使用App过程中发生卡顿现象,计为一次卡顿,卡顿时间默认是5s,也支持自定义时间。
- ANR:用户在使用App过程中出现弹框,提示应用无响应,计为一次ANR,ANR仅用于Android平台应用。
- 错误:主动上报的Exception、Error,或脚本(如C#、Lua、JS等)错误,统称为错误。
- 发生次数:一个异常发生且被记录上报,计为一次异常发生。
- 影响用户:一台设备发生异常,计为一个影响用户。 在指定时间范围内,若一个设备发生多次异常,只算一个影响用户。
- 用户异常率:即影响用户/联网用户的比值,诸如用户崩溃率、用户卡顿率、用户ANR率、用户错误率等。
- 次数异常率:即发生次数/联网次数的比值,诸如次数崩溃率、次数卡顿率、次数ANR率、次数错误率等。
- 联网次数:即 启动次数+跨天联网次数。
- 跨天联网:用户没有启动应用,只有应用进程在后台运行,且超过零点,计为一次跨天联网。
- 启动次数:以下场景均计为一次启动
- 1、应用完全退出后重新启动,计为一次启动;
- 2、应用被切换至后台后,30秒后被切换至前台,计为一次启动,若未超过30秒切换至前台,不算一次启动。
- 联网用户:以设备为判断指标,每一个发生联网的设备,即为一个联网用户;在指定时间范围内,若一个设备重复发生联网行为,只算一个联网用户。
最简单的接入配置
配置依赖
implementation 'com.tencent.bugly:crashreport:2.6.6'
1
1
1
implementation 'com.tencent.bugly:crashreport:2.6.6'
权限
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
4
4
1
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
2
<uses-permission android:name="android.permission.INTERNET"/>
3
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
4
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
避免混淆
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
2
2
1
-dontwarn com.tencent.bugly.**
2
-keep public class com.tencent.bugly.**{*;}
初始化
CrashReport.initCrashReport(this, "6c32268c59", true);//APPID、是否为调试模式
1
1
1
CrashReport.initCrashReport(this, "6c32268c59", true);//APPID、是否为调试模式
第三个参数为SDK调试模式开关,调试模式的行为特性如下:
- 输出详细的Bugly SDK的Log
- 每一条Crash都会被立即上报
- 自定义日志将会在Logcat中输出
建议在测试阶段建议设置成true,发布时设置为false。
测试
switch (position) {
case 0:
Log.i("bqt", "" + string.length());//空指针异常
break;
case 1:
CrashReport.testJavaCrash();//测试Java Crash
break;
case 2:
CrashReport.testANRCrash();//测试ANR Crash
break;
case 3:
CrashReport.testNativeCrash();//测试Native Crash
break;
}
14
14
1
switch (position) {
2
case 0:
3
Log.i("bqt", "" + string.length());//空指针异常
4
break;
5
case 1:
6
CrashReport.testJavaCrash();//测试Java Crash
7
break;
8
case 2:
9
CrashReport.testANRCrash();//测试ANR Crash
10
break;
11
case 3:
12
CrashReport.testNativeCrash();//测试Native Crash
13
break;
14
}
高级配置
用户策略 UserStrategy
UserStrategy是Bugly的初始化扩展
类,在这里您可以修改本次初始化Bugly数据的版本、渠道及部分初始化行为:
CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(this);//在初始化Bugly时增加一个上报进程的策略配置
strategy.setAppChannel("myChannel") //设置渠道,会覆盖 AndroidManifest 里面的配置
.setAppVersion("1.0.1") //App的版本,会覆盖 AndroidManifest 里面的配置
.setAppPackageName("com.bqt.test") //App的包名
.setAppReportDelay(20 * 1000) //设置Bugly初始化延迟时间,Bugly默认会在启动10s后联网同步数据
.setUploadProcess(isMainProcess);//只在主进程下上报数据
CrashReport.initCrashReport(this, "6c32268c59", true, strategy);// 初始化Bugly
7
7
1
CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(this);//在初始化Bugly时增加一个上报进程的策略配置
2
strategy.setAppChannel("myChannel") //设置渠道,会覆盖 AndroidManifest 里面的配置
3
.setAppVersion("1.0.1") //App的版本,会覆盖 AndroidManifest 里面的配置
4
.setAppPackageName("com.bqt.test") //App的包名
5
.setAppReportDelay(20 * 1000) //设置Bugly初始化延迟时间,Bugly默认会在启动10s后联网同步数据
6
.setUploadProcess(isMainProcess);//只在主进程下上报数据
7
CrashReport.initCrashReport(this, "6c32268c59", true, strategy);// 初始化Bugly
设置Crash回调
strategy.setCrashHandleCallback(new CrashReport.CrashHandleCallback() {
/**
* Crash处理.
*
* @param crashType 错误类型:CRASHTYPE_JAVA,CRASHTYPE_NATIVE,CRASHTYPE_U3D ,CRASHTYPE_ANR
* @param errorType 错误的类型名
* @param errorMessage 错误的消息
* @param errorStack 错误的堆栈
* @return 返回额外的自定义信息上报
*/
@Override
public Map<String, String> onCrashHandleStart(int crashType, String errorType, String errorMessage, String errorStack) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("Key", "Value");
return map;
}
@Override
public byte[] onCrashHandleStart2GetExtraDatas(int crashType, String errorType, String errorMessage, String errorStack) {
try {
return "Extra data.".getBytes("UTF-8");
} catch (Exception e) {
return null;
}
}
});
27
27
1
strategy.setCrashHandleCallback(new CrashReport.CrashHandleCallback() {
2
/**
3
* Crash处理.
4
*
5
* @param crashType 错误类型:CRASHTYPE_JAVA,CRASHTYPE_NATIVE,CRASHTYPE_U3D ,CRASHTYPE_ANR
6
* @param errorType 错误的类型名
7
* @param errorMessage 错误的消息
8
* @param errorStack 错误的堆栈
9
* @return 返回额外的自定义信息上报
10
*/
11
12
public Map<String, String> onCrashHandleStart(int crashType, String errorType, String errorMessage, String errorStack) {
13
LinkedHashMap<String, String> map = new LinkedHashMap<>();
14
map.put("Key", "Value");
15
return map;
16
}
17
18
19
public byte[] onCrashHandleStart2GetExtraDatas(int crashType, String errorType, String errorMessage, String errorStack) {
20
try {
21
return "Extra data.".getBytes("UTF-8");
22
} catch (Exception e) {
23
return null;
24
}
25
}
26
27
});
两个回调返回的数据将伴随Crash一起上报到Bugly平台,并展示在附件中。
崩溃类型:
public static final int CRASHTYPE_JAVA_CRASH = 0; // Java crash
public static final int CRASHTYPE_JAVA_CATCH = 1; // Java caught exception
public static final int CRASHTYPE_NATIVE = 2; // Native crash
public static final int CRASHTYPE_U3D = 3; // Unity error
public static final int CRASHTYPE_ANR = 4; // ANR
public static final int CRASHTYPE_COCOS2DX_JS = 5; // Cocos JS error
public static final int CRASHTYPE_COCOS2DX_LUA = 6; // Cocos Lua error
7
7
1
public static final int CRASHTYPE_JAVA_CRASH = 0; // Java crash
2
public static final int CRASHTYPE_JAVA_CATCH = 1; // Java caught exception
3
public static final int CRASHTYPE_NATIVE = 2; // Native crash
4
public static final int CRASHTYPE_U3D = 3; // Unity error
5
public static final int CRASHTYPE_ANR = 4; // ANR
6
public static final int CRASHTYPE_COCOS2DX_JS = 5; // Cocos JS error
7
public static final int CRASHTYPE_COCOS2DX_LUA = 6; // Cocos Lua error
注意,需要尽量保证回调的逻辑简单和稳定,绝对不能在回调中Kill掉进程,否则会影响Crash的上报。如果需要执行类似于Crash之后Kill掉进程并重新拉起的动作,建议自定义一个Crash handler,并在初始化Bugly之前注册。
MultiDex 注意事项
如果使用了MultiDex,建议通过 Gradle 的 multiDexKeepFile 配置等方式把Bugly的类放到主Dex,另外建议在Application类的 attachBaseContext 方法中主动加载非主dex:
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(context);
Multidex.install(this);
}
}
7
7
1
public class MyApplication extends SomeOtherApplication {
2
3
protected void attachBaseContext(Context base) {
4
super.attachBaseContext(context);
5
Multidex.install(this);
6
}
7
}
上报进程控制
如果App使用了多进程且各个进程都会初始化Bugly(例如在Application类onCreate()中初始化Bugly),那么每个进程下的Bugly都会进行数据上报,造成不必要的资源浪费。
为了节省流量、内存等资源,建议初始化的时候对上报进程进行控制,只在主进程下上报数据。
String packageName = getPackageName();// 获取当前包名
String processName = getProcessName(android.os.Process.myPid());// 获取当前进程名
boolean isMainProcess = processName == null || processName.equals(packageName);//通过进程名是否为包名来判断是否是主进程
strategy.setUploadProcess(isMainProcess);//只在主进程下上报数据
5
5
1
String packageName = getPackageName();// 获取当前包名
2
String processName = getProcessName(android.os.Process.myPid());// 获取当前进程名
3
boolean isMainProcess = processName == null || processName.equals(packageName);//通过进程名是否为包名来判断是否是主进程
4
5
strategy.setUploadProcess(isMainProcess);//只在主进程下上报数据
获取进程号对应的进程名
/**
* 获取进程号对应的进程名
*
* @param pid 进程号
* @return 进程名
*/
private static String getProcessName(int pid) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline"));
String processName = reader.readLine();
if (!TextUtils.isEmpty(processName)) {
processName = processName.trim();
}
return processName;
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException exception) {
exception.printStackTrace();
}
}
return null;
}
}
29
29
1
/**
2
* 获取进程号对应的进程名
3
*
4
* @param pid 进程号
5
* @return 进程名
6
*/
7
private static String getProcessName(int pid) {
8
BufferedReader reader = null;
9
try {
10
reader = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline"));
11
String processName = reader.readLine();
12
if (!TextUtils.isEmpty(processName)) {
13
processName = processName.trim();
14
}
15
return processName;
16
} catch (Throwable throwable) {
17
throwable.printStackTrace();
18
} finally {
19
try {
20
if (reader != null) {
21
reader.close();
22
}
23
} catch (IOException exception) {
24
exception.printStackTrace();
25
}
26
}
27
return null;
28
}
29
}
CrashReport 中的其他设置
1、自定义标签
用于标明App的某个“场景”。在发生Crash时会显示该Crash所在的“场景”,以最后设置的标签为准,标签id需大于0。例:当用户进入界面A时,打上9527的标签:
CrashReport.setUserSceneTag(context, 9527); // 上报后的Crash会显示该标签
1
1
1
CrashReport.setUserSceneTag(context, 9527); // 上报后的Crash会显示该标签
打标签之前,需要在Bugly产品页配置中添加标签,取得标签ID后在代码中上报。
2、设置自定义Map参数
自定义Map参数可以保存发生Crash时的一些自定义的环境信息。在发生Crash时会随着异常信息一起上报并在页面展示。
CrashReport.putUserData(context, "userkey", "uservalue");
1
1
1
CrashReport.putUserData(context, "userkey", "uservalue");
最多可以有9对自定义的key-value(超过则添加失败);
key限长50字节,value限长200字节,过长截断;
key必须匹配正则:[a-zA-Z[0-9]]+。
3、设置开发设备
在开发测试阶段,可以在初始化Bugly之前通过以下接口把调试设备设置成“开发设备”。
CrashReport.setIsDevelopmentDevice(context, true);
1
1
1
CrashReport.setIsDevelopmentDevice(context, true);
ADT 17增加了BuildConfig特性,可以通过获取BuildConfig类的DEBUG变量来设置:
CrashReport.setIsDevelopmentDevice(context, BuildConfig.DEBUG);
1
1
1
CrashReport.setIsDevelopmentDevice(context, BuildConfig.DEBUG);
4、设置用户ID
您可能会希望能精确定位到某个用户的异常,我们提供了用户ID记录接口。例:网游用户登录后,通过该接口记录用户ID,在页面上可以精确定位到每个用户发生Crash的情况。
CrashReport.setUserId("9527"); //该用户本次启动后的异常日志用户ID都将是9527
1
1
1
CrashReport.setUserId("9527"); //该用户本次启动后的异常日志用户ID都将是9527
5、主动上报开发者Catch的异常
您可能会关注某些重要异常的Catch情况。我们提供了上报这类异常的接口。 例:统计某个重要的数据库读写问题比例。
try {
//...
} catch (Throwable thr) {
CrashReport.postCatchedException(thr); // bugly会将这个throwable上报
}
5
5
1
try {
2
//...
3
} catch (Throwable thr) {
4
CrashReport.postCatchedException(thr); // bugly会将这个throwable上报
5
}
自定义日志功能
我们提供了自定义Log的接口,用于记录一些开发者关心的调试日志,可以更全面地反应App异常时的前后文环境。使用方式与android.util.Log一致。用户传入TAG和日志内容。该日志将在Logcat输出,并在发生异常时上报
BuglyLog.i(tag, log)
1
1
1
BuglyLog.i(tag, log)
注意:
- 使用BuglyLog接口时,为了减少磁盘IO次数,我们会先将日志缓存在内存中。当缓存大于一定阈值(默认10K),会将它持久化至文件。
- 您可以通过 setCache 接口设置缓存大小,范围为0-30K。
BuglyLog.setCache(12 * 1024) //将Cache设置为12K
1
BuglyLog.setCache(12 * 1024) //将Cache设置为12K
- 如果您没有使用 BuglyLog 接口,且初始化 Bugly 时 isDebug 参数设置为 false,该 Log 功能将不会有新的资源占用。
- 为了方便开发者调试,当初始化 Bugly 的 isDebug 参数为 true 时,异常日志同时还会记录 Bugly 本身的日志。
- 请在 App 发布时将其设置为false。
- 上报Log最大30K。
Javascript 的异常捕获功能
Bugly Android SDK 1.2.8及以上版本提供了Javascript的异常捕获和上报能力,以便开发者可以感知到 WebView中发生的Javascript异常。
在WebChromeClient的onProgressChanged函数中调用接口:
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView webView, int progress) {
CrashReport.setJavascriptMonitor(webView, true);//设置Javascript的异常监控(自动注入方式)
super.onProgressChanged(webView, progress);
}
});
7
7
1
webView.setWebChromeClient(new WebChromeClient() {
2
3
public void onProgressChanged(WebView webView, int progress) {
4
CrashReport.setJavascriptMonitor(webView, true);//设置Javascript的异常监控(自动注入方式)
5
super.onProgressChanged(webView, progress);
6
}
7
});
setJavascriptMonitor 方法声明:
/**
* 设置Javascript的异常监控
*
* @param webView 指定被监控的webView
* @param autoInject 是否自动注入Bugly.js文件
* @return true 设置成功;false 设置失败
*/
CrashReport.setJavascriptMonitor(WebView webView, boolean autoInject)
8
8
1
/**
2
* 设置Javascript的异常监控
3
*
4
* @param webView 指定被监控的webView
5
* @param autoInject 是否自动注入Bugly.js文件
6
* @return true 设置成功;false 设置失败
7
*/
8
CrashReport.setJavascriptMonitor(WebView webView, boolean autoInject)
注意:
- Bugly.js 文件在Bugly SDK包中,可以在HTML手动嵌入;
- 如果使用自动集成SDK方式,可以使用自动注入和手动注入两种方式。
- 由于Android 4.4以下版本存在反射漏洞,接口默认只对Android 4.4及以上版本有效;
- 接口不会设置webView的WebViewClient和Listener;
- 接口默认会开启webView的JS执行能力;
在Bugly Android SDK捕获到Javascript异常后,默认会上报以下信息:
- Android设备的相关信息;
- Javascript异常堆栈和其他信息;
- Java堆栈;
- WebView的信息,目前只包括ContentDescription。
如果使用了非Android官方的WebView(例如使用X5内核),需要下载2.5.0或以上版本Bugly Android SDK并按照以下方法使用:
CrashReport.WebViewInterface webViewInterface = new CrashReport.WebViewInterface() {
@Override
public String getUrl() {
return webView.getUrl();//获取WebView URL
}
@Override
public void setJavaScriptEnabled(boolean flag) {
webView.getSettings().setJavaScriptEnabled(flag);//开启JavaScript.
}
@Override
public void loadUrl(String url) {
webView.loadUrl(url);//加载URL
}
@Override
public void addJavascriptInterface(H5JavaScriptInterface jsInterface, String name) {
webView.addJavascriptInterface(jsInterface, name);//添加JavaScript接口对象
}
@Override
public CharSequence getContentDescription() {
return webView.getContentDescription();//获取WebView的内容描述
}
};
26
26
1
CrashReport.WebViewInterface webViewInterface = new CrashReport.WebViewInterface() {
2
3
public String getUrl() {
4
return webView.getUrl();//获取WebView URL
5
}
6
7
8
public void setJavaScriptEnabled(boolean flag) {
9
webView.getSettings().setJavaScriptEnabled(flag);//开启JavaScript.
10
}
11
12
13
public void loadUrl(String url) {
14
webView.loadUrl(url);//加载URL
15
}
16
17
18
public void addJavascriptInterface(H5JavaScriptInterface jsInterface, String name) {
19
webView.addJavascriptInterface(jsInterface, name);//添加JavaScript接口对象
20
}
21
22
23
public CharSequence getContentDescription() {
24
return webView.getContentDescription();//获取WebView的内容描述
25
}
26
};
发生一个空指针异常时所收集的信息
崩溃列表
解决方案
解决方案
该异常表示尝试去调用virtual method,使用了一个空对象引用,建议您检查引用的对象是否为空。
[解决方案]:这种异常通常是调用一个对象的方法抛出的,凡是调用一个对象的方法之前,一定要进行判空或者进行try-catch,这样基本可以规避大部分空指针异常。
Attempt to invoke virtual method 'void android.view.View.setTranslationZ(float)' on a null object reference
android.view.View$6.setValue support 24.2.1 的bug
https://code.google.com/p/android/issues/detail?id=22499
Attempt to invoke virtual method 'android.net.NetworkInfo$State android.net.NetworkInfo.getState()' on a null object reference
ConnectivityManager.getNetworkInfo(Network) 返回了null。需检查权限以及是否机型兼容问题。
Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
最常见是在 回调接口, 如 网络请求回调,第三方登录回调 返回的时候 调用 context.startActivity 时 context 为空导致。可以在 context 为空的时候使用 Application Context, 加上
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
16
16
1
解决方案
2
该异常表示尝试去调用virtual method,使用了一个空对象引用,建议您检查引用的对象是否为空。
3
[解决方案]:这种异常通常是调用一个对象的方法抛出的,凡是调用一个对象的方法之前,一定要进行判空或者进行try-catch,这样基本可以规避大部分空指针异常。
4
5
Attempt to invoke virtual method 'void android.view.View.setTranslationZ(float)' on a null object reference
6
android.view.View$6.setValue support 24.2.1 的bug
7
https://code.google.com/p/android/issues/detail?id=22499
8
9
Attempt to invoke virtual method 'android.net.NetworkInfo$State android.net.NetworkInfo.getState()' on a null object reference
10
ConnectivityManager.getNetworkInfo(Network) 返回了null。需检查权限以及是否机型兼容问题。
11
12
Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
13
最常见是在 回调接口, 如 网络请求回调,第三方登录回调 返回的时候 调用 context.startActivity 时 context 为空导致。可以在 context 为空的时候使用 Application Context, 加上
14
if (!(context instanceof Activity)) {
15
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
16
}
版本信息、设备信息、上报趋势
最近一次上报、上报记录
出错堆栈、
跟踪数据、跟踪日志、其他信息、符号表
2018-6-6