再見(jiàn)友盟训措!Acra詳細(xì)分析 -Part 1

概述

Acra是老牌的bug自動(dòng)采集系統(tǒng)。接入sdk后,可以實(shí)現(xiàn)程序崩潰自動(dòng)發(fā)送崩潰日志。
發(fā)送自定義的錯(cuò)誤日志等功能蒜焊。具體詳細(xì)介紹可以參見(jiàn)acra官網(wǎng)地址唆迁。
整體來(lái)看,Acra就是通過(guò)sdk收集進(jìn)程的崩潰日志,然后以http或mail(默認(rèn)的兩類Sender)的方式將數(shù)據(jù)發(fā)送出去。服務(wù)器則是一套基于json的restful的接口抄罕。
服務(wù)端方面不是本次分析重點(diǎn),暫不進(jìn)行分析冒晰。
本系列文章將基于Acra 4.9.0 RC2源碼進(jìn)行分析。

Backend

服務(wù)端方面我們需要先搭建一個(gè)server,才能更好的看到我們的崩潰信息询枚,
更直觀的看到acra給我們提供了哪些針對(duì)崩潰的采集內(nèi)容。
官方提供了acralyzer以及一些針對(duì)acra的第三方開(kāi)源實(shí)現(xiàn)尝胆。
關(guān)于世面上常用的server端的贪染,該文章做了明確分析因妙,針對(duì)不同backend的比較
官方backend acralyzer的搭建非常簡(jiǎn)單,具體可以參見(jiàn)該文章的server配置部分
項(xiàng)目搭建完成后可以使用通過(guò)如下的url對(duì)server端進(jìn)行訪問(wèn)跷车。

查看app崩潰的表結(jié)構(gòu)
http://ip:port/_utils/

utils.png

查看崩潰日志
http://ip:port/acralyzer/_design/acralyzer/index.html#/dashboard/

analyze dashboard.png

關(guān)于server端的介紹結(jié)束。不是重點(diǎn)。

Client

項(xiàng)目構(gòu)建

最新版本項(xiàng)目基于Gradle構(gòu)建,了解Acra歷史的肯定知道該項(xiàng)目是存在了很久了.
Android世界中項(xiàng)目最早是基于ant構(gòu)建,后來(lái)是maven,現(xiàn)在是Gradle。
在沒(méi)有Gradle的編譯環(huán)境之前,基本上大部分是基于maven構(gòu)建核偿。
查看最新版本的代碼可以看到仍然包含了之前maven的配置文件。
并且使用Gradle編譯編譯中使用到的version name等配置參數(shù)也都是從pom.xml中讀取的。
具體可以參看build.gradle中關(guān)于版本號(hào)的相關(guān)配置。

需要注意的是,從github clone下來(lái)的項(xiàng)目是無(wú)法直接使用Gradle進(jìn)行編譯的。
熟悉Gradle android 編譯流程的人應(yīng)該從build.gradle文件中可以找出錯(cuò)誤的原因。
具體的編譯文件需要修改的地方為,在build.gradle中開(kāi)頭位置添加編譯android項(xiàng)目使用到的plugin。
如下所示:

//此部分添加到build.gradle開(kāi)頭
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
    }
}
allprojects {
    repositories {
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}
//此部分添加到build.gradle開(kāi)頭

添加之后肮柜,就可以執(zhí)行gradle build命令打出需要使用的aar包。


項(xiàng)目配置及使用

首先需要注意一點(diǎn),Acra使用獨(dú)立進(jìn)程:acra,進(jìn)行采集數(shù)據(jù)的發(fā)送仰剿,保證當(dāng)app崩潰時(shí)誊酌,采集仍然能發(fā)送出去。
由于使用獨(dú)立的進(jìn)程,所以會(huì)導(dǎo)致application被實(shí)例化多次,這樣就需要注意app自身的某些業(yè)務(wù)邏輯,不要在application類中執(zhí)行多次毙替,從而導(dǎo)致app產(chǎn)生bug拷邢。
對(duì)Acra的相關(guān)配置一般在application中進(jìn)行初始化。

初始化配置

在application中進(jìn)行初始化配置。

  1. 使用注解初始化
    import org.acra.*;
    import org.acra.annotation.*;

    @ReportsCrashes(
        formUri = "http://www.backendofyourchoice.com/reportpath"
    )
    public class MyApplication extends Application {
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            // 調(diào)用init方法,對(duì)acra進(jìn)行初始化.
            ACRA.init(this);
        }
    }
  1. 動(dòng)態(tài)初始化配置
    import org.acra.ACRA;
    import org.acra.configuration.*;

    public class MyApplication extends Application {
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            //使用ConfigurationBuilder構(gòu)建ACRAConfirueation
            final ACRAConfiguration config = new ConfigurationBuilder(this)
                .setFoo(foo)
                .setBar(bar)
                .build();
            // 傳參的方式初始化acra
            ACRA.init(this, config);
        }
    }

