保存全局Crash报告&发送邮件

上篇写到,将程序中没有处理到的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)