ACRA源码分析总结

init()初始化

首次初始化

进行首次初始化,应用反射机制,分析app对象。

如果@ReportCrashes中没有做设置则报错,否则做第二步初始化。

init(app, new ConfigurationBuilder(app)),构造一个ConfigurationBuilder,放置@ReportCrashes中声明的设置。

public static void init(@NonNull Application app) {
final ReportsCrashes reportsCrashes = app.getClass().getAnnotation(ReportsCrashes.class);
if (reportsCrashes == null) {
log.e(LOG_TAG, "ACRA#init(Application) called but no ReportsCrashes annotation on Application " + app.getPackageName());
return;
}
init(app, new ConfigurationBuilder(app));
}

第二步初始化

进行第二步初始化,向第三步初始化增加一个布尔传入参数。

public static void init(@NonNull Application app, @NonNull ConfigurationBuilder builder) {
init(app, builder, true);
}

第三步初始化

进行第三步初始化,builder.build()
Builds the ACRAConfiguration which will be used to configure ACRA.

public static void init(@NonNull Application app, @NonNull ConfigurationBuilder builder, boolean checkReportsOnApplicationStart) {
try {
init(app, builder.build(), checkReportsOnApplicationStart);
} catch (ACRAConfigurationException e) {
log.w(LOG_TAG, "Configuration Error - ACRA not started : " + e.getMessage());
}
}

最终初始化

init()的最终初始化

public static void init(@NonNull Application app, @NonNull ACRAConfiguration config, boolean checkReportsOnApplicationStart)

init()流程

isACRASenderServiceProcess()

如果当前进程正在运行SenderService,返回True。

final boolean senderServiceProcess = isACRASenderServiceProcess();

isACRASenderServiceProcess()判断SenderService方法:
读取/proc/self/cmdline文件内容,获取进程启动时的命令行参数,根据其末尾是否带有:acra判断。
ACRA.java中与之相关的设置是:

private static final String ACRA_PRIVATE_PROCESS_NAME= ":acra"

如果senderServiceProcess为True,则说明SenderService在运行,不会捕捉异常,仅执行发送。

Not initialising ACRA to listen for uncaught Exceptions as this is the SendWorker process and we only send reports, we don’t capture them to avoid infinite loops

supportedAndroidVersion && config !=null && mApplication != null

一系列判断和检查。

ErrorReporter对象(核心部分)

In ACRA.java

建立ErrorReporter对象,传入上面的参数,捕获异常和发送报告。

errorReporterSingleton = new ErrorReporter(mApplication, configProxy, prefs,
enableAcra, supportedAndroidVersion, !senderServiceProcess);

In ErrorReporter.java

UncaughtExceptionHandler接口

ErrorReporter实现了Thread.UncaughtExceptionHandler接口,在线程死亡之前,,可以将异常传递到ErrorReporter处理器。

public class ErrorReporter implements Thread.UncaughtExceptionHandler
ErrorReporter构造器

enabled Whether this ErrorReporter should capture Exceptions and forward their reports.
listenForUncaughtExceptions Whether to listen for uncaught Exceptions.

ErrorReporter(@NonNull Application context, @NonNull ACRAConfiguration config, @NonNull SharedPreferences prefs,
boolean enabled, boolean supportedAndroidVersion, boolean listenForUncaughtExceptions)

创建一个CrashReportDataFactory对象和一个Thread.UncaughtExceptionHandler,然后设置默认的异常处理器。

//..
crashReportDataFactory = new CrashReportDataFactory(this.context, config, prefs, appStartDate, initialConfiguration);
final Thread.UncaughtExceptionHandler defaultExceptionHandler;
if (listenForUncaughtExceptions) {
defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
} else {
defaultExceptionHandler = null;
}
//Other setting.
final ReportPrimer reportPrimer = getReportPrimer(config);
reportExecutor = new ReportExecutor(context, config, crashReportDataFactory, lastActivityManager, defaultExceptionHandler, reportPrimer);
  • UncaughtExceptionHandler对象

UncaughtExceptionHandler捕获全局异常

  • setDefaultUncaughtExceptionHandler()

为所有线程安装一个默认的处理器,这里设置为this

  • uncaughtException()

实现uncaughtException()方法,处理器中真正完成处理的部分。

线程的所有未被catch的Exception被传递到这个方法,将出错的Thread和对象Throwable传入一个新建的ReportBuilder对象。

新建的ReportBuilder对象执行build方法,传入上面创建的ReportExecutor的对象reportExecutor。

public void uncaughtException(@Nullable Thread t, @NonNull Throwable e){
new ReportBuilder()
.uncaughtExceptionThread(t)
.exception(e)
.endApplication()
.build(reportExecutor);
}

In ReportBuilder.java

Assembles and sends the crash report.

build方法,调用reportExecutor的execute方法。

public void build(@NonNull ReportExecutor reportExecutor) {
if (message == null && exception == null) {
message = "Report requested by developer";
}
reportExecutor.execute(this);
}

ReportExecutor

ReportExecutor的构造器

ReportExecutor(android.content.Context context, ACRAConfiguration config,
CrashReportDataFactory crashReportDataFactory, LastActivityManager lastActivityManager,
java.lang.Thread.UncaughtExceptionHandler defaultExceptionHandler, ReportPrimer reportPrimer)

Try to send a report, if an error occurs stores a report file for a later attempt.

 public void execute(@NonNull final ReportBuilder reportBuilder) {
//..
// Prime this crash report with any extra data.
reportPrimer.primeReport(context, reportBuilder);
//..使用createCrashData 将 reportBuilder的内容传递到一个CrashReportData对象。
final CrashReportData crashReportData = crashReportDataFactory.createCrashData(reportBuilder);
//..将CrashReportData的内容写入文件。
final File reportFile = getReportFileName(crashReportData);
saveCrashReportFile(reportFile, crashReportData);
//根据不同的reportingInteractionMode比如Dialog或者TOAST等进行处理和发送
startSendingReports(sendOnlySilentReports);
//..
}

