一、概述

CrashSDK 允许在崩溃前预先增加一些不经常改变的信息,也可以在崩溃时回调获取最新的信息,还可以缓存最近的一些信息(如用户的操作步骤或场景)等等,在崩溃时,CrashSDK 会将这些指定的内容全部写入到崩溃日志中。

本文档主要介绍如何向各类崩溃日志中,增加各 APP 自己的内容,以便更好地分析和解决问题。
一般来说,需要增加自定义内容的场景有如下几类:

  • APP 特有的比较重要的内容,如对于浏览器而言有当前打开的 url,对于需要登录的 APP 而言有当前登录的帐号等;

  • 特定崩溃问题分析时,在灰度包中加的一些分析问题用的临时信息;

  • 特定问题(并不局限于崩溃问题)生成自定义日志收集更多信息,以便优化。

在阅读本文前,请先阅读CrashSDK 接入步骤

二、如何向日志中增加内容

1. 增加头信息

头信息一般是数据量比较小,且运行过程中不会改变或者很少情况下会发生改变的信息,如用户ID、某些设置项等。

使用 CrashApi.addHeaderInfo 即可增加头信息:

  1. sCrashApi.addHeaderInfo("userId", getUserId());
  2. sCrashApi.addHeaderInfo("header key", "header value");

头信息的个数没有限制,其将为所有的日志(java, native, unexp, 自定义日志)增加。

2. 注册线程

java 和 native 崩溃日志中,默认只会将崩溃线程的栈及相关信息写到日志中。如果想知道崩溃时其它某些重要的线程(如主线程)的栈等信息,则可以调用 CrashApi.registerThread

另外,在某些 ROM 中,native 崩溃经常会因栈溢出(其它原因导致的,并不是 APP 自己的代码存在栈溢出),而无法进入 signal 处理,无法生成 native 崩溃日志,但使用 CrashApi.registerThread 为 native 日志注册线程时,CrashSDK 会为该线程做一些特殊处理,该线程出现 native 崩溃时,就不会因栈内存不足而无法生成日志。

  1. sCrashApi.registerThread(LogType.JAVA | LogType.NATIVE, null);

该接口必需在需要注册的线程内部调用,不能由 A 线程注册另一个 B 线程。

3. 增加 dump 文件

java, native, unexp 三类日志,最多可分别指定 10 个文件,在生成崩溃日志时,CrashSDK 会将注册的文件中的内容写到日志中。API 为 CrashApi.addDumpFile

  1. int logType = LogType.JAVA | LogType.NATIVE | LogType.UNEXP;
  2. DumpFileInfo info = new DumpFileInfo("/path/to/a/file", "myfile", logType);
  3. sCrashApi.addDumpFile(info);

4. 增加回调

在 CrashSDK 初始化完成后,可注册信息回调,在崩溃时,CrashSDK 回调指定接口来获取内容,将获取的内容写入到崩溃日志中。API 为 CrashApi.registerInfoCallback

  1. public static final String sCallbackTest = "test callback:";
  2. public static void registerCallback() {
  3. int logType = LogType.JAVA | LogType.NATIVE;
  4. sCrashApi.registerInfoCallback(sCallbackTest, logType);
  5. }
  6. private static class CrashClientImpl implements ICrashClient {
  7. @Override
  8. public String onGetCallbackInfo(String category) {
  9. if (sCallbackTest.equals(category)) {
  10. return "any string you want write into log";
  11. }
  12. return "":
  13. }
  14. ...
  15. }

5. 增加缓存

CrashSDK 提供了一套缓存机制,可由 APP 不断加入最新的信息,CrashSDK 只会保留最近加入的 n 条信息,在崩溃时,将这些数据写入到崩溃日志中。相关 API 为 CrashApi.createCachedInfoCrashApi.addCachedInfo

使用如下:

  1. private static final String CACHED_INFO_TEST = "test cached buffer";
  2. public static void testCachedInfo() {
  3. sCrashApi.createCachedInfo(CACHED_INFO_TEST, 10, LogType.JAVA | LogType.NATIVE);
  4. for (int i = 0; i < 15; ++i) {
  5. sCrashApi.addCachedInfo(CACHED_INFO_TEST, "data line " + i);
  6. }
  7. }

三、如何使用 native API

注意:目前 native API 不提供 CrashSDK 初始化及日志开关等接口,仅提供向日志中增加内容的相关接口。

1. 准备工作

请先参考初级接入文档的复制文件说明。

  • 先复制 crashsdk.h 到自己的工程中,再将 crashsdk.h 所在的目录,加到需要使用到 CrashSDK native API 工程的 Android.mk 中的 LOCAL_C_INCLUDES 包含目录;

  • 将当前目标编译 CPU 架构的 libcrashsdk.so 复制到对应的 “libs/$(arch)/“ 目录下,并链接 libcrashsdk.so

