前言
目前我们使用最多的记录bug 崩溃记录的应该是友盟或者bugly了吧,通过官网的集成步骤,一步一步的集成到项目中,然后又错误日志就可以到相应的网站中去看,倒也方便。 不过今天我使用开源项目来捕捉我们的错误日志,可以免去集成多余的第三方包。 无意中发现了这个项目,正好可以集成到我们的项目中。于是就开始吧。
Acra介绍
(项目地址:(https://github.com/ACRA/acra))
摘抄自 https://github.com/ACRA/acra
开始集成
ACRA is a library enabling Android Application to automatically post their crash reports to a report server. It is targeted to android applications developers to help them get data from their applications when they crash or behave erroneously
ACRA是一个库,使Android应用程序可以自动将其崩溃报告发布到报表服务器。 它的目标是Android应用程序开发人员帮助他们从应用程序获取数据时遇到错误或行为错误
集成步骤 https://github.com/ACRA/acra/wiki/BasicSetup
首先我们根据项目中的指导,集成Acra到我们的项目中,在项目中gradle文件中,添加
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
compile 'ch.acra:acra:4.9.2' //配置acra
}
然后在我们自己的Application中初始化,并且提供了三种方式向用户传达错误消息
Toast notification //Toast方式
Dialog //弹窗的方式
Status bar notification //消息通知栏的方式
并且可以通过配置服务器接收地址 通过post/put的方式,可以是form表单或者json数据格式,还可以通过发送邮件的方式,发送到你指定的邮箱中,具体细节就不详细介绍了,可以点击下载看看这个demo acraDemo
开始我们的集成
首先我们要做的2件事情,
1,集成acra,能够捕捉错误日志,
2 将捕捉到的错误日志通过邮件的方式,自动发送到我们指定的邮箱。
开始第一步 集成acra,捕捉错误日志
由于我们是要通过邮箱的方式,所以我们需要配置自己的Sender,自己实现错误日志的处理,
项目中介绍了如何自定义自己的Sender 如下
在4.8+版本是这样定义自己的Sender
public class YourOwnSender implements ReportSender {
...
@Override
public void send(Context context, CrashReportData report) throws ReportSenderException {
// Iterate over the CrashReportData instance and do whatever
// you need with each pair of ReportField key / String value
}
}
public class YourOwnSenderfactory implements ReportSenderFactory {
// NB requires a no arg constructor.
public ReportSender create(Context context, ACRAConfiguration config) {
...
return new YourOwnSender(someConfigPerhaps);
}
}
这里我们就照搬代码就是,在我们的项目中新建2个类 YourOwnSenderfactory 和YourOwnSender类
public class YourOwnSenderfactory implements ReportSenderFactory {
/***
* 注意这里必须要是空的构造方法
*/
public YourOwnSenderfactory() {
}
@NonNull
@Override
public ReportSender create(@NonNull Context context, @NonNull ACRAConfiguration acraConfiguration) {
return new YourOwnSender();
}
}
public class YourOwnSender implements ReportSender {
@Override
public void send(@NonNull Context context, @NonNull CrashReportData crashReportData) throws ReportSenderException {
//发送邮件
}
}
写完了以后我们去自己的application中配置自己的ReportSenderFactory ,配置如下
@ReportsCrashes(
reportSenderFactoryClasses ={com.simple.acrasimple.YourOwnSenderfactory.class},
customReportContent = { ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, ReportField.CUSTOM_DATA, ReportField.STACK_TRACE, ReportField.LOGCAT },
resToastText = R.string.msg_acra_toast
)
public class APP extends Application {
@Override
public void onCreate() {
ACRA.init(this);
super.onCreate();
}
}
注意需要将manifest中application替换成我们自己的application
其中customReportContent 中的具体参数可根据Acra提供的已有的来添加customReportContent
然后我们在自己的Sender中先打印一下错误日志
public class YourOwnSender implements ReportSender {
@Override
public void send(@NonNull Context context, @NonNull CrashReportData crashReportData) throws ReportSenderException {
//发送邮件
Log.i("YourOwnSender", "send: " + crashReportData.toJSON());
}
}
然后,添加一个会发生崩溃的代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println(1 / 0);
}
}
然后运行看看
可以看到错误日志已经打印出来了
I/YourOwnSender( 4374): send: {"IS_SILENT":false,"REPORT_ID":"dfaf8d8c-ed66-4adb-99ec-75896b0a8722","APP_VERSION_NAME":"1.0","LOGCAT":"05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477
): \tat android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.app.ActivityThread.access$800(ActivityThread.java:151)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.app.ActivityThread$H.handleMessage(ActivityThrea
d.java:1303)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.os.Handler.dispatchMessage(Handler.java:102)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.os.Looper.loop(Looper.java:135)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.app.ActivityThread.main(ActivityThre
ad.java:5254)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat java.lang.reflect.Method.invoke(Native Method)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat java.lang.reflect.Method.invoke(Method.java:372)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat com.android.internal.os.ZygoteInit$Metho
dAndArgsCaller.run(ZygoteInit.java:903)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): Caused by: java.lang.ArithmeticException: divide by zero\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \t
at com.simple.acrasimple.MainActivity.onCreate(MainActivity.java:12)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.app.Activity.performCreate(Activity.java:5990)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)\n
05-15 14:35:30.628 E\/AndroidRuntime( 3477): \tat android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)\n05-15 14:35:30.628 E\/AndroidRuntime( 3477): \t... 10 more\n05-15 14:36:07.682 I\/Process ( 3477): Sending signal. PID: 3477 SIG: 9\n05-15 14:36:09.774 I\/art ( 4088): Not l
ate-enabling -Xcheck:jni (already on)\n05-15 14:36:09.968 I\/InstantRun( 4088): starting instant run server: is main process\n05-15 14:36:10.151 W\/art ( 4088): Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(
android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable\n05-15 14:36:10.344 D\/AndroidRuntime( 4088): Shutting down VM\n05-
这里的日志由于打印的日志只有这么长的长度,所以没有截全。.
可以在上面的日志中看到
at com.simple.acrasimple.MainActivity.onCreate(MainActivity.java:12)
在12行这里好像发生了异常。然后就去这里看看究竟吧,
可以看到这里我们的错误日志已经能正常捕获到了 然后我们就可以在我们Sender里面做一些不可描述的事情了。可以自己发送错误日志到自己的服务器,也可以通过邮件的方式发送,可以保存到本地,只有想不到,没有做不到。哈哈。
集成邮件发送
之前我试了阿帕奇的comm email (http://commons.apache.org/proper/commons-email/userguide.html)这个,发现单独用是挺好用的,但是在android上使用还需要一些其他包,我都把jar包添加了又给我说有其他错误,好吧 那我就只有换一个了。发现有一个android-mail的jar包,可以自行下载。不过好像要翻墙,地址 (https://code.google.com/archive/p/javamail-android/downloads)
demo 地址在demo
然后我们现在也直接拿来用,暂时不去修改,大家可根据自己的需求去修改相应的配置,增删代码
我们先添加下载的这三个jar包,添加一个Email类
public class Mail extends javax.mail.Authenticator {
private String _user;
private String _pass;
private String[] _to;
private String _from;
private String _port;
private String _sport;
private String _host;
private String _subject;
private String _body;
private boolean _auth;
private boolean _debuggable;
private Multipart _multipart;
public Mail() {
_host = "smtp.gmail.com"; // default smtp server
_port = "465"; // default smtp port
_sport = "465"; // default socketfactory port
_user = ""; // username
_pass = ""; // password
_from = ""; // email sent from
_subject = ""; // email subject
_body = ""; // email body
_debuggable = false; // debug mode on or off - default off
_auth = true; // smtp authentication - default on
_multipart = new MimeMultipart();
// There is something wrong with MailCap, javamail can not find a handler for the multipart/mixed part, so this bit needs to be added.
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);
}
public Mail(String user, String pass) {
this();
_user = user;
_pass = pass;
}
public boolean send() throws Exception {
Properties props = _setProperties();
if(!_user.equals("") && !_pass.equals("") && _to.length > 0 && !_from.equals("") && !_subject.equals("") && !_body.equals("")) {
Session session = Session.getInstance(props, this);
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(_from));
InternetAddress[] addressTo = new InternetAddress[_to.length];
for (int i = 0; i < _to.length; i++) {
addressTo[i] = new InternetAddress(_to[i]);
}
msg.setRecipients(MimeMessage.RecipientType.TO, addressTo);
msg.setSubject(_subject);
msg.setSentDate(new Date());
// setup message body
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(_body);
_multipart.addBodyPart(messageBodyPart);
// Put parts in message
msg.setContent(_multipart);
// send email
Transport.send(msg);
return true;
} else {
return false;
}
}
public void addAttachment(String filename) throws Exception {
BodyPart messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(filename);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(filename);
_multipart.addBodyPart(messageBodyPart);
}
@Override
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(_user, _pass);
}
private Properties _setProperties() {
Properties props = new Properties();
props.put("mail.smtp.host", _host);
if(_debuggable) {
props.put("mail.debug", "true");
}
if(_auth) {
props.put("mail.smtp.auth", "true");
}
props.put("mail.smtp.port", _port);
props.put("mail.smtp.socketFactory.port", _sport);
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");
return props;
}
// the getters and setters
public String getBody() {
return _body;
}
public void setBody(String _body) {
this._body = _body;
}
// more of the getters and setters …..
}
由于我用的是163邮箱 所以上面的_host改为 smtp.163.com 如果是其他邮箱,自己看看smtp host是多少。
然后配置我们的邮件发送
public class YourOwnSender implements ReportSender {
@Override
public void send(@NonNull Context context, @NonNull CrashReportData crashReportData) throws ReportSenderException {
//发送邮件
// Log.i("YourOwnSender", "send: " + crashReportData.toJSON());
Mail mail=new Mail("your email userName","your email password");
mail.set_to(new String[]{"接受者的邮箱"});//接受者邮箱 可以是多个
mail.set_from("填写你自己的邮箱地址也可以");//邮件来源
mail.set_subject("错误日志demo");//设置主题标题
mail.setBody(crashReportData.toString());
try {
if( mail.send()){
Log.i("YourOwnSender", "send: 发送成功");
}else{
Log.i("YourOwnSender", "send: 发送失败");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后运行一遍 ,然后成功接收到邮件
最后的最后
我们还可以自定义一些其他数据,在崩溃的时候发送,大概写法是
crashReportData.put(ReportField.CUSTOM_DATA,new ComplexElement(map)) 可以发送一个map到邮件中。
根据acr文档描述,不一定在崩溃的时候发送,其他情况下也可以根据事件的相应做处理,想想真是太棒了, 也不需要额外的添加数据埋点。直接可以通过该项目可以实现。
多看点其他开源项目,可以接触到一些没接触过的东西,也可以多学习一点新东西。 里面acra里面具体的实现还没来得及看,有时间了去看看源码吧。有兴趣的同学可以看看 。
( ^_^ )/~~拜拜
github地址:acra+android-mail