一般使用acra我們的目的是采集崩潰梯找,所以需要在manifest中申請(qǐng)網(wǎng)絡(luò)權(quán)限久免,以保證crash的正常發(fā)送。
<uses-permission android:name="android.permission.INTERNET"/>

目標(biāo)服務(wù)器配置

acra中發(fā)送crash數(shù)據(jù)是通過(guò)Sender實(shí)現(xiàn)的氨淌,Sender是通過(guò)ReportSenderFactory實(shí)例化出來(lái)的豪筝。
而ReportSenderFactory是可以在初始化時(shí)進(jìn)行配置的团搞。
acra默認(rèn)提供了email及http 兩種sender。
如果自定義Sender則需要兩個(gè)步驟,

  1. 實(shí)現(xiàn)ReportSender接口,用來(lái)執(zhí)行發(fā)送報(bào)告操作洪鸭。
  2. 實(shí)現(xiàn)ReportSenderFactory接口镇饮,用來(lái)創(chuàng)建自定義sender嘶是。
public class YourOwnSender implements ReportSender {
    @Override
    public void send(Context context, CrashReportData report) throws ReportSenderException {
        // 遍歷 CrashReportData 并做發(fā)送操作
    }
}

public class YourOwnSenderfactory implements ReportSenderFactory {
    // 由于在SenderService中通過(guò)Class.newInstance()來(lái)實(shí)例化對(duì)象
    // 所以需要保證實(shí)例化的類的構(gòu)造函數(shù)有一個(gè)默認(rèn)無(wú)參的構(gòu)造函數(shù)
    // 自定義的ReportSenderFactory必須包含一個(gè)不含參數(shù)的構(gòu)造函數(shù)
    public ReportSender create(Context context, ACRAConfiguration config) {
        ...
        return new YourOwnSender(someConfigPerhaps);
    }
}

針對(duì)Sender的配置有兩種形式,一種為注解酝蜒,一種為通過(guò)代碼進(jìn)行設(shè)置邀跃。

//注解的方式設(shè)置Sender
@ReportCrashes{
   reportSenderFactoryClasses = {
        your.funky.ReportSenderFactory.class, 
        other.funky.ReportSenderFactory.class
   } 
}
public class YourApplication extends Application {
   ...
}

//代碼的方式設(shè)置Sender
@ReportCrashes{
   ...
}
public class YourApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        final Class<? extends ReportSenderFactory>[] myReportSenderFactoryClasses = ...

        // 初始化一個(gè)ConfigurationBuilder,并設(shè)置ReportSenderFactoryClasses.
        final ACRAConfiguration config = new ConfigurationBuilder(this)
            .setReportSenderFactoryClasses(myReportSenderFactoryClasses)
            .build();
        ACRA.init(this, config);
    }
}

Acra中默認(rèn)提供兩個(gè)Sender

  1. HttpSender
    • 提供了Post及Put兩種提交crash到服務(wù)器的方式丽涩。
    • 提交的類型可以為JSON或Form表單兩種方式。

建議使用Put方式進(jìn)行提交。
Put可以理解成已經(jīng)知道了某個(gè)資源的位置.代表直接更新或創(chuàng)建該資源。
POST為不知道某個(gè)資源的位置,由server端來(lái)決定對(duì)該資源進(jìn)行何種方式的存儲(chǔ)幌甘。
所以在此場(chǎng)景下使用Put操作更合適皱埠,因?yàn)槊恳粭lbug實(shí)際上就應(yīng)該對(duì)應(yīng)與數(shù)據(jù)庫(kù)中的一條训枢,
只是該條記錄還沒(méi)有上傳到服務(wù)器。
關(guān)于post與put的差別,具體可以查看該文檔when should use PUT and when should use POST

  1. EmailIntentSender
    組拼crash Report 通過(guò)intent調(diào)用系統(tǒng)提供的發(fā)送email的app。

