崩溃处理,ACRA核心流程简析

ACRA

2016/10/02
上海
Read the fucking source code。

    ACRA Application Crash Reports for Android 。
    GitHub:https://github.com/ACRA

(文章基于ACRA v4.8.0)
ACRA目的简单明了,收集App崩溃堆栈信息,生成报告并发送到指定端。

通俗的崩溃处理流程:
1,实现Thread.UncaughtExceptionHandler接口,自定义崩溃处理器。
2,保留系统默认Thread.UncaughtExceptionHandler接口(通过Thread.getDefaultUncaughtExceptionHandler()获得),
   当自定义处理器无法分析崩溃异常时,提交给默认处理器解决。
3,绑定自定义处理器(通过Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)方法)
4,崩溃,自定义处理器捕捉崩溃异常,生成报表。
5,处理报表(我习惯发送到静态BroadcastReceiver处理报表并重启应用,尝试将报表发送到服务器,若发送失败,则写入本地文件)
ACRA核心类:
ACRA    ACRA入口类
ACRAConfiguration    ACRA配置器
ConfiguraitonBuilder    配置器的构造器
ErrorReporter    崩溃堆栈捕捉线程(实现Thread.UncaughtExceptionHandler)
ReportBuilder    错误报表生成器
SendService    报表提交服务(处于ErrorReporter与ReportSender之间)
SenderServiceStarter    SendService帮助类
ReportSender    错误报表发送器
ReportSenderFactory    ReportSender工厂类

ACRA初始化:

//初始化三种方式
ACRA.init(Application app);
ACRA.init(Application app, ACRAConfiguration config);
//ACRAConfiguration是ACRA配置器,与注解是相同功能,ACRAConfiguration优先于注解
//checkReportsOnApplicationStart,这个参数决定了三件事:
  1,是否删除上个版本未发送的错误报告,
  2,是否删除用户未批准发送的错误报告,
  3,是否尝试发送之前发送失败的错误报表。
ACRA.init(Application app, ACRAConfiguration config, boolean checkReportsOnApplicationStart);
//省略版本适配,优化等代码
public static void init(Application app, ACRAConfiguration config, boolean checkReportsOnApplicationStart){
    //判断ACRA是否处于运行状态
    final boolean senderServiceProcess = isACRASenderServiceProcess(app);
    //android 系统版本是否>=8
    boolean supportedAndroidVersion = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO);

    mApplication = app;
    configProxy = config;
    try {
    ...
        省略ACRA版本适配相关,ACRA版本升级,本地错误报表转移
    ...

        //checkCrashResources()方法检查注释中的相关字段是否完整,Toast模式必须设置ToastText字段等..
        config.checkCrashResources();
        //是否加载Acra
        final boolean enableAcra = supportedAndroidVersion && !shouldDisableACRA(prefs);
        //创建ErrorReporter实例
        errorReporterSingleton = new ErrorReporter(mApplication, configProxy, prefs, enableAcra, supportedAndroidVersion, !senderServiceProcess);
        //
        if (checkReportsOnApplicationStart && !senderServiceProcess) {
            //ApplicationStartupProcessor 用于每次ACRA初始化时,查找任何现有的报告并发送。
            final ApplicationStartupProcessor startupProcessor = new ApplicationStartupProcessor(mApplication,  config);
            //如果配置器中设定删除App上个版本未发送的错误报表
            if (config.deleteOldUnsentReportsOnApplicationStart()) {
                //则删除
                startupProcessor.deleteUnsentReportsFromOldAppVersion();
            }
            //如果配置器中设定删除用户未批准发送的错误报告
            if (config.deleteUnapprovedReportsOnApplicationStart()) {
                //则删除
                startupProcessor.deleteAllUnapprovedReportsBarOne();
            }
            //如果可以启动ACRA
            if (enableAcra) {
              //尝试发送之前发送失败的错误报表。                
              startupProcessor.sendApprovedReports();
            }
        }
    } catch (ACRAConfigurationException e) {
        log.w(LOG_TAG, "Error : ", e);
    }
    .......
}

ErrorReporter,崩溃堆栈捕捉线程

public class ErrorReporter implements Thread.UncaughtExceptionHandler

ErrorReporter初始化:

//省略版本适配,优化等代码
ErrorReporter(Application context, ACRAConfig config, SharedPreferences prefs, boolean enabled, boolean supportedAndroidVersion, boolean listenForUncaughtExceptions) {
    this.context = context;
    this.config = config;
    this.supportedAndroidVersion = supportedAndroidVersion;
    
......
    //获取系统默认异常捕捉线程
    defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
    //将ErrorReporter设置为默认异常捕捉线程
    Thread.setDefaultUncaughtExceptionHandler(this);

......
}

