上篇写到,将程序中没有处理到的crash信息保存到本地文件夹下。但是实际的情况是,你不可能总是将用户的设备拿过来。所以一般性的处理是,将crash reports发送到服务器或者邮箱。所以针对上篇的代码,改动如下:
 
CrashHandler.java
当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告

package com.amanda.crash2file;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
 
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
 
/**
* UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
*
* @author user
*
*/
public class CrashHandler implements UncaughtExceptionHandler {
 
    private static final String TAG = "CrashHandler";
    private static final String CRASH_FLOD_NAME = "crash";
 
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler实例
    private static CrashHandler INSTANCE = new CrashHandler();
    //程序的Context对象
    private Context mContext;
 
    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyyMMdd_kkmmss");
 
    /** 保证只有一个CrashHandler实例 */
    private CrashHandler() {
    }
 
    /** 获取CrashHandler实例 ,单例模式 */
    public static CrashHandler getInstance() {
        return INSTANCE;
    }
 
    /**
     * 初始化
     *
     * @param context
     */
    public void init(Context context) {
        mContext = context;
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
 
    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            //退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }
 
    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
 
        //异步工作:获取版本信息、写入文件、发送邮件
        ProgressTask mTask = new ProgressTask(mContext);
        mTask.execute(ex);
 
 
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
 
        return true;
    }
 
 
    private class ProgressTask extends AsyncTask<Throwable, Void, Boolean> {
        private Context mContext;
 
        public ProgressTask(Context vContext){
            mContext = vContext;
        }
 
        protected void onPreExecute() {
        }
 
 
        protected void onPostExecute(Boolean result) {
        }
 
        @Override
        protected Boolean doInBackground(Throwable... params) {
            boolean mResult = false;
 
            //保存日志文件
            String filePath = saveCrashInfo2File(mContext,params[0]);       
            Log.d("test","filePath: "+filePath);
 
            //发送邮件
            if(filePath != null)
            {
                boolean isSuccessMail = sendMailByJavaMail(filePath);
                Log.d("test","isSuccessMail: "+isSuccessMail);
 
                //如果发送成功,则删除本地的crash report
                if(isSuccessMail){
                    deleteFile(filePath);
                }
 
                mResult = isSuccessMail;
            }
 
            return mResult;
        }
 
 
        /**
         * 收集设备参数信息
         * @param ctx
         */
        private Map<String, String> collectDeviceInfo(Context ctx) {
            Map<String, String> infos = new HashMap<String, String>();
            try {
                PackageManager pm = ctx.getPackageManager();
                PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
                if (pi != null) {
                    infos.put("packageName", pi.packageName);
                    String versionName = pi.versionName == null ? "null" : pi.versionName;
                    String versionCode = pi.versionCode + "";               
                    infos.put("versionName", versionName);
                    infos.put("versionCode", versionCode);               
                }
            } catch (NameNotFoundException e) {
                Log.e(TAG, "an error occured when collect package info", e);
            }
            Field[] fields = Build.class.getDeclaredFields();
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    infos.put(field.getName(), field.get(null).toString());
                    Log.d(TAG, field.getName() + " : " + field.get(null));
                } catch (Exception e) {
                    Log.e(TAG, "an error occured when collect crash info", e);
                }
            }
            return infos;
        }
 
        /**
         * 保存错误信息到文件中
         *
         * @param ex
         * @return    返回文件名称,便于将文件传送到服务器
         */
        private String saveCrashInfo2File(Context ctx,Throwable ex) {
            Map<String, String> infos = collectDeviceInfo(ctx);
 
            StringBuffer sb = new StringBuffer();
            for (Map.Entry<String, String> entry : infos.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                sb.append(key + "=" + value + "\n");
            }
 
            Writer writer = new StringWriter();
            PrintWriter printWriter = new PrintWriter(writer);
            ex.printStackTrace(printWriter);
            Throwable cause = ex.getCause();
            while (cause != null) {
                cause.printStackTrace(printWriter);
                cause = cause.getCause();
            }
            printWriter.close();
            String result = writer.toString();
            sb.append(result);
            try {
                long timestamp = System.currentTimeMillis();
                String time = formatter.format(new Date());
                String fileName = "crash_" + time + "_" + timestamp + ".log";
                String fileAbsPath = "";
                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) &&
                        !Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
                    fileAbsPath = Environment.getExternalStorageDirectory().getPath()+File.separator+CRASH_FLOD_NAME+File.separator;
                }
                else{
                    fileAbsPath = mContext.getFilesDir().getPath()+File.separator+CRASH_FLOD_NAME+File.separator;
                }
 
                File dir = new File(fileAbsPath);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
 
                FileOutputStream fos = new FileOutputStream(fileAbsPath + fileName);
                fos.write(sb.toString().getBytes());
                fos.close();
                return fileAbsPath + fileName;
            } catch (Exception e) {
                Log.e(TAG, "an error occured while writing file...", e);
            }
            return null;
        }
 
 
        private  boolean sendMailByJavaMail(String filePath) {
            Mail m = new Mail("xx@126.com", "xxxx");
            m.set_debuggable(false);
            String[] toArr = {"xx@126.com"};
            m.set_to(toArr);
            m.set_from("xx@126.com");
            m.set_subject("CrashReport");
            m.setBody("Email body. test by Java Mail final");
            try {
                m.addAttachment(filePath);
 
                if(m.send()) {
                    Log.i("test","Email was sent successfully.");
                    return true;
 
                } else {
                    Log.i("test","Email was sent failed.");
                    return false;
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("test", "Could not send email", e);
            }
 
            return false;
        }
 
 
        private void deleteFile(String filePath)
        {
            File file = new File(filePath);
            if(file!= null && file.exists()){
                file.delete();
            }
        }
    }
}

 
Mail.java
发送邮件。该部分是参考了网上的相关资料。这里需要引入三个jar包。已打包放在附件。

