Android程序崩溃异常收集框架
来源:互联网 发布:java编写信息管理系统 编辑:程序博客网 时间:2024/05/16 10:28
最近在写Android程序崩溃异常处理,完成之后,稍加封装与大家分享。
我的思路是这样的,在程序崩溃之后,将异常信息保存到一个日志文件中,然后对该文件进行处理,比如发送到邮箱,或发送到服务器。
所以,第一步是先定义一个接口,用于在保存好日志之后的回调。代码如下:
- /*
- * @(#)CrashListener.java Project: crash
- * Date:2014-5-27
- *
- * Copyright (c) 2014 CFuture09, Institute of Software,
- * Guangdong Ocean University, Zhanjiang, GuangDong, China.
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.githang.android.crash;
- import java.io.File;
- /**
- * @author Geek_Soledad <a target="_blank" href=
- * "http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=XTAuOSVzPDM5LzI0OR0sLHM_MjA"
- * style="text-decoration:none;"><img src=
- * "http://rescdn.qqmail.com/zh_CN/htmledition/images/function/qm_open/ico_mailme_01.png"
- * /></a>
- */
- public interface CrashListener {
- /**
- * 保存异常的日志。
- *
- * @param file
- */
- public void afterSaveCrash(File file);
- }
接下来是用于处理崩溃异常的类,它要实现UncaughtExceptionHandler接口。实现它之后,将它设为默认的线程异常的处理者,这样程序崩溃之后,就会调用它了。但是在调用它之前,还需要先获取保存之前默认的handler,用于在我们收集了异常之后对程序进行处理,比如默认的弹出“程序已停止运行”的对话框(当然你也可以自己实现一个),终止程序,打印LOG。
我的实现如下:
- /*
- * @(#)CrashHandler.java Project: crash
- * Date:2014-5-26
- *
- * Copyright (c) 2014 CFuture09, Institute of Software,
- * Guangdong Ocean University, Zhanjiang, GuangDong, China.
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.githang.android.crash;
- import java.io.File;
- import java.lang.Thread.UncaughtExceptionHandler;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Future;
- /**
- * @author Geek_Soledad <a target="_blank" href=
- * "http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=XTAuOSVzPDM5LzI0OR0sLHM_MjA"
- * style="text-decoration:none;"><img src=
- * "http://rescdn.qqmail.com/zh_CN/htmledition/images/function/qm_open/ico_mailme_01.png"
- * /></a>
- */
- public class CrashHandler implements UncaughtExceptionHandler {
- private static final CrashHandler sHandler = new CrashHandler();
- private static final UncaughtExceptionHandler sDefaultHandler = Thread
- .getDefaultUncaughtExceptionHandler();
- private static final ExecutorService THREAD_POOL = Executors.newSingleThreadExecutor();
- private Future<?> future;
- private CrashListener mListener;
- private File mLogFile;
- public static CrashHandler getInstance() {
- return sHandler;
- }
- @Override
- public void uncaughtException(Thread thread, Throwable ex) {
- CrashLogUtil.writeLog(mLogFile, "CrashHandler", ex.getMessage(), ex);
- future = THREAD_POOL.submit(new Runnable() {
- public void run() {
- if (mListener != null) {
- mListener.afterSaveCrash(mLogFile);
- }
- };
- });
- if (!future.isDone()) {
- try {
- future.get();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- sDefaultHandler.uncaughtException(thread, ex);
- }
- public void init(File logFile, CrashListener listener) {
- mLogFile = logFile;
- mListener = listener;
- }
- }
这个类很简单,就是在发生未能捕获的异常之后,保存LOG到文件,然后 调用前面定义的接口,对日志文件进行处理。其中CrashLogUtil是我实现的保存LOG到文件的类。代码如下:
- /*
- * @(#)LogUtil.java Project: crash
- * Date:2014-5-27
- *
- * Copyright (c) 2014 CFuture09, Institute of Software,
- * Guangdong Ocean University, Zhanjiang, GuangDong, China.
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.githang.android.crash;
- import java.io.BufferedWriter;
- import java.io.Closeable;
- import java.io.File;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Locale;
- /**
- * @author Geek_Soledad <a target="_blank" href=
- * "http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=XTAuOSVzPDM5LzI0OR0sLHM_MjA"
- * style="text-decoration:none;"><img src=
- * "http://rescdn.qqmail.com/zh_CN/htmledition/images/function/qm_open/ico_mailme_01.png"
- * /></a>
- */
- public class CrashLogUtil {
- private static final SimpleDateFormat timeFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS",
- Locale.getDefault());
- /**
- * 将日志写入文件。
- *
- * @param tag
- * @param message
- * @param tr
- */
- public static synchronized void writeLog(File logFile, String tag, String message, Throwable tr) {
- logFile.getParentFile().mkdirs();
- if (!logFile.exists()) {
- try {
- logFile.createNewFile();
- } catch (IOException e) {
- e.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 e) {
- closeQuietly(fileWriter);
- closeQuietly(bufdWriter);
- closeQuietly(printWriter);
- }
- }
- }
- public static void closeQuietly(Closeable closeable) {
- if (closeable != null) {
- try {
- closeable.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
- }
- }
在日志保存之后,我们还需要生成一个报告,并发送给服务器。报告的方法,可以是发送到邮箱,或者http请求发送给服务器。所以这里写了一个抽象类,实现了生成标题和内容,设置日志路径等。代码如下:
- /*
- * @(#)AbstractReportHandler.java Project: crash
- * Date:2014-5-27
- *
- * Copyright (c) 2014 CFuture09, Institute of Software,
- * Guangdong Ocean University, Zhanjiang, GuangDong, China.
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.githang.android.crash;
- import java.io.File;
- import android.content.Context;
- import android.content.pm.ApplicationInfo;
- import android.content.pm.PackageInfo;
- import android.content.pm.PackageManager;
- import android.os.Build;
- /**
- * @author Geek_Soledad <a target="_blank" href=
- * "http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=XTAuOSVzPDM5LzI0OR0sLHM_MjA"
- * style="text-decoration:none;"><img src=
- * "http://rescdn.qqmail.com/zh_CN/htmledition/images/function/qm_open/ico_mailme_01.png"
- * /></a>
- */
- public abstract class AbstractCrashReportHandler implements CrashListener {
- private Context mContext;
- public AbstractCrashReportHandler(Context context) {
- mContext = context;
- CrashHandler handler = CrashHandler.getInstance();
- handler.init(getLogDir(context), this);
- Thread.setDefaultUncaughtExceptionHandler(handler);
- }
- protected File getLogDir(Context context) {
- return new File(context.getFilesDir(), "crash.log");
- }
- protected abstract void sendReport(String title, String body, File file);
- @Override
- public void afterSaveCrash(File file) {
- sendReport(buildTitle(mContext), buildBody(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 pi = pm.getPackageInfo(ai.packageName, 0);
- sb.append("Version Code: ").append(pi.versionCode).append('\n');
- sb.append("Version Name: ").append(pi.versionName).append('\n');
- } catch (PackageManager.NameNotFoundException e) {
- e.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();
- }
- }
这样一个框架就算基本完成了。
当然,下面我还给出了报告的一种实现,发送邮件。
如何发送邮箱,网上已有不少资料,这里不再简而言之。
首先需要用到三个jar包: activation.jar, additionnal.jar, mail.jar。
然后 写一个类,继承自Authenticator。代码如下:
- /*
- * @(#)Snippet.java Project: CrashHandler
- * Date: 2014-5-27
- *
- * Copyright (c) 2014 CFuture09, Institute of Software,
- * Guangdong Ocean University, Zhanjiang, GuangDong, China.
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.githang.android.crash;
- import android.util.Log;
- import java.util.Date;
- import java.util.Properties;
- import javax.activation.CommandMap;
- import javax.activation.DataHandler;
- import javax.activation.DataSource;
- import javax.activation.FileDataSource;
- import javax.activation.MailcapCommandMap;
- import javax.mail.Authenticator;
- import javax.mail.BodyPart;
- import javax.mail.MessagingException;
- import javax.mail.Multipart;
- import javax.mail.PasswordAuthentication;
- import javax.mail.Session;
- import javax.mail.Transport;
- import javax.mail.internet.InternetAddress;
- import javax.mail.internet.MimeBodyPart;
- import javax.mail.internet.MimeMessage;
- import javax.mail.internet.MimeMultipart;
- /**
- * Author: msdx (645079761@qq.com) Time: 14-5-27 上午9:07
- */
- 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() {
- 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);
- props = new Properties();
- props.put("mail.smtp.host", host);
- props.put("mail.smtp.auth", "true");
- props.put("mail.smtp.port", port);
- props.put("mail.smtp.socketFactory.port", port);
- props.put("mail.transport.protocol", "smtp");
- props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
- props.put("mail.smtp.socketFactory.fallback", "false");
- }
- public boolean send() throws MessagingException {
- if (!user.equals("") && !pass.equals("") && !to.equals("") && !from.equals("")) {
- Session session = Session.getDefaultInstance(props, this);
- Log.d("SendUtil", host + "..." + port + ".." + user + "..." + pass);
- MimeMessage msg = new MimeMessage(session);
- msg.setFrom(new InternetAddress(from));
- InternetAddress addressTo = new InternetAddress(to);
- msg.setRecipient(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 filePath, String fileName) throws Exception {
- BodyPart messageBodyPart = new MimeBodyPart();
- DataSource source = new FileDataSource(filePath);
- messageBodyPart.setDataHandler(new DataHandler(source));
- messageBodyPart.setFileName(fileName);
- multipart.addBodyPart(messageBodyPart);
- }
- @Override
- public PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(user, pass);
- }
- }
- /*
- * @(#)CrashEmailReport.java Project: CrashHandler
- * Date: 2014-5-27
- *
- * Copyright (c) 2014 CFuture09, Institute of Software,
- * Guangdong Ocean University, Zhanjiang, GuangDong, China.
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.githang.android.crash;
- import java.io.File;
- import android.content.Context;
- /**
- * @author Geek_Soledad <a target="_blank" href=
- * "http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=XTAuOSVzPDM5LzI0OR0sLHM_MjA"
- * style="text-decoration:none;"><img src=
- * "http://rescdn.qqmail.com/zh_CN/htmledition/images/function/qm_open/ico_mailme_01.png"
- * /></a>
- */
- public class CrashEmailReport extends AbstractCrashReportHandler {
- private String mReceiveEmail;
- public CrashEmailReport(Context context) {
- super(context);
- }
- public void setReceiver(String receiveEmail) {
- mReceiveEmail = receiveEmail;
- }
- @Override
- protected void sendReport(String title, String body, File file) {
- LogMail sender = new LogMail().setUser("irain_log@163.com").setPass("xxxxxxxx")
- .setFrom("irain_log@163.com").setTo(mReceiveEmail).setHost("smtp.163.com")
- .setPort("465").setSubject(title).setBody(body);
- sender.init();
- try {
- sender.addAttachment(file.getPath(), file.getName());
- sender.send();
- file.delete();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
这样,一个完整的程序崩溃异常框架就完成了。对于日志报告,可自己继承AbstractCrashReportHandler来扩展实现。
使用的时候,需要写一个继承自Application的类,在onCreate方法中加上如下代码,即设置接收邮箱。
- new CrashEmailReport(this).setReceiver("log@msdx.pw");
然后在AndroidManifest.xml中配置这个类。
目前我已经把框架放在了github了,地址如下:https://github.com/msdx/android-crash
使用方法见github上面的README.md。
本项目相关jar包已发布在jcenter,如果你使用gradle构建工具,可以直接添加依赖,具体见项目的README.
0 0
- Android程序崩溃异常收集框架
- Android程序崩溃异常收集框架
- Android程序崩溃异常处理框架
- Android 代码崩溃异常收集整理
- Android:处理程序崩溃异常
- Android:处理程序崩溃异常
- Android程序异常崩溃处理
- Android程序异常信息收集
- android处理crash程序崩溃异常
- Android 对程序异常崩溃的捕捉
- Android 对程序异常崩溃的捕捉
- Android 对程序异常崩溃的捕捉
- android处理crash程序崩溃异常
- 捕获android程序崩溃异常日志
- Android 对程序异常崩溃的捕捉
- Android检测程序崩溃框架CustomActivityOnCrash
- Android全局异常处理(捕获异常,不弹出程序崩溃)
- Android全局异常处理(捕获异常,不弹出程序崩溃)
- 平时工作开发中,常见问题解决方案收集整理(持续更新)
- Android网络框架Volley
- Hadoop MapReduce做大数据排序
- 支付宝
- 理解事务机制
- Android程序崩溃异常收集框架
- 餐饮网站O2O转型兴起背面:五大力气凸显和强化
- 加强、提升SSL的密钥位数防止被黑客攻破
- 尊重原创,请保证您的文章为原创作品
- 朴素贝叶斯的应用
- 安卓学习笔记——关于流程控制
- Sql Server函数和存储过程
- STM32与FreeRTOS学习备忘,xSemaphoreGiveFromISR
- 【Git学习笔记】使用git status时刻关注仓库状态