Android程序崩溃异常收集框架

来源:互联网 发布:交通灯单片机程序设计 编辑:程序博客网 时间:2024/06/06 03:05

最近在写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);    }}
然后是发送报告邮件的类了,它继承自前面所写的AbstractCrashReportHandler,实现如下:

/* * @(#)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中配置这个类。


项目已开源,见另一篇博客:http://blog.csdn.net/maosidiaoxian/article/details/27320815


2 0
原创粉丝点击