package com.amanda.crash2file;
 
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.BodyPart;
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;
 
 
public class Mail extends javax.mail.Authenticator {  
    private String _user;  
    private String _pass;   
    private String[] _to;  
    private String _from;   
    private String _port;  
    private String _sport;   
    private String _host;   
    private String _subject;  
    private String _body;   
    private boolean _auth;     
    private boolean _debuggable;   
    private Multipart _multipart;    
    public Mail() {    
        _host = "smtp.126.com"; // default smtp server   
        _port = "465"; // default smtp port    
        _sport = "465"; // default socketfactory port    
        _user = ""; // username     _pass = ""; // password   
        _from = ""; // email sent from  
        _subject = ""; // email subject
        _body = ""; // email body 
        _debuggable = false; // debug mode on or off - default off 
        _auth = true; // smtp authentication - default on 
        _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);   }  
    public String get_user() {
        return _user;
    }
    public void set_user(String _user) {
        this._user = _user;
    }
    public String get_pass() {
        return _pass;
    }
    public void set_pass(String _pass) {
        this._pass = _pass;
    }
    public String[] get_to() {
        return _to;
    }
    public void set_to(String[] _to) {
        this._to = _to;
    }
    public String get_from() {
        return _from;
    }
    public void set_from(String _from) {
        this._from = _from;
    }
    public String get_port() {
        return _port;
    }
    public void set_port(String _port) {
        this._port = _port;
    }
    public String get_sport() {
        return _sport;
    }
    public void set_sport(String _sport) {
        this._sport = _sport;
    }
    public String get_host() {
        return _host;
    }
    public void set_host(String _host) {
        this._host = _host;
    }
    public String get_subject() {
        return _subject;
    }
    public void set_subject(String _subject) {
        this._subject = _subject;
    }
    public String get_body() {
        return _body;
    }
    public void set_body(String _body) {
        this._body = _body;
    }
    public boolean is_auth() {
        return _auth;
    }
    public void set_auth(boolean _auth) {
        this._auth = _auth;
    }
    public boolean is_debuggable() {
        return _debuggable;
    }
    public void set_debuggable(boolean _debuggable) {
        this._debuggable = _debuggable;
    }
    public Multipart get_multipart() {
        return _multipart;
    }
    public void set_multipart(Multipart _multipart) {
        this._multipart = _multipart;
    }
    public Mail(String user, String pass) {   
        this();  
        _user = user;   
        _pass = pass; 
        }   
    public boolean send() throws Exception {
        Properties props = _setProperties();
        if(!_user.equals("") && !_pass.equals("") && _to.length > 0 && !_from.equals("") && !_subject.equals("") && !_body.equals("")) {    
            Session session = Session.getInstance(props, this);     
            MimeMessage msg = new MimeMessage(session);     
            msg.setFrom(new InternetAddress(_from));       
            InternetAddress[] addressTo = new InternetAddress[_to.length];  
            for (int i = 0; i < _to.length; i++) {  
                addressTo[i] = new InternetAddress(_to[i]); 
                }       
            msg.setRecipients(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 filename) throws Exception {  
            BodyPart messageBodyPart = new MimeBodyPart(); 
            DataSource source = new FileDataSource(filename); 
            messageBodyPart.setDataHandler(new DataHandler(source)); 
            messageBodyPart.setFileName(filename);   
            _multipart.addBodyPart(messageBodyPart);
            }  
        @Override   public PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(_user, _pass); 
            }
        private Properties _setProperties() { 
            Properties props = new Properties(); 
            props.put("mail.smtp.host", _host);    
            if(_debuggable) { 
                props.put("mail.debug", "true");     }  
            if(_auth) {    
                props.put("mail.smtp.auth", "true");  
                }      props.put("mail.smtp.port", _port);  
                props.put("mail.smtp.socketFactory.port", _sport); 
                props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");  
                props.put("mail.smtp.socketFactory.fallback", "false");  
                return props;   }    // the getters and setters 
        public String getBody() {   
            return _body;   }  
        public void setBody(String _body) {
 
            this._body = _body;
            }    // more of the getters and setters ….. }
    }

 