Android.mk 中增加如下:

  1. # 1. 加入包含目录
  2. LOCAL_C_INCLUDES += /the/full/path/that/contains/file/crashsdk_h
  3. # 2. 链接 libcrashsdk.so
  4. LOCAL_SHARED_LIBRARIES += -lcrashsdk

2. 增加信息

native 提供增加信息的 API 与 java 的 API 基本一致,可通过 native API 向 java, native, unexp 日志中增加自定义的内容。

2.1 增加头信息

native 使用 crashsdk_addHeaderInfo 增加头信息,同 java API CrashApi.addHeaderInfo

  1. crashsdk_addHeaderInfo("native HeaderInfo key", "any string as value");

2.2 注册线程

native 使用 crashsdk_registerThread 注册当前线程,同 java API CrashApi.registerThread

  1. crashsdk_registerThread(NULL, LogTypeNative | LogTypeJava);

如果当前线程为 native 启动的线程,没有 JNI 环境,则只会为 native 崩溃日志注册当前线程。

2.3 增加 dump 文件

native 使用 crashsdk_addDumpFile 增加文件,同 java API CrashApi.addDumpFile

  1. DumpFileInfo info;
  2. memset(&info, 0, sizeof(info));
  3. char tmp[32] = { 0 };
  4. snprintf(tmp, 32, "/proc/%d/stack", getpid());
  5. info.infoSize = sizeof(info);
  6. info.category = "linux kernel stack:";
  7. info.fileTobeDump = tmp;
  8. info.logType = LogTypeNative | LogTypeJava | LogTypeUnexp;
  9. info.writeCategory = true;
  10. info.deleteAfterDump = false;
  11. crashsdk_addDumpFile(&info);

2.4 增加回调

