zoukankan      html  css  js  c++  java
  • Android程序崩溃异常收集框架

    近期在写Android程序崩溃异常处理,完毕之后,稍加封装与大家分享。

    我的思路是这种,在程序崩溃之后。将异常信息保存到一个日志文件里,然后对该文件进行处理。比方发送到邮箱,或发送到server。

    所以,第一步是先定义一个接口。用于在保存好日志之后的回调。

    代码例如以下:

    /*
     * @(#)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; } }


    这个类非常easy,就是在发生未能捕获的异常之后,保存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(' '); 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 } } } }


    在日志保存之后,我们还须要生成一个报告。并发送给server。报告的方法,能够是发送到邮箱。或者http请求发送给server。所以这里写了一个抽象类,实现了生成标题和内容。设置日志路径等。代码例如以下:

    /*
     * @(#)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('
    ');
            PackageManager pm = context.getPackageManager();
            ApplicationInfo ai = context.getApplicationInfo();
            sb.append("Application : ").append(pm.getApplicationLabel(ai)).append('
    ');
    
            try {
                PackageInfo pi = pm.getPackageInfo(ai.packageName, 0);
                sb.append("Version Code: ").append(pi.versionCode).append('
    ');
                sb.append("Version Name: ").append(pi.versionName).append('
    ');
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
    
            sb.append('
    ').append("DEVICE INFORMATION").append('
    ');
            sb.append("Board: ").append(Build.BOARD).append('
    ');
            sb.append("BOOTLOADER: ").append(Build.BOOTLOADER).append('
    ');
            sb.append("BRAND: ").append(Build.BRAND).append('
    ');
            sb.append("CPU_ABI: ").append(Build.CPU_ABI).append('
    ');
            sb.append("CPU_ABI2: ").append(Build.CPU_ABI2).append('
    ');
            sb.append("DEVICE: ").append(Build.DEVICE).append('
    ');
            sb.append("DISPLAY: ").append(Build.DISPLAY).append('
    ');
            sb.append("FINGERPRINT: ").append(Build.FINGERPRINT).append('
    ');
            sb.append("HARDWARE: ").append(Build.HARDWARE).append('
    ');
            sb.append("HOST: ").append(Build.HOST).append('
    ');
            sb.append("ID: ").append(Build.ID).append('
    ');
            sb.append("MANUFACTURER: ").append(Build.MANUFACTURER).append('
    ');
            sb.append("PRODUCT: ").append(Build.PRODUCT).append('
    ');
            sb.append("TAGS: ").append(Build.TAGS).append('
    ');
            sb.append("TYPE: ").append(Build.TYPE).append('
    ');
            sb.append("USER: ").append(Build.USER).append('
    ');
    
            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


  • 相关阅读:
    篝火晚会
    SECHS
    emmc4.X boot1 and boot2
    imx6Q Android7.1 Udisk Mount
    imx6Q 4.1.15 Perf support
    imx6Q 4.1.15 Kgtp support
    imx6Q 4.1.15 early console support
    imx6Q USB OTG Host/Device纯软件切换
    iMX6Q DDR Stresstest
    iMX6Q PowerSave调试
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/6758336.html
Copyright © 2011-2022 走看看