另外,需增加可以访问网络的权限
AndroidManifest.xml
<? xml   version = "1.0"   encoding = "utf-8" ?>
< manifest   xmlns:android = "http://schemas.android.com/apk/res/android"
     package = "com.amanda.crash2file"
     android:versionCode = "2"
     android:versionName = "1.1"   >
     < uses-sdk
         android:minSdkVersion = "15"
         android:targetSdkVersion = "15"   />
    
     < uses-permission   android:name = "android.permission.WRITE_EXTERNAL_STORAGE" />
     < uses-permission   android:name = "android.permission.INTERNET" />
     < application
         android:name = ".CrashApplication"         
         android:allowBackup = "true"
         android:icon = "@drawable/ic_launcher"
         android:label = "@string/app_name"
         android:theme = "@style/AppTheme"   >
         < activity
             android:name = "com.amanda.crash2file.MainActivity"
             android:label = "@string/app_name"   >
             < intent-filter >
                 < action   android:name = "android.intent.action.MAIN"   />
                 < category   android:name = "android.intent.category.LAUNCHER"   />
             </ intent-filter >
         </ activity >
     </ application >
</ manifest >

 
实现功能:当出现UncaughtException时,将其相关信息保存到文件/sdcard/crash/xx.log或者/data/data/<package name>/file/crash/xx.log,并且将该文件以附件的形式发送到邮箱。如果发送成功,则将保存的本地crash report删除。
 
后续需要增强:
1、成功发送邮件的几率不高,需提高成功率
2、每次发送的邮件附件改成crash目录下的所有本地report,这样没有发送成功的report可以下次再尝试发送
3、本地目录文件管理&封装
4、在此文的基础上,实现用户行为report或者Log report等等

附件列表

