上篇文章只是粗略的讲到到异常捕获的处理机制。当我们应用奔溃时,不一定能复现或者查看到log。所以需要将错误信息保存到日志,并在保存后发送日志到服务器或者邮箱。在此我们需要3个jar包。activation.jar additionnal.jar mail.jar。附上链接下载链接。http://download.csdn.net/download/android_cmos/9493514
下载依赖之后。在自定义一个类继承于Application,并在AndroidManifest.xml中的application节点中使用name属性,将类名添加进去,这样当程序一启动就会先执行继承自application类里面的配置,最后要别忘了添加权限,一个是网络权限,一个是往sd卡写的权限
public class CrashApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initEmailReporter();
}
/**
* 使用EMAIL发送日志
*/
private void initEmailReporter() {
CrashEmailReporter reporter = new CrashEmailReporter(this);
reporter.setReceiver("[email protected]");//接收日志的邮箱
reporter.setSender("[email protected]");//发送日志的邮箱
reporter.setSendPassword("xxxxx");//说到这个密码,你可以设置一个客户端授权码,它是登录第三方客户端的专用密码,和主登录密码不冲突
reporter.setSMTPHost("smtp.163.com");//这里使用的是163发送邮件的服务,所以主机名是163的,有需要修改的,也可以更改对应的主机名
reporter.setPort("465");//端口号,可选25端口号,具体的看是否使用ssl安全协议
AndroidCrash.getInstance().setCrashReporter(reporter).init(this);
}
/**
* 使用HTTP发送日志
*/
private void initHttpReporter() {
CrashHttpReporter reporter = new CrashHttpReporter(this) {
/**
* 重写此方法,可以弹出自定义的崩溃提示对话框,而不使用系统的崩溃处理。
*
* @param thread
* @param ex
*/
@Override
public void closeApp(Thread thread, Throwable ex) {
// final Activity activity = AppManager.currentActivity();
// Toast.makeText(activity, "发生异常,正在退出", Toast.LENGTH_SHORT).show();
// 自定义弹出对话框
new AlertDialog.Builder(getApplicationContext()).
setMessage("程序发生异常,现在退出").
setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// AppManager.AppExit(activity);
}
}).create().show();
Log.d("MyApplication", "thead:" + Thread.currentThread().getName());
}
};
reporter.setUrl("http://xxx.crashreport.jd-app.com/your_receiver").setFileParam("fileName").setToParam("to").setTo("你的接收邮箱").setTitleParam("subject").setBodyParam("message");
reporter.setCallback(new CrashHttpReporter.HttpReportCallback() {
@Override
public boolean isSuccess(int i, String s) {
return s.endsWith("ok");
}
});
AndroidCrash.getInstance().setCrashReporter(reporter).init(this);
}
}
上面邮箱中需要有一个授权码,这个授权码是可以不同于邮箱的登录密码的。对于自动发送邮件的,一定要邮箱开启SMTP服务功能,否则程序会报一个自动验证失败的异常,发送不了邮件。具体怎么设置SMTP服务,请点击下面的链接 http://jingyan.baidu.com/article/0aa223755d15dc88cd0d6473.html
先写个保存好日志之后的回调
public interface CrashListener {
void sendFile(File var1);
void closeApp(Thread var1, Throwable var2);
}
然后是用于处理崩溃异常的类,它要实现UncaughtExceptionHandler接口。实现它之后,将它设为默认的线程异常的处理者,这样程序崩溃之后,就会调用它了。但是在调用它之前,还需要先获取保存之前默认的handler,用于在我们收集了异常之后对程序进行处理,比如默认的弹出“程序已停止运行”的对话框(当然你也可以自己实现一个),终止程序,打印LOG
public class CrashCatcher implements UncaughtExceptionHandler {
private static final String LOG_TAG = CrashCatcher.class.getSimpleName();
private static final CrashCatcher sHandler = new CrashCatcher();
private CrashListener mListener;
private File mLogFile;
public CrashCatcher() {
}
public static CrashCatcher getInstance() {
return sHandler;
}
public void uncaughtException(final Thread thread, final Throwable ex) {
try {
LogWriter.writeLog(this.mLogFile, "CrashHandler", ex.getMessage(), ex);
} catch (Exception var4) {
Log.w(LOG_TAG, var4);
}
this.mListener.sendFile(this.mLogFile);
(new Thread(new Runnable() {
public void run() {
Looper.prepare();
try {
CrashCatcher.this.mListener.closeApp(thread, ex);
} catch (Exception var2) {
var2.printStackTrace();
}
Looper.loop();
}
})).start();
}
public void init(File logFile, CrashListener listener) {
AssertUtil.assertNotNull("logFile", logFile);
AssertUtil.assertNotNull("crashListener", listener);
this.mLogFile = logFile;
this.mListener = listener;
}
}
然后是保存log的类。就是在发生未能捕获的异常之后,保存LOG到文件,然后 调用前面定义的接口,对日志文件进行处理。其中LogWriter是我实现的保存LOG到文件的类。代码如下:
public class LogWriter {
private static final SimpleDateFormat timeFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.getDefault());
public LogWriter() {
}
public static synchronized void writeLog(File logFile, String tag, String message, Throwable tr) {
logFile.getParentFile().mkdirs();
if(!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException var13) {
var13.printStackTrace();
}
}
String time = timeFormat.format(Calendar.getInstance().getTime());
synchronized(logFile) {
FileWriter fileWriter = null;
BufferedWriter bufdWriter = null;
PrintWriter printWriter = null;
try {
fileWriter = new FileWriter(logFile, true);
bufdWriter = new BufferedWriter(fileWriter);
printWriter = new PrintWriter(fileWriter);
bufdWriter.append(time).append(" ").append("E").append('/').append(tag).append(" ").append(message).append('\n');
bufdWriter.flush();
tr.printStackTrace(printWriter);
printWriter.flush();
fileWriter.flush();
} catch (IOException var11) {
closeQuietly(fileWriter);
closeQuietly(bufdWriter);
closeQuietly(printWriter);
}
}
}
public static void closeQuietly(Closeable closeable) {
if(closeable != null) {
try {
closeable.close();
} catch (IOException var2) {
;
}
}
}
}
最终在日志保存之后,需要生成一个报告,并发送给服务器。报告的方法,可以是发送到邮箱,或者http请求发送给服务器。所以写了一个抽象类,实现了生成标题和内容,设置日志路径等。代码如下
public abstract class AbstractCrashHandler implements CrashListener {
private static final UncaughtExceptionHandler sDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
private Context mContext;
private ExecutorService mSingleExecutor = Executors.newSingleThreadExecutor();
protected Future mFuture;
private int TIMEOUT = 5;
public AbstractCrashHandler(Context context) {
this.mContext = context;
}
protected abstract void sendReport(String var1, String var2, File var3);
public void sendFile(final File file) {
if(this.mFuture != null && !this.mFuture.isDone()) {
this.mFuture.cancel(false);
}
this.mFuture = this.mSingleExecutor.submit(new Runnable() {
public void run() {
AbstractCrashHandler.this.sendReport(AbstractCrashHandler.this.buildTitle(AbstractCrashHandler.this.mContext), AbstractCrashHandler.this.buildBody(AbstractCrashHandler.this.mContext), file);
}
});
}
public String buildTitle(Context context) {
return "Crash Log: " + context.getPackageManager().getApplicationLabel(context.getApplicationInfo());
}
public String buildBody(Context context) {
StringBuilder sb = new StringBuilder();
sb.append("APPLICATION INFORMATION").append('\n');
PackageManager pm = context.getPackageManager();
ApplicationInfo ai = context.getApplicationInfo();
sb.append("Application : ").append(pm.getApplicationLabel(ai)).append('\n');
try {
PackageInfo e = pm.getPackageInfo(ai.packageName, 0);
sb.append("Version Code: ").append(e.versionCode).append('\n');
sb.append("Version Name: ").append(e.versionName).append('\n');
} catch (NameNotFoundException var6) {
var6.printStackTrace();
}
sb.append('\n').append("DEVICE INFORMATION").append('\n');
sb.append("Board: ").append(Build.BOARD).append('\n');
sb.append("BOOTLOADER: ").append(Build.BOOTLOADER).append('\n');
sb.append("BRAND: ").append(Build.BRAND).append('\n');
sb.append("CPU_ABI: ").append(Build.CPU_ABI).append('\n');
sb.append("CPU_ABI2: ").append(Build.CPU_ABI2).append('\n');
sb.append("DEVICE: ").append(Build.DEVICE).append('\n');
sb.append("DISPLAY: ").append(Build.DISPLAY).append('\n');
sb.append("FINGERPRINT: ").append(Build.FINGERPRINT).append('\n');
sb.append("HARDWARE: ").append(Build.HARDWARE).append('\n');
sb.append("HOST: ").append(Build.HOST).append('\n');
sb.append("ID: ").append(Build.ID).append('\n');
sb.append("MANUFACTURER: ").append(Build.MANUFACTURER).append('\n');
sb.append("PRODUCT: ").append(Build.PRODUCT).append('\n');
sb.append("TAGS: ").append(Build.TAGS).append('\n');
sb.append("TYPE: ").append(Build.TYPE).append('\n');
sb.append("USER: ").append(Build.USER).append('\n');
return sb.toString();
}
public void closeApp(Thread thread, Throwable ex) {
try {
this.mFuture.get((long)this.TIMEOUT, TimeUnit.SECONDS);
} catch (Exception var4) {
var4.printStackTrace();
}
sDefaultHandler.uncaughtException(thread, ex);
}
}
这里写了报告的一种实现,发送邮件。继承自Authenticator
public class LogMail extends Authenticator {
private String host;
private String port;
private String user;
private String pass;
private String from;
private String to;
private String subject;
private String body;
private Multipart multipart;
private Properties props;
public LogMail() {
}
public LogMail(String user, String pass, String from, String to, String host, String port, String subject, String body) {
this.host = host;
this.port = port;
this.user = user;
this.pass = pass;
this.from = from;
this.to = to;
this.subject = subject;
this.body = body;
}
public LogMail setHost(String host) {
this.host = host;
return this;
}
public LogMail setPort(String port) {
this.port = port;
return this;
}
public LogMail setUser(String user) {
this.user = user;
return this;
}
public LogMail setPass(String pass) {
this.pass = pass;
return this;
}
public LogMail setFrom(String from) {
this.from = from;
return this;
}
public LogMail setTo(String to) {
this.to = to;
return this;
}
public LogMail setSubject(String subject) {
this.subject = subject;
return this;
}
public LogMail setBody(String body) {
this.body = body;
return this;
}
public void init() {
this.multipart = new MimeMultipart();
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);
this.props = new Properties();
this.props.put("mail.smtp.host", this.host);
this.props.put("mail.smtp.auth", "true");
this.props.put("mail.smtp.port", this.port);
this.props.put("mail.smtp.socketFactory.port", this.port);
this.props.put("mail.transport.protocol", "smtp");
this.props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
this.props.put("mail.smtp.socketFactory.fallback", "false");
}
public boolean send() throws MessagingException {
if(!this.user.equals("") && !this.pass.equals("") && !this.to.equals("") && !this.from.equals("")) {
Session session = Session.getDefaultInstance(this.props, this);
Log.d("SendUtil", this.host + "..." + this.port + ".." + this.user + "..." + this.pass);
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(this.from));
InternetAddress addressTo = new InternetAddress(this.to);
msg.setRecipient(RecipientType.TO, addressTo);
msg.setSubject(this.subject);
msg.setSentDate(new Date());
MimeBodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(this.body);
this.multipart.addBodyPart(messageBodyPart, 0);
msg.setContent(this.multipart);
Transport.send(msg);
return true;
} else {
return false;
}
}
public void addAttachment(String filePath, String fileName) throws Exception {
MimeBodyPart messageBodyPart = new MimeBodyPart();
((MimeBodyPart)messageBodyPart).attachFile(filePath);
this.multipart.addBodyPart(messageBodyPart);
}
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(this.user, this.pass);
}
}
然后是发送报告邮件的类了,它继承自前面所写的AbstractCrashReportHandler
public class CrashEmailReporter extends AbstractCrashHandler {
private String mReceiveEmail;
private String mSendEmail;
private String mSendPassword;
private String mHost;
private String mPort;
public CrashEmailReporter(Context context) {
super(context);
}
public void setReceiver(String receiveEmail) {
this.mReceiveEmail = receiveEmail;
}
public void setSender(String email) {
this.mSendEmail = email;
}
public void setSendPassword(String password) {
this.mSendPassword = password;
}
public void setSMTPHost(String host) {
this.mHost = host;
}
public void setPort(String port) {
this.mPort = port;
}
protected void sendReport(String title, String body, File file) {
LogMail sender = (new LogMail()).setUser(this.mSendEmail).setPass(this.mSendPassword).setFrom(this.mSendEmail).setTo(this.mReceiveEmail).setHost(this.mHost).setPort(this.mPort).setSubject(title).setBody(body);
sender.init();
try {
sender.addAttachment(file.getPath(), file.getName());
sender.send();
file.delete();
} catch (Exception var6) {
var6.printStackTrace();
}
}
}
这样就把发送日志的功能完成了。然后在MainActivity 中模拟任何崩溃的情况,日志就会保存下来,并能自动发送日志到邮箱了。发送到服务器的先不讲了。
给个最终发送邮箱成功的图。