流程分析及重點(diǎn)類分析

初始化設(shè)置流程

Acra的初始化函數(shù)為init,所以使用入口函數(shù)ACRA.init()對(duì)acra進(jìn)行初始化。
一般入口函數(shù)在application初始化時(shí)進(jìn)行調(diào)用。

ACRA.init()

使用ReportsCrashes來(lái)初始化Acra。
ACRA提供多個(gè)init方法,經(jīng)過(guò)內(nèi)部調(diào)用,最終都會(huì)調(diào)用參數(shù)最多的init方法完成初始化相關(guān)邏輯。
下面對(duì)重要的init方法進(jìn)行說(shuō)明

class ACRA {
    //使用Application的注解進(jìn)行初始化
    public static void init(Application app){
        //獲取application上的注解
        final ReportsCrashes reportsCrashes = 
            app.getClass().getAnnotation(ReportsCrashes.class);
        //ConfigurationBuilder中通過(guò)注解獲取application上配置的注解信息
        init(app, new ConfigurationBuilder(app).build());
    } 
    //參數(shù) checkReportsOnApplicationStart 表示
    //是否立即執(zhí)行ErrorReporter.checkReportsOnApplicationStart()方法
    public static void init(Application app, ACRAConfiguration config, boolean checkReportsOnApplicationStart){
        //根據(jù)process的名字判斷執(zhí)行當(dāng)前方法執(zhí)行時(shí)所在的進(jìn)程是否是發(fā)送crash的進(jìn)程
        final boolean senderServiceProcess = isACRASenderServiceProcess(app);
        //ACRA只支持2.3以上的系統(tǒng)版本姐霍,所以預(yù)先做判斷
        final boolean supportedAndroidVersion = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
        //保存config
        configProxy = config;
        //獲取ACRA保存配置的SharedPreferences
        final SharedPreferences prefs = new SharedPreferencesFactory(mApplication, configProxy).create();
        if (!prefs.getBoolean(PREF__LEGACY_ALREADY_CONVERTED_TO_4_8_0, false)) {
            //處理之前的版本的日志文件
        }
        errorReporterSingleton = new ErrorReporter(mApplication, configProxy, prefs, enableAcra, supportedAndroidVersion, !senderServiceProcess);   
        //當(dāng)在非Sender進(jìn)程介衔,并設(shè)置app啟動(dòng)時(shí)發(fā)送report的情況下進(jìn)行檢測(cè)寒波。
        //當(dāng)在Sender進(jìn)程中级野,不需要進(jìn)行檢測(cè),因?yàn)镾ender進(jìn)程中的邏輯自己會(huì)進(jìn)行判斷處理
        if (checkReportsOnApplicationStart && !senderServiceProcess) {
        //執(zhí)行發(fā)送的相關(guān)業(yè)務(wù)邏輯
            final ApplicationStartupProcessor startupProcessor = 
                new ApplicationStartupProcessor(mApplication,  config);
                if (config.deleteOldUnsentReportsOnApplicationStart()) {
                    startupProcessor.deleteUnsentReportsFromOldAppVersion();
                }
                if (config.deleteUnapprovedReportsOnApplicationStart()) {
                    startupProcessor.deleteAllUnapprovedReportsBarOne();
                }
                if (enableAcra) {
                    startupProcessor.sendApprovedReports();
                }
         }
    }
}

ConfigurationBuilder

主要用來(lái)封裝構(gòu)造ACRAConfiguration的相關(guān)屬性潜索。
提供了兩種方式來(lái)設(shè)置相關(guān)屬性的值玩焰。

  1. 構(gòu)造函數(shù)通過(guò)注解的方式蔓榄,獲取Application中定義注解的值,進(jìn)行設(shè)置荤西。
  2. 通過(guò)set方法,設(shè)置每個(gè)不同的配置項(xiàng)。

獲取屬性值之后咬荷,通過(guò)調(diào)用build()方法唇牧,創(chuàng)建ACRAConfiguration對(duì)象。

