上篇文章只是粗略的講到到異常捕獲的處理機(jī)制。當(dāng)我們應(yīng)用奔潰時(shí),不一定能復(fù)現(xiàn)或者查看到log。所以需要將錯(cuò)誤信息保存到日志,并在保存后發(fā)送日志到服務(wù)器或者郵箱。在此我們需要3個(gè)jar包。activation.jar additionnal.jar mail.jar。附上鏈接下載鏈接。http://download.csdn.net/download/android_cmos/9493514
下載依賴(lài)之后。在自定義一個(gè)類(lèi)繼承于Application,并在AndroidManifest.xml中的application節(jié)點(diǎn)中使用name屬性,將類(lèi)名添加進(jìn)去,這樣當(dāng)程序一啟動(dòng)就會(huì)先執(zhí)行繼承自application類(lèi)里面的配置,最后要?jiǎng)e忘了添加權(quán)限,一個(gè)是網(wǎng)絡(luò)權(quán)限,一個(gè)是往sd卡寫(xiě)的權(quán)限
public class CrashApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initEmailReporter();
}
/**
* 使用EMAIL發(fā)送日志
*/
private void initEmailReporter() {
CrashEmailReporter reporter = new CrashEmailReporter(this);
reporter.setReceiver("xxxxxx@qq.com");//接收日志的郵箱
reporter.setSender("xxxxx@163.com");//發(fā)送日志的郵箱
reporter.setSendPassword("xxxxx");//說(shuō)到這個(gè)密碼,你可以設(shè)置一個(gè)客戶(hù)端授權(quán)碼,它是登錄第三方客戶(hù)端的專(zhuān)用密碼,和主登錄密碼不沖突
reporter.setSMTPHost("smtp.163.com");//這里使用的是163發(fā)送郵件的服務(wù),所以主機(jī)名是163的,有需要修改的,也可以更改對(duì)應(yīng)的主機(jī)名
reporter.setPort("465");//端口號(hào),可選25端口號(hào),具體的看是否使用ssl安全協(xié)議
AndroidCrash.getInstance().setCrashReporter(reporter).init(this);
}
/**
* 使用HTTP發(fā)送日志
*/
private void initHttpReporter() {
CrashHttpReporter reporter = new CrashHttpReporter(this) {
/**
* 重寫(xiě)此方法,可以彈出自定義的崩潰提示對(duì)話框,而不使用系統(tǒng)的崩潰處理。
*
* @param thread
* @param ex
*/
@Override
public void closeApp(Thread thread, Throwable ex) {
// final Activity activity = AppManager.currentActivity();
// Toast.makeText(activity, "發(fā)生異常,正在退出", Toast.LENGTH_SHORT).show();
// 自定義彈出對(duì)話框
new AlertDialog.Builder(getApplicationContext()).
setMessage("程序發(fā)生異常,現(xiàn)在退出").
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);
}
}
上面郵箱中需要有一個(gè)授權(quán)碼,這個(gè)授權(quán)碼是可以不同于郵箱的登錄密碼的。對(duì)于自動(dòng)發(fā)送郵件的,一定要郵箱開(kāi)啟SMTP服務(wù)功能,否則程序會(huì)報(bào)一個(gè)自動(dòng)驗(yàn)證失敗的異常,發(fā)送不了郵件。具體怎么設(shè)置SMTP服務(wù),請(qǐng)點(diǎn)擊下面的鏈接 http://jingyan.baidu.com/article/0aa223755d15dc88cd0d6473.html
先寫(xiě)個(gè)保存好日志之后的回調(diào)
public interface CrashListener {
void sendFile(File var1);
void closeApp(Thread var1, Throwable var2);
}
然后是用于處理崩潰異常的類(lèi),它要實(shí)現(xiàn)UncaughtExceptionHandler接口。實(shí)現(xiàn)它之后,將它設(shè)為默認(rèn)的線程異常的處理者,這樣程序崩潰之后,就會(huì)調(diào)用它了。但是在調(diào)用它之前,還需要先獲取保存之前默認(rèn)的handler,用于在我們收集了異常之后對(duì)程序進(jìn)行處理,比如默認(rèn)的彈出“程序已停止運(yùn)行”的對(duì)話框(當(dāng)然你也可以自己實(shí)現(xiàn)一個(gè)),終止程序,打印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的類(lèi)。就是在發(fā)生未能捕獲的異常之后,保存LOG到文件,然后 調(diào)用前面定義的接口,對(duì)日志文件進(jìn)行處理。其中LogWriter是我實(shí)現(xiàn)的保存LOG到文件的類(lèi)。代碼如下:
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) {
;
}
}
}
}
最終在日志保存之后,需要生成一個(gè)報(bào)告,并發(fā)送給服務(wù)器。報(bào)告的方法,可以是發(fā)送到郵箱,或者h(yuǎn)ttp請(qǐng)求發(fā)送給服務(wù)器。所以寫(xiě)了一個(gè)抽象類(lèi),實(shí)現(xiàn)了生成標(biāo)題和內(nèi)容,設(shè)置日志路徑等。代碼如下
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);
}
}
這里寫(xiě)了報(bào)告的一種實(shí)現(xiàn),發(fā)送郵件。繼承自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);
}
}
然后是發(fā)送報(bào)告郵件的類(lèi)了,它繼承自前面所寫(xiě)的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();
}
}
}
這樣就把發(fā)送日志的功能完成了。然后在MainActivity 中模擬任何崩潰的情況,日志就會(huì)保存下來(lái),并能自動(dòng)發(fā)送日志到郵箱了。發(fā)送到服務(wù)器的先不講了。
給個(gè)最終發(fā)送郵箱成功的圖。