保存全局Crash报告&发送邮件的更多相关文章

  1. 保存全局Crash报告

    CrashHandler.java UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告 package  com.amanda;imp ...

  2. Android应用如何反馈Crash报告

    转自:http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html 一.为什么要Crash crash可以理解成堕落,垮台.按照我们 ...

  3. ios如何生成crash报告

    #include <signal.h> #include <execinfo.h> void OnProcessExceptionHandler(int sigl) { do ...

  4. BFS(广度优先搜索遍历保存全局状态,华容道翻版做法)--08--DFS--蓝桥杯青蛙跳杯子

    题目描述 X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色. X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去. 如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙 ...

  5. Breakpad Google的crash捕获、抓取开源库

    简介: Breadpad为google chrominum项目下用于处理dump的一套工具:内部采用跨平台方式实现捕获.生成.解析与平台无关的dump,便于统一处理:支持进程内与进程外捕获,当为进程外 ...

  6. iOS应用的crash日志的分析基础

        Outline如何获得crash日志如何解析crash日志如何分析crash日志     1. iOS策略相关     2. 常见错误标识     3. 代码bug 一.如何获得crash日志 ...

  7. Oracle AWR报告生成和性能分析

    目录 一.AWE报告生成步骤 1.1 工具选择 1.2 自动创建快照 1.3 手工创建快照 1.4 生成AWR报告 二.AWR报告分析 2.1 AWR之DB Time 2.2 AWR之load_pro ...

  8. 定时执行自动化脚本-(二)ant发送邮件及邮件中添加附件

    发送邮件及邮件添加附件均需要用java来实现 1.idea创建一个maven的java项目,目录结构如下 2.pom.xml文件添加依赖的javax.mail <dependencies> ...

  9. 了解和分析iOS Crash

    WeTest 导读 北京时间凌晨一点,苹果一年一度的发布会如期而至.新机型的发布又会让适配相关的同学忙上一阵子啦,并且iOS Crash的问题始终伴随着移动开发者.本文将从三个阶段,由浅入深的介绍如何 ...

随机推荐

  1. 20155303 《Java程序设计》实验一(Java开发环境的熟悉)实验报告

    20155303 <Java程序设计>实验一(Java开发环境的熟悉)实验报告 一.实验内容及步骤 (一)使用JDK编译.运行简单的java程序 命令行下的程序开发 步骤一(新建文件夹): ...

  2. Python Webdriver 重新使用已经打开的浏览器实例

    因为Webdriver每次实例化都会新开一个全新的浏览器会话,在有些情况下需要复用之前打开未关闭的会话.比如爬虫,希望结束脚本时,让浏览器处于空闲状态.当脚本重新运行时,它将继续使用这个会话工作.还就 ...

  3. 理解mipi协议【转】

    转自:http://blog.csdn.net/wanglining1987/article/details/50202615 完成mipi信号通道分配后,需要生成与物理层对接的时序.同步信号: MI ...

  4. MySQL5.7之多源复制&Nginx中间件(上)【转】

    有生之年系列----MySQL5.7之多源复制&Nginx中间件(上)-wangwenan6-ITPUB博客http://blog.itpub.net/29510932/viewspace-1 ...

  5. avalonJS-源码阅读(三) VMODEL

    avalon的重头戏.终于要到我最期待的vmodel了. ps:这篇博文想做的全一点,错误少一点,所以会有后续的更新在这篇文章中. 状态:一稿 目录[-] avalon dom小结 数据结构 观察者模 ...

  6. pre,html转义,abbr缩写,表格table

    <pre></pre>预定义文本标签pre(保留换行和空格) <sdds>对html转义 <abbr title="sddsdsds"&g ...

  7. Django集成Xadmin list index out of range报错解决方案

    return self.render(context) File "C:\Python36\lib\site-packages\django\template\defaulttags.py& ...

  8. Java基础82 jsp中的EL表达式(网页知识)

    1.EL表达式的作用 EL表达式的作用:向浏览器输出域对象中的变量值或者表达式计算结果.语法:${变量或者表达式} 注: Jsp的核心语法:jsp的表达式<%= %>和jsp的脚本< ...

  9. js数组基本操作

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtm ...

  10. javaweb 要学习的东西

    我学院课设是Javaweb程序,要用eclipse,tomcat,jbdc,和数据库 jbdc,是连接数据库的驱动,tomcat是一种类似于服务器的东西,现在买不起服务器,就用tomcat