//通過(guò)app的注解所配置的值對(duì)builder對(duì)象本身進(jìn)行初始化
public ConfigurationBuilder(@NonNull Application app) 
{
    //.....
}
//構(gòu)建ACRAConfiguration對(duì)象
public ACRAConfiguration build() {
    return new ACRAConfiguration(this);
}

....
//對(duì)外提供的設(shè)置相關(guān)屬性的方法
public ConfigurationBuilder setHttpHeaders(@NonNull Map<String, String> headers) {
    this.httpHeaders.clear();
    this.httpHeaders.putAll(headers);
    return this;
} 

可能有些同學(xué)不太清楚注解的相關(guān)知識(shí),可以參考該文章注解知識(shí)的介紹

ACRAConfiguration

用來(lái)保存ACRA涉及到的所有配置。

SharedPreferencesFactory

用來(lái)獲取ACRA所使用的SharedPreferences的文件。
通過(guò)這層封裝可以對(duì)sp進(jìn)行一些自定義的設(shè)置,比如sp的名字。

public class SharedPreferencesFactory {
    //獲取默認(rèn)sharedPreferences的流程為
    //1.如果通過(guò)builder或ReportsCrashes配置所構(gòu)建的類生成的config文件,
    //  包含sp相關(guān)配置,則使用該配置項(xiàng)。
    //2.如果不滿足1的條件,則通過(guò)android api PreferenceManager返回默認(rèn)的sp文件
    public SharedPreferences create() {
        if (context == null) {
        //..
        } else if (!"".equals(config.sharedPreferencesName())) {
            return context.getSharedPreferences(
                config.sharedPreferencesName(), config.sharedPreferencesMode()
            );
        } else {
            return PreferenceManager.getDefaultSharedPreferences(context);
        }
    }
}

ErrorReporter

ACRA最核心的類,該類用來(lái)捕獲crash相關(guān)的信息,以及發(fā)送crash信息。
Android平臺(tái)如果想要捕獲java層代碼的crash需要設(shè)置application Thread的UncaughtExceptionHandler。
ACRA會(huì)將ErrorReporter設(shè)置為Application Thread的UncaughtExceptionHandler。
從而實(shí)現(xiàn)對(duì)異常的捕獲兵拢。

這里有一點(diǎn)需要注意的嘹履,Thread中的defaultUncaughtHandler為一個(gè)對(duì)象焰枢,
所以多次設(shè)置該屬性,則會(huì)使用最后一個(gè)作為異常捕獲的類。
比如現(xiàn)在市面上比較火的umeng等相關(guān)包含崩潰采集功能sdk岩喷。
使用的時(shí)候偷霉,需要注意查看文檔或反編譯其源碼,查看sdk是怎么實(shí)現(xiàn)該部分功能的赞警。
否則容易造成先設(shè)置的異常捕獲類,無(wú)法被執(zhí)行旁瘫。

public class ErrorReporter implements Thread.UncaughtExceptionHandler {
    ErrorReporter(
        @NonNull Application context, @NonNull ACRAConfiguration config, 
        @NonNull SharedPreferences prefs,boolean enabled, 
        boolean supportedAndroidVersion, boolean listenForUncaughtExceptions)
    {
        ...
        //通過(guò)ConfigurationCollector獲取系統(tǒng)的相關(guān)環(huán)境信息
         if (config.getReportFields().contains(ReportField.INITIAL_CONFIGURATION)) {
            initialConfiguration = ConfigurationCollector.collectConfiguration(this.context);
        } else {
            initialConfiguration = null;
        }
        //獲取系統(tǒng)時(shí)間,崩潰發(fā)生時(shí)上傳
        final Calendar appStartDate = new GregorianCalendar();
        crashReportDataFactory = new CrashReportDataFactory(
            this.context, config, prefs, appStartDate, initialConfiguration);
        final Thread.UncaughtExceptionHandler defaultExceptionHandler;
        //listenForUncaughtExceptions為Acra初始化流程中傳過(guò)來(lái)的榨了。
        //如果當(dāng)前運(yùn)行的進(jìn)程是Sender進(jìn)程則不監(jiān)聽(tīng)崩潰。
        //如果當(dāng)前運(yùn)行的進(jìn)程是app主進(jìn)程則對(duì)崩潰進(jìn)行監(jiān)聽(tīng)
        if (listenForUncaughtExceptions) {
            defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler(this);
        } else {
            defaultExceptionHandler = null;
        }
        //記錄最后的activity
        final LastActivityManager lastActivityManager = new LastActivityManager(this.context);
        //用來(lái)保存針對(duì)崩潰的一些用戶自定義的信息
        final ReportPrimer reportPrimer = getReportPrimer(config);
        
        reportExecutor = new ReportExecutor(
            context, config, crashReportDataFactory, 
            lastActivityManager, defaultExceptionHandler, reportPrimer);
        reportExecutor.setEnabled(enabled);
    }
    
