概述
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/
查看崩潰日志
http://ip:port/acralyzer/_design/acralyzer/index.html#/dashboard/
關(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)行初始化配置。
- 使用注解初始化
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);
}
}
- 動(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è)步驟,
- 實(shí)現(xiàn)ReportSender接口,用來(lái)執(zhí)行發(fā)送報(bào)告操作洪鸭。
- 實(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
- 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
- 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)屬性的值玩焰。
- 構(gòu)造函數(shù)通過(guò)注解的方式蔓榄,獲取Application中定義注解的值,進(jìn)行設(shè)置荤西。
- 通過(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)容主要包括
- ACRA如何配置(服務(wù)端遵蚜,客戶端的配置)
- 崩潰信息相關(guān)內(nèi)容如何采集,涉及到的關(guān)鍵類奈惑。
后面的部分會(huì)繼續(xù)分析如何將生成的file發(fā)送到服務(wù)端吭净。