native 使用 crashsdk_registerInfoCallback 增加回调,同 java API CrashApi.registerInfoCallback

  1. const char* testInfoCallbackFunc(const char* category, LogType type, long* dataSize)
  2. {
  3. const int kBufferSize = getpagesize(); // 4KB
  4. // Do not use libc malloc or free here, which may leads crash with the libc
  5. // heap corruption. Use mmap system call to allocate a buffer is recommended.
  6. //char* buffer = (char*)malloc(kBufferSize);
  7. char* buffer = (char*)mmap(NULL, kBufferSize, PROT_READ | PROT_WRITE,
  8. MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  9. int len = snprintf(buffer, kBufferSize,
  10. "enter native callback function.\n"
  11. "category: '%s', type: %d.\n"
  12. "you can add any content as you want.",
  13. category, type);
  14. *dataSize = len;
  15. // REMIND: the buffer can not be unmap-d!
  16. return buffer;
  17. }
  18. // register info callback
  19. crashsdk_registerInfoCallback("test native callback:",
  20. LogTypeNative | LogTypeJava, testInfoCallbackFunc);

2.5 增加缓存

native 使用 crashsdk_createCachedInfocrashsdk_addCachedInfo 创建和增加缓存信息,同 java API CrashApi.createCachedInfoCrashApi.addCachedInfo

  1. const char* cachedBufferKey = "test native cached buffer:";
  2. crashsdk_createCachedInfo(cachedBufferKey, 10, LogTypeNative | LogTypeJava);
  3. char buffer[256] = { 0 };
  4. for (int i = 0; i < 15; ++i) {
  5. sprintf(buffer, "native data line %d", i);
  6. crashsdk_addCachedInfo(cachedBufferKey, buffer, strlen(buffer));
  7. }

四、自定义日志

上面所介绍的是为 CrashSDK 内置的崩溃日志类型增加自定义内容;CrashSDK 还提供生成自定义日志的功能,这类日志的所有内容完全由生成日志者决定,可为跟进解决某一类特定问题而制定一类自定义日志,也可为收集某些重要的信息而制定另一类自定义日志。

java API CrashApi.generateCustomLog 的使用示例如下:

  1. StringBuffer buffer = new StringBuffer();
  2. buffer.append("Any strings as you want write into the log from java.");
  3. CustomLogInfo info = new CustomLogInfo(buffer, "mytype");
  4. sCrashApi.generateCustomLog(info);

native API crashsdk_generateCustomLog 的使用示例如下:

  1. CustomLogInfo info;
  2. memset(&info, 0, sizeof(info));
  3. const char* datas = "Any string that you want write into the log from native";
  4. info.infoSize = sizeof(info);
  5. info.datas = datas;
  6. info.dataSize = strlen(datas);
  7. info.logType = "mytype";
  8. info.addHeader = true;
  9. info.addFooter = true;
  10. info.addLogcat = true;
  11. info.uploadNow = false;
  12. crashsdk_generateCustomLog(&info);

不过,native API 生成自定义日志所在的线程,必需有 JNI 环境,否则生成自定义日志失败。

五、应用场景示例

1. 头信息

每条头信息的内容一般只有一行,比较简洁。头信息主要适用于各 APP 定义的一些不经常变化且重要的基本信息,如用户唯一标识、当前网络环境,以及一些重要的设置项的值等信息。

2. 注册线程

一般比较重要的线程才建议注册,通常来说如主线程;还有一些干活比较多的线程,建议也注册,尤其是经常会调用到 native 代码的线程,注册后可提高该线程 native 崩溃的捕获率。

3. dump 文件

主要的场景是:某些子模块运行中会生成一些日志文件,在崩溃前将这些文件预先注册到 CrashSDK 中,在崩溃时,CrashSDK 就会将这些文件的内容写到崩溃日志中。

4. 回调

回调的应用范围比较广,基本上可以替代其它向日志中增加内容的 API,通过回调可以向日志里面写入任何内容。 不过,一般建议那些对实时性要求比较高的内容才在回调中获取,因为回调的成功率较其它的几种写入自定义内容的成功率会低一些。

通常的场景有:崩溃时打开了哪些 View、加载了哪些模块、某些易变设置项最新的状态等信息。

5. 缓存

缓存的设计最初是为了记录用户最近的操作、行为等信息,方便对崩溃问题进行重现,或者按场景对崩溃进行归类。当然也可以使用缓存记录某个模块在崩溃前最新的运行状态等信息。

6. 自定义日志

参见文档:CrashSDK 原理介绍

六、崩溃统计

CrashSDK 在开启 CustomInfo.mEnableStatReport 后,会上报崩溃率相关统计数据到啄木鸟后台;若接入的 APP 自己也需要收集一份崩溃统计数据,可使用 CrashApi.reportCrashStats 获取各类崩溃次数,或使用 CrashApi.getLastExitType 获取上次退出类型等数据。

在适配崩溃统计之前,建议先阅读崩溃统计相关的介绍文档。

1. 获取统计数据

1.1 ExitType

ExitType 中定义了几种退出类型,使用接口 CrashApi.getLastExitType 可获取上次的退出类型,如下:

  1. final int type = sCrashApi.getLastExitType();
  2. final boolean isFgCrash = (type == ExitType.FOREGROUND_CAUGHT_CRASH ||
  3. type == ExitType.FOREGROUND_UNCAUGHT_CRASH);
  4. final boolean isBgCrash = (type == ExitType.BACKGROUND_CAUGHT_CRASH ||
  5. type == ExitType.BACKGROUND_UNCAUGHT_CRASH);
  6. final boolean isCrashed = (type != ExitType.NORMAL_EXIT);
  7. final boolean hasGeneratedLog = (type == ExitType.FOREGROUND_CAUGHT_CRASH ||
  8. type == ExitType.BACKGROUND_CAUGHT_CRASH ||
  9. type == ExitType.UNKNOWN_GROUND_CAUGHT_CRASH);
  10. // TODO: report |type| to your server

服务器端,可使用崩溃类型的 ExitType 数量,除以所有类型 ExitType 的总数量,得到前台崩溃率、后台崩溃率、前台捕获崩溃率、后台捕获崩溃率等。

1.2. CrashStatKey

在调用 CrashApi.reportCrashStats 后,崩溃统计数据会通过回调接口 ICrashClient.onAddCrashStats 逐一回调。

ICrashClient 的实例必需在初始化时传入,另外也可以初始化后使用 CrashApi.registerCallback 注册独立的回调。

APP 中请求报告统计数据:

  1. private void reportCrashStats() {
  2. sCrashApi.reportCrashStats(false);
  3. }

初始化时传入的 ICrashClient 回调实例中处理统计:

  1. @Override
  2. public void onAddCrashStats(String processName, int key, int count) {
  3. Log.i(TAG, "onAddCrashStats, processName: " + processName
  4. + ", key: " + key + ", count: " + count);
  5. // TODO: report to your server
  6. }

ICrashClient.onAddCrashStats 将在 CrashApi.reportCrashStats 中同步回调。参数 keyCrashStatKey 中定义的任意常量,count 为其对应的数量,建议将所有的字段都上报。

统计数据在 CrashSDK 内部有临时缓存,只会缓存上次和本次运行过程中的统计数据,若不上报,可能会丢失。

服务器端,使用总崩溃次数、java崩溃次数、native崩溃次数等,除以 APP 启动次数 APP_START_TIMES,分别得到总崩溃率、java崩溃率、native崩溃率等数据。

2. 自定义统计字段

CrashSDK 可使用接口 CrashApi.addStatInfo 增加自定义统计项。自定义统计项将随着崩溃率统计数据一起上报到啄木鸟后台。具体见接口 CrashApi.addStatInfo 的说明。


(本文完)