    //崩潰采集需要實(shí)現(xiàn)UncaughtExceptionHandler為接口。
    @Override
    public void uncaughtException(@Nullable Thread t, @NonNull Throwable e) {
        //未開(kāi)啟crash采集時(shí),使用之前默認(rèn)的ExceptionHandler處理
        if (!reportExecutor.isEnabled()) {
            reportExecutor.handReportToDefaultExceptionHandler(t, e);
            return;
        }
        try {
            ACRA.log.e(LOG_TAG, "ACRA caught a " + e.getClass().getSimpleName() +
                " for " + context.getPackageName(), e);
            if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Building report");
            performDeprecatedReportPriming();
            // 生成并發(fā)送report
            new ReportBuilder()
                .uncaughtExceptionThread(t)
                .exception(e)
                .endApplication()
                .build(reportExecutor);
        } catch (Throwable fatality) {
            // ACRA failed. Prevent any recursive call to ACRA.uncaughtException(), let the native reporter do its job.
            ACRA.log.e(LOG_TAG, "ACRA failed to capture the error - handing off to native error reporter" , fatality);
            reportExecutor.handReportToDefaultExceptionHandler(t, e);
        }
    }
    
}

參見(jiàn)代碼可以知道丑念,acra通過(guò)設(shè)置默認(rèn)ExceptionHandler來(lái)捕獲異常。
并把自己設(shè)置為處理對(duì)象推正。

LastActivityManager

是用來(lái)記錄最后展示的Activity的植榕,通過(guò)application.registerActivityLifecycleCallbacks來(lái)實(shí)現(xiàn)記錄功能的尊残。ACRA可以在崩潰的時(shí)候彈出Dialog寝衫,所以需要記住最后的Activity拐邪。

ReportExecutor

主要業(yè)務(wù)邏輯關(guān)注execute()方法.
該類主要負(fù)責(zé)調(diào)用CrashReportDataFactory采集數(shù)據(jù)扎阶,
調(diào)用CrashReportPersister對(duì)崩潰數(shù)據(jù)進(jìn)行持久化乘陪,
調(diào)用SenderServiceStarter運(yùn)行Service發(fā)送的報(bào)告啡邑。

ApplicationStartupProcessor

封裝一些App啟動(dòng)時(shí)可能執(zhí)行的任務(wù)

class ApplicationStartupProcessor{
    void deleteUnsentReportsFromOldAppVersion(){
        //app版本更新后戚绕,一般會(huì)修掉老的崩潰等問(wèn)題,
        //所以當(dāng)老版本更新到新版本后,可以將老版本記錄的日志全部刪除掉
    }
    
    void deleteAllUnapprovedReportsBarOne(){
        //unapproved的文件夾內(nèi)的文件鸵钝,只保留最新創(chuàng)建的日志文件韧献,其他的全部刪除掉绘证。
    }
    
    void sendApprovedReports(){
        //調(diào)用SenderServiceStarter開(kāi)啟Service進(jìn)行崩潰日志的發(fā)送。
    }
    
}

ReportLocator

關(guān)于ACRA對(duì)日志文件位置的處理主要是ReportLocator來(lái)設(shè)置的。
acra內(nèi)部使用文件對(duì)崩潰日志進(jìn)行保存肠缨,該類用來(lái)獲取文件夾的名字闷袒。
內(nèi)部有兩個(gè)文件夾acra-unapproved(未處理),acra-approved(處理過(guò))分別用來(lái)保存未處理及處理過(guò)的崩潰文件列疗。


采集內(nèi)容

