上篇写到,将程序中没有处理到的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. java数字转字符串前面自动补0或者其他数字

    /**  * Java里数字转字符串前面自动补0的实现.  *  * @author  xiaomo *  */  public class TestStringFormat {    public ...

  2. sleep命令

    sleep支持睡眠(分,小时) sleep 1 睡眠1秒 sleep 1s 睡眠1秒 sleep 1m 睡眠1分 sleep 1h 睡眠1小时

  3. HTML5 localStorage、sessionStorage 作用域

    一.localStorage localStorage有效期:永不失效,除非web应用主动删除. localStorage作用域:localStorage的作用域是限定在文档源级别的.文档源通过协议. ...

  4. 洛谷P2746校园网

    传送门啦 下面来看任务B.我们发现,图中只要存在入度为0的点和出度为0的点就永远不可能满足要求:" 不论我们给哪个学校发送新软件,它都会到达其余所有的学校 ".我们还发现,只要在入 ...

  5. HDU 3374 String Problem(KMP+最大(最小)表示)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3374 题目大意:给出一个字符串,依次左移一个单位形成一堆字符串,求其字典序最小和最大的字符串需要左移多 ...

  6. Codeforces 777E - Hanoi Factory(贪心+栈)

    题目链接:http://codeforces.com/problemset/problem/777/E 题意:有n个环给你内环半径.外环半径和高度,叠这些环还要满足以下要求: ①:下面的环的外径要&g ...

  7. 由结构体成员地址计算结构体地址——list_entry()原理详解

    #define list_entry(ptr, type, member) container_of(ptr, type, member) 在进行编程的时候,我们经常在知道结构体地址的情况下,寻找其中 ...

  8. MySql数据库 主从复制/共享 报错

    从 获取不到 共享主的数据, 错误信息: Waiting for master to send event 解决方案: // 1. 从V表获取PrNo的数据 select * from Vendor_ ...

  9. Spark(十)Spark之数据倾斜调优

    一 调优概述 有的时候,我们可能会遇到大数据计算中一个最棘手的问题——数据倾斜,此时Spark作业的性能会比期望差很多.数据倾斜调优,就是使用各种技术方案解决不同类型的数据倾斜问题,以保证Spark作 ...

  10. 【58沈剑架构系列】mysql并行复制优化思路

    一.缘起 mysql主从复制,读写分离是互联网用的非常多的mysql架构,主从复制最令人诟病的地方就是,在数据量较大并发量较大的场景下,主从延时会比较严重. 为什么mysql主从延时这么大? 回答:从 ...