In CrashReportDataFactory.java

  • CrashReportDataFactory对象

负责给一个Exception创建CrashReportData.

Also responsible for holding the custom data to send with each report.

  • createCrashData方法

Collects crash data, return CrashReportData

public CrashReportData createCrashData(@NonNull ReportBuilder builder) 

In CrashReportData.java

Stores a crash reports data with ReportField enum values as keys.

This is basically the source of Properties adapted to extend an EnumMap(枚举映射)
instead of Hashtable and with a few tweaks to avoid losing crazy amounts
of android time in the generation of a date comment when storing to file.

CrashReportData构造器,应用反射机制,使用.class获取ReportField的Class对象

public final class CrashReportData extends EnumMap<ReportField, String>{
//..
public CrashReportData() {
super(ReportField.class);
}
public String getProperty(@NonNull ReportField key) {
return super.get(key);
}
public JSONObject toJSON() throws JSONReportException {
return JSONReportBuilder.buildJSONReport(this);
}
//..
}

createCrashData

收集数据部分。

CrashReportDataFactory.java中createCrashData方法,对于DROPBOX的收集数据部分是与logcat的收集数据部分放在一个代码块中的。

在这一部分,ACRA的代码注释中,有这样的描述:

Before JellyBean, this required the READ_LOGS permission
Since JellyBean,READ_LOGS is not granted to third-party apps anymore for security reasons.
Though, we can call logcat without any permission and still get traces related to our app.

可见,如果Android版本高于JellyBean,无需配置READ_LOGS权限,即可读取 logcat与当前App相关的信息;然而,对于是否能够读取Drop Box的信息,ACRA的源码中并没有提及。

实测,debug信息中有输出READ_LOGS granted! ACRA can include LogCat and DropBox data.,并且没有报错Error while retrieving DROPBOX data ,证明下面的代码可以正常执行到DropboxCollector().read()的地方。

final boolean hasReadLogsPermission = pm.hasPermission(Manifest.permission.READ_LOGS) || Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
if (prefs.getBoolean(ACRA.PREF_ENABLE_SYSTEM_LOGS, true) && hasReadLogsPermission) {
if (ACRA.DEV_LOGGING)
ACRA.log.d(LOG_TAG, "READ_LOGS granted! ACRA can include LogCat and DropBox data.");
if (crashReportFields.contains(DROPBOX)) {
try {
crashReportData.put(DROPBOX, new DropBoxCollector().read(context, config));
} catch (RuntimeException e) {
ACRA.log.e(LOG_TAG, "Error while retrieving DROPBOX data", e);
}
//..
}
  • In DropBoxCollector.java

构造器中设置SYSYTEM_TAGS等。

final class DropBoxCollector {
private static final String[] SYSTEM_TAGS = {"system_app_anr", "system_app_wtf", "system_app_crash",
"system_server_anr", "system_server_wtf", "system_server_crash", "BATTERY_DISCHARGE_INFO",
"SYSTEM_RECOVERY_LOG", "SYSTEM_BOOT", "SYSTEM_LAST_KMSG", "APANIC_CONSOLE", "APANIC_THREADS",
"SYSTEM_RESTART", "SYSTEM_TOMBSTONE", "data_app_strictmode"};
private static final String NO_RESULT = "N/A"
//...
}

read()方法,提取drop box中的信息,返回字符串。主要步骤如下:

使用一个ArrayList对象保存设置的SYSYTEM_TAGS和additionalTags,进行提取信息的操作,如果捕捉到异常,则输出DropBoxManager not available.

public String read(@NonNull Context context, @NonNull ACRAConfiguration config) {
try {
final DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
final List<String> tags = new ArrayList<String>();
if (config.includeDropBoxSystemTags()) {
tags.addAll(Arrays.asList(SYSTEM_TAGS));
}
final StringBuilder dropboxContent = new StringBuilder();
//..for(.....) add
}
return dropboxContent.toString();
}catch (Exception e) {
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "DropBoxManager not available.");
}
return NO_RESULT;
}

执行for循环,先调用getNextEntry(String tag, long msec) 从drop box获取在特定时间mesc后的下一个entry。

如果entry不为null,则调用 getText(int maxBytes) 返回entry的uncompressed text contents,追加到最终返回的字符串中,MaxBytes为返回String的最大值。

final StringBuilder dropboxContent = new StringBuilder();
for (String tag : tags) {
dropboxContent.append("Tag: ").append(tag).append('\n');
DropBoxManager.Entry entry = dropbox.getNextEntry(tag, time);
if (entry == null) {
dropboxContent.append("Nothing.").append('\n');
continue;
}
while (entry != null) {
final long msec = entry.getTimeMillis();
calendar.setTimeInMillis(msec);
dropboxContent.append('@').append(dateFormat.format(calendar.getTime())).append('\n');
final String text = entry.getText(500);
if (text != null) {
dropboxContent.append("Text: ").append(text).append('\n');
} else {
dropboxContent.append("Not Text!").append('\n');
}
entry.close();
entry = dropbox.getNextEntry(tag, msec);
}
}

实测的Debug结果显示,运行过程输出DropBoxManager not available. 证明在read()方法中出现异常,导致无法正常获取Drop Box的信息。

OnSharedPreferenceChangeListener

We HAVE to keep a reference otherwise the listener could be garbage.

mPrefListener = new OnSharedPreferenceChangeListener()