崩潰采集斥赋,必然需要采集崩潰及手機(jī)的相關(guān)信息。
ACRA中涉及到崩潰相關(guān)信息的主要有如下一些類吨悍。
ReportBuilder,ReportPrimer,CrashReportDataFactory,CrashReportData,
LogCatCollector,DropBoxCollector,ReportUtils,UUID,
Installation,ConfigurationCollector,DumpSysCollector,ReflectionCollector,
DisplayManagerCollector,DeviceFeaturesCollector,settingsCollector,
LogFileCollector,MediaCodecListCollector,ThreadCollector.
ACRA獲取全部數(shù)據(jù)焰手,涉及到的類比較多。下面逐個(gè)分析犹褒。

ReportBuilder

對(duì)throwable,message,自定義信息,以及exception的簡(jiǎn)單封裝哪亿。
主要方法為build()板辽,通過(guò)build方法調(diào)用ReportExecutor.execute()方法,
在ReportExcutor中進(jìn)行真正的crash采集以及調(diào)用發(fā)送Service

ReportPrimer

用來(lái)設(shè)置崩潰時(shí)候纲仍,用戶需要保存的一些用戶自定義的信息沸版。
比如崩潰時(shí)候在此類中設(shè)置一些用戶賬號(hào)等相關(guān)信息茴肥。
該類中設(shè)置的相關(guān)內(nèi)容會(huì)一起發(fā)送到服務(wù)端,從而更好的定位一些崩潰信息财破。

CrashReportDataFactory绽诚,CrashReportData

CrashReportDataFactory類用來(lái)實(shí)例化CrashReportData贾费。
其中最重要的方法為createCrashData()方法,使用該方法來(lái)組拼CrashReportData。
CrashReportData繼承EnumMap,其中保存的數(shù)據(jù)的key為各種上傳時(shí)候的key滨嘱,
對(duì)應(yīng)的值為崩潰的相關(guān)信息。后面的流程中該類中的值會(huì)通過(guò)CrashReportPersister類寫(xiě)入file文件。

LogCatCollector

用來(lái)獲取logcat日志中的相關(guān)信息,執(zhí)行Logcat命令引谜,讀取命令輸出信息。

class LogCatCollector{
    public String collectLogCat(){
        //根據(jù)所傳參數(shù)不同組拼不同的logcat命令
        //主要組拼出的命令為
        //1.logcat -t 100 -v time 
        //2.logcat -t 100 -v time -b radio
        //1.logcat -t 100 -v time -b events
    }
}
logcat -b events

05-18 19:45:46.158 31191 31191 I auditd  : type=1400 audit(0.0:505001): avc: denied { search } for comm="PerfFgMonitor" name="1711" dev="proc" ino=18618 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:r:radio:s0 tclass=dir permissive=0

logcat -b radio

05-18 19:44:39.343  1711  1785 D RILJ    : [9679]< RIL_REQUEST_GET_CELL_INFO_LIST [CellInfoWcdma:{mRegistered=YES mTimeStampType=oem_ril mTimeStamp=1283159923921792ns CellIdentityWcdma:{ mMcc=460 mMnc=1 mLac=53529 mCid=101852154 mPsc=438} CellSignalStrengthWcdma: ss=8 ber=99}] [SUB0]
05-18 19:44:39.345  1711  1975 D GsmSST  : [GsmSST] SST.getAllCellInfo(): X size=1 list=[CellInfoWcdma:{mRegistered=YES mTimeStampType=oem_ril mTimeStamp=1283159923921792ns CellIdentityWcdma:{ mMcc=460 mMnc=1 mLac=53529 mCid=101852154 mPsc=438} CellSignalStrengthWcdma: ss=8 ber=99}]
05-18 19:44:39.346  1711  1975 D GsmSST  : [GsmSST] getCellLocation(): X ret WCDMA info=[53529,101852154,438]
05-18 19:44:43.068  1711  1927 D SubscriptionController: [getPhoneId]- no sims, returning default phoneId=2147483647

其實(shí)相信大部分人不太清楚logcat的相關(guān)命令尖阔。
針對(duì)以上的三條命令做如下解釋

logcat -t 100 -v time
-t 限制打印100行內(nèi)容
-v time 設(shè)置日志輸出格式崎场。打印日志的為:打印日期->觸發(fā)時(shí)間->優(yōu)先級(jí)(E,W,V)->tag->出問(wèn)題進(jìn)程的pid
關(guān)于日志輸出格式的介紹參見(jiàn)此處日志輸出格式螃宙。

