异常捕获 腾讯 Bugly

介绍

官网    文档    SDK下载    产品列表  

腾讯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
   @Override
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
   @Override
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
  @Override
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
   @Override
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
   @Override
3
   public String getUrl() {
4
      return webView.getUrl();//获取WebView URL
5
   }
6
   
7
   @Override
8
   public void setJavaScriptEnabled(boolean flag) {
9
      webView.getSettings().setJavaScriptEnabled(flag);//开启JavaScript.
10
   }
11
   
12
   @Override
13
   public void loadUrl(String url) {
14
      webView.loadUrl(url);//加载URL
15
   }
16
   
17
   @Override
18
   public void addJavascriptInterface(H5JavaScriptInterface jsInterface, String name) {
19
      webView.addJavascriptInterface(jsInterface, name);//添加JavaScript接口对象
20
   }
21
   
22
   @Override
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




猜你喜欢

转载自www.cnblogs.com/baiqiantao/p/9145809.html
今日推荐