ErrorReporter异常处理:

@Overridepublic void uncaughtException(Thread t, Throwable e) {
    ......
        //出现异常,利用错误报表构造器,生成错误报表
        new ReportBuilder()
             //ReportBuilder内部持有崩溃数据t实例
            .uncaughtExceptionThread(t)
            //ReportBuilder内部持有崩溃数据e实例
            .exception(e)
            //ReportBuilder内部有app是否结束的标示
            .endApplication()
            //构建
            .build(reportExecutor);
    ......
}
public void build(ReportExecutor reportExecutor) {
    .....
    //通过错误报表构造器,分析错误堆栈,并写入本地文件
    reportExecutor.execute(ReportBuilder.this);
}
public void execute(final ReportBuilder reportBuilder) {
    ...分析并写入本地,再根据配置,显示对应的提示...
    //发送错误报表
    startSendingReports(是否是静默发送, 是否直接批准发送);
}
private void startSendingReports(boolean onlySendSilentReports, boolean approveReportsFirst) {
......
        //创建SenderServiceStarter 对象
        final SenderServiceStarter starter = new SenderServiceStarter(context, config);
        //调用startService方法
        starter.startService(onlySendSilentReports, approveReportsFirst);
......
    } 
}
//启动SenderService服务
public void startService(boolean onlySendSilentReports, boolean approveReportsFirst) {
    final Intent intent = new Intent(context, SenderService.class);
    //存入是否静默发送
    intent.putExtra(SenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, onlySendSilentReports);
    //存入是否直接批准发送
    intent.putExtra(SenderService.EXTRA_APPROVE_REPORTS_FIRST, approveReportsFirst);
    //存入发射器工厂类的Class对象,此对象为ReportSenderFactory实例
    intent.putExtra(SenderService.EXTRA_REPORT_SENDER_FACTORIES, config.reportSenderFactoryClasses());
    //存入ACRA配置器
    intent.putExtra(SenderService.EXTRA_ACRA_CONFIG, config);
    //启动SenderService
    context.startService(intent);
}

SenderService继承自IntentService

public class SenderService extends IntentService

ReportSenderFactory接口

public interface ReportSenderFactory {
     //返回ReportSender 实例
    ReportSender create(Context context, ACRAConfig config);
}

ReportSender 接口

public interface ReportSender {
    //处理错误报表
    void send(Context context, CrashReportData errorContent) throws ReportSenderException;
}
//启动IntentService,调用startService(intent);intent都会传入此方法
@Overrideprotected void onHandleIntent(final Intent intent) {
    //是否静默发送
    final boolean onlySendSilentReports = intent.getBooleanExtra(EXTRA_ONLY_SEND_SILENT_REPORTS, false);
    //是否直接批准发送
    final boolean approveReportsFirst = intent.getBooleanExtra(EXTRA_APPROVE_REPORTS_FIRST, false);
    //发射器工厂类
    final Class<? extends ReportSenderFactory>[] senderFactoryClasses =            (Class<? extends ReportSenderFactory>[]) intent.getSerializableExtra(EXTRA_REPORT_SENDER_FACTORIES);
    //ACRA配置器
    final ACRAConfig config = (ACRAConfig) intent.getSerializableExtra(EXTRA_ACRA_CONFIG);
    try {
        //配置器ReportSenderFactory可以配置多个,所以ReportSender实例的个数与ReportSenderFactory个数相同,getSenderInstances方法通过ReportSenderFactory.Class创建ReportSenderFactory实例,并创建ReportSender,返回ReportSender列表
        final List<ReportSender> senderInstances = getSenderInstances(config, senderFactoryClasses);
        ......
        // 获取本地所有错误报表
        final File[] reports = locator.getApprovedReports();
        //ReportDistributor 用于向所有ReportSender实例分发报告
        final ReportDistributor reportDistributor = new ReportDistributor(this, config, senderInstances);
        for (final File report : reports) {
......
            //向所有ReportSender实例分发报告
            reportDistributor.distribute(report);
......
        }
    } catch (Exception e) {
        ......
    }
}
public void distribute(File reportFile) {
......
        sendCrashReport(reportFile格式化后);
......
}
//CrashReportData 为错误信息格式化后的对象
public void sendCrashReport(CrashReportData errorContent) {
......
  //遍历所有ReportSender 实例
  for (ReportSender sender : reportSenders) {
......
        //通过ReportSender 实例发送错误报告
        sender.send(context, errorContent);
......
  }
......
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容