logcat -b [options] 切換打印log的內(nèi)容級(jí)別

  • radio radio/telephony相關(guān)log
  • events events-related相關(guān)log
  • main 默認(rèn)的log

DropBoxCollector

通過(guò)DropBoxManager讀取系統(tǒng)系統(tǒng)的日志信息
DropBoxManager,很多人應(yīng)該也沒(méi)接觸過(guò)籍凝。
android系統(tǒng)實(shí)際上是有三種日志打印的。log EventLog DropBox,關(guān)于三種log的介紹參見(jiàn)此處箱叁。
三種log的介紹
關(guān)于DropBoxManager的相關(guān)內(nèi)容可以參見(jiàn)此處。dropboxManager介紹

class DropBoxCollector{
    public String read(){
        //通過(guò)DropBoxService獲取系統(tǒng)的DropBoxManager
        //讀取所有預(yù)先定義的不同tag對(duì)應(yīng)的日志內(nèi)容
    }
}

ReportUtils

封裝的各種工具類牡肉,用來(lái)獲取系統(tǒng)相關(guān)的信息

public getAvailableInternalMemorySize(){
    //通過(guò)StatFs類獲取可用內(nèi)存block數(shù)量及每個(gè)block的size
    //block_size * free_block_count = 可用內(nèi)存數(shù)
}

public getTotalInternalMemorySize(){
    //通過(guò)StatFs類獲取所有內(nèi)存block數(shù)量及每個(gè)block的size
    //block_size * total_block_count = 總內(nèi)存數(shù)
}

public getDeviceId(){
    //通過(guò)TelephonyManager獲取deviceId
    //GSM手機(jī)對(duì)應(yīng)與IMEI
    //CDMA手機(jī)對(duì)應(yīng)與ESN或MEID
}

public getApplicationFilePath(){
    //通過(guò)context.getFilesDir()獲取當(dāng)前app的絕對(duì)路徑
    //'/data/user/0/yftx.net.oschina.git.gradlesample/files'
}

public getLocalIpAddress(){
    //通過(guò)NetworkInterface 獲取當(dāng)前設(shè)備的ip
}

public getTimeString(){
    //通過(guò)Calendar類獲取當(dāng)前時(shí)間
}

UUID

java.util包中提供的類捧灰,用來(lái)生成唯一字符串的類。

Installation

用來(lái)生成唯一身份串的類。

class Installation{
    void id(){
        //獲取的id用來(lái)標(biāo)記用戶的身份毛俏。
        //具體算法可以參見(jiàn)android blog中的解釋炭庙。
        //http://android-developers.blogspot.com/2011/03/identifying-app-installations.html
    }
}

ConfigurationCollector

通過(guò)反射系統(tǒng)的Configuration類,獲取系統(tǒng)相關(guān)參數(shù)煌寇。

class ConfigurationCollector{
    void collectConfiguration(Context context){
        //通過(guò) context.getResources().getConfiguration()獲取configration對(duì)象焕蹄,
        //并用反射獲取該類中的相關(guān)信息
    }
}

DumpSysCollector

通過(guò)執(zhí)行dumpsys meminfo xxxpid 來(lái)分析內(nèi)存
關(guān)于dumpsys的介紹參見(jiàn)此:dumsys相關(guān)介紹

class DumpSysCollector{
    void collectMemInfo(){
    //執(zhí)行dumsys 相關(guān)命令
    }
}

ReflectionCollector

相當(dāng)于Util類,主要通過(guò)反射獲取傳過(guò)來(lái)的類的一些信息阀溶。

class ReflectionCollector{
    void collectConstants(){
        //通過(guò)反射獲取系統(tǒng)的相關(guān)信息
        //acra中主要獲取Build腻脏,Build.Version中的相關(guān)數(shù)據(jù)
    }
}

DisplayManagerCollector

主要用來(lái)獲取手機(jī)顯示相關(guān)的數(shù)據(jù)

class DisplayManagerCollector{
    void collectDisplays(){
        //通過(guò)Display類獲取屏幕寬,高,方向等顯示相關(guān)的參數(shù)
    }
}

DeviceFeaturesCollector

通過(guò)PackageManager獲取系統(tǒng)相關(guān)特性。比如glEsVersion等

class DeviceFeaturesCollector{
    void getFeatures(){
        //通過(guò)PackageManager獲取系統(tǒng)相關(guān)特性银锻。比如glEsVersion等
    }
}

SettingsCollector

使用反射獲取android.provider.Settings.x中的相關(guān)內(nèi)容永品。

class SettingsCollector{
    void collectSystemSettings(){
        //獲取系統(tǒng)Settings類中的相關(guān)信息
    }
    
    void collectSecureSettings(){
        //獲取Settings.Secure中的相關(guān)信息
    }
    
    void collectGlobalSettings(){
        //獲取Settings.Global中的相關(guān)信息
    }
}

LogFileCollector

獲取用戶自己保存的相關(guān)的log文件,使用該接口可以讓acra結(jié)合logback-android這類類庫(kù)相結(jié)合。
很多做android的同學(xué)都沒(méi)有做過(guò)java web開(kāi)發(fā)击纬,并且android的Log接口也還算好用鼎姐,再加上客戶端編程和服務(wù)端編程系統(tǒng)的不同,所以可能理解不了logback-android這樣庫(kù)的意義更振。
實(shí)際上logback-android這類庫(kù)主要就是可以指定log輸出的位置炕桨,以及l(fā)og的打印級(jí)別。
關(guān)于java開(kāi)發(fā)中l(wèi)og的重要性可以參見(jiàn)此文章肯腕,java log的意義

MediaCodecListCollector

主要用來(lái)獲取系統(tǒng)支持哪些音視頻類型等媒體相關(guān)的献宫。

ThreadCollector

獲取崩潰線程的相關(guān)信息。

class ThreadCollector{
    void collect(Thread t){
        //獲取線程t的相關(guān)信息乎芳,id,name,priority,groupName
    }
}

ACRA中用到的其他一些獲取異常的方法

getStackTracehash(Throwable th){
    //通過(guò)組拼Error的className及MethodName生成的字符串
    //獲取該字符串的hash值
    //服務(wù)端可以根據(jù)該值做崩潰分類
}

結(jié)語(yǔ)

本部分內(nèi)容主要包括

  1. ACRA如何配置(服務(wù)端遵蚜,客戶端的配置)
  2. 崩潰信息相關(guān)內(nèi)容如何采集,涉及到的關(guān)鍵類奈惑。

后面的部分會(huì)繼續(xù)分析如何將生成的file發(fā)送到服務(wù)端吭净。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肴甸,隨后出現(xiàn)的幾起案子寂殉,更是在濱河造成了極大的恐慌,老刑警劉巖原在,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件友扰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡庶柿,警方通過(guò)查閱死者的電腦和手機(jī)村怪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浮庐,“玉大人甚负,你說(shuō)我怎么就攤上這事。” “怎么了梭域?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵斑举,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我病涨,道長(zhǎng)富玷,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任既穆,我火速辦了婚禮赎懦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘循衰。我一直安慰自己铲敛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布会钝。 她就那樣靜靜地躺著伐蒋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪迁酸。 梳的紋絲不亂的頭發(fā)上先鱼,一...
    開(kāi)封第一講書(shū)人閱讀 50,021評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音奸鬓,去河邊找鬼焙畔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛串远,可吹牛的內(nèi)容都是我干的宏多。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼澡罚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伸但!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起留搔,我...
    開(kāi)封第一講書(shū)人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤更胖,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后隔显,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體却妨,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年括眠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彪标。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掷豺,死狀恐怖捞烟,靈堂內(nèi)的尸體忽然破棺而出账锹,到底是詐尸還是另有隱情,我是刑警寧澤坷襟,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站生年,受9級(jí)特大地震影響婴程,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抱婉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一档叔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蒸绩,春花似錦衙四、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至步藕,卻和暖如春惦界,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咙冗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工沾歪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雾消。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓灾搏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親立润。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狂窑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)范删,斷路器蕾域,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,778評(píng)論 6 342
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,854評(píng)論 25 707
  • 尋找一種易于理解的一致性算法(擴(kuò)展版) 摘要 Raft 是一種為了管理復(fù)制日志的一致性算法。它提供了和 Paxos...
    yflau閱讀 951評(píng)論 0 1
  • 阿雷的魷魚(yú)閱讀 196評(píng)論 0 0