应用崩溃后发送错误日志

上篇文章只是粗略的讲到到异常捕获的处理机制。当我们应用奔溃时,不一定能复现或者查看到log。所以需要将错误信息保存到日志,并在保存后发送日志到服务器或者邮箱。在此我们需要3个jar包。activation.jar additionnal.jar mail.jar。附上链接下载链接。http://download.csdn.net/download/android_cmos/9493514

下载依赖之后。在自定义一个类继承于Application,并在AndroidManifest.xml中的application节点中使用name属性,将类名添加进去,这样当程序一启动就会先执行继承自application类里面的配置,最后要别忘了添加权限,一个是网络权限,一个是往sd卡写的权限

public class CrashApplication extends Application {

@Override
public void onCreate() {
    super.onCreate();
    
    initEmailReporter();
}

/**
 * 使用EMAIL发送日志
 */
private void initEmailReporter() {
    CrashEmailReporter reporter = new CrashEmailReporter(this);
    reporter.setReceiver("[email protected]");//接收日志的邮箱
    reporter.setSender("[email protected]");//发送日志的邮箱
    reporter.setSendPassword("xxxxx");//说到这个密码,你可以设置一个客户端授权码,它是登录第三方客户端的专用密码,和主登录密码不冲突
    reporter.setSMTPHost("smtp.163.com");//这里使用的是163发送邮件的服务,所以主机名是163的,有需要修改的,也可以更改对应的主机名
    reporter.setPort("465");//端口号,可选25端口号,具体的看是否使用ssl安全协议
    AndroidCrash.getInstance().setCrashReporter(reporter).init(this);
}

/**
 * 使用HTTP发送日志
 */
private void initHttpReporter() {
    CrashHttpReporter reporter = new CrashHttpReporter(this) {
        /**
         * 重写此方法,可以弹出自定义的崩溃提示对话框,而不使用系统的崩溃处理。
         *
         * @param thread
         * @param ex
         */
        @Override
        public void closeApp(Thread thread, Throwable ex) {
            //  final Activity activity = AppManager.currentActivity();
            //   Toast.makeText(activity, "发生异常,正在退出", Toast.LENGTH_SHORT).show();
            // 自定义弹出对话框
            new AlertDialog.Builder(getApplicationContext()).
                    setMessage("程序发生异常,现在退出").
                    setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            //      AppManager.AppExit(activity);
                        }
                    }).create().show();
            Log.d("MyApplication", "thead:" + Thread.currentThread().getName());
        }
    };
    reporter.setUrl("http://xxx.crashreport.jd-app.com/your_receiver").setFileParam("fileName").setToParam("to").setTo("你的接收邮箱").setTitleParam("subject").setBodyParam("message");
    reporter.setCallback(new CrashHttpReporter.HttpReportCallback() {
        @Override
        public boolean isSuccess(int i, String s) {
            return s.endsWith("ok");
        }
    });
    AndroidCrash.getInstance().setCrashReporter(reporter).init(this);
}
}

上面邮箱中需要有一个授权码,这个授权码是可以不同于邮箱的登录密码的。对于自动发送邮件的,一定要邮箱开启SMTP服务功能,否则程序会报一个自动验证失败的异常,发送不了邮件。具体怎么设置SMTP服务,请点击下面的链接 http://jingyan.baidu.com/article/0aa223755d15dc88cd0d6473.html

先写个保存好日志之后的回调

  public interface CrashListener {
      void sendFile(File var1);

      void closeApp(Thread var1, Throwable var2);
  }

然后是用于处理崩溃异常的类,它要实现UncaughtExceptionHandler接口。实现它之后,将它设为默认的线程异常的处理者,这样程序崩溃之后,就会调用它了。但是在调用它之前,还需要先获取保存之前默认的handler,用于在我们收集了异常之后对程序进行处理,比如默认的弹出“程序已停止运行”的对话框(当然你也可以自己实现一个),终止程序,打印LOG

  public class CrashCatcher implements UncaughtExceptionHandler {
  private static final String LOG_TAG = CrashCatcher.class.getSimpleName();
   private static final CrashCatcher sHandler = new CrashCatcher();
   private CrashListener mListener;
   private File mLogFile;

   public CrashCatcher() {
   }

   public static CrashCatcher getInstance() {
       return sHandler;
   }

   public void uncaughtException(final Thread thread, final Throwable ex) {
       try {
           LogWriter.writeLog(this.mLogFile, "CrashHandler", ex.getMessage(), ex);
       } catch (Exception var4) {
           Log.w(LOG_TAG, var4);
       }

       this.mListener.sendFile(this.mLogFile);
       (new Thread(new Runnable() {
           public void run() {
               Looper.prepare();

               try {
                   CrashCatcher.this.mListener.closeApp(thread, ex);
               } catch (Exception var2) {
                   var2.printStackTrace();
               }

               Looper.loop();
           }
       })).start();
   }

   public void init(File logFile, CrashListener listener) {
       AssertUtil.assertNotNull("logFile", logFile);
       AssertUtil.assertNotNull("crashListener", listener);
       this.mLogFile = logFile;
       this.mListener = listener;
   }
}

然后是保存log的类。就是在发生未能捕获的异常之后,保存LOG到文件,然后 调用前面定义的接口,对日志文件进行处理。其中LogWriter是我实现的保存LOG到文件的类。代码如下:

  public class LogWriter {
private static final SimpleDateFormat timeFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.getDefault());

public LogWriter() {
}

public static synchronized void writeLog(File logFile, String tag, String message, Throwable tr) {
    logFile.getParentFile().mkdirs();
    if(!logFile.exists()) {
        try {
            logFile.createNewFile();
        } catch (IOException var13) {
            var13.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 var11) {
            closeQuietly(fileWriter);
            closeQuietly(bufdWriter);
            closeQuietly(printWriter);
        }

    }
}

public static void closeQuietly(Closeable closeable) {
    if(closeable != null) {
        try {
            closeable.close();
        } catch (IOException var2) {
            ;
        }
    }
}
}

最终在日志保存之后,需要生成一个报告,并发送给服务器。报告的方法,可以是发送到邮箱,或者http请求发送给服务器。所以写了一个抽象类,实现了生成标题和内容,设置日志路径等。代码如下

public abstract class AbstractCrashHandler implements CrashListener {
private static final UncaughtExceptionHandler sDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
private Context mContext;
private ExecutorService mSingleExecutor = Executors.newSingleThreadExecutor();
protected Future mFuture;
private int TIMEOUT = 5;

public AbstractCrashHandler(Context context) {
    this.mContext = context;
}

protected abstract void sendReport(String var1, String var2, File var3);

public void sendFile(final File file) {
    if(this.mFuture != null && !this.mFuture.isDone()) {
        this.mFuture.cancel(false);
    }

    this.mFuture = this.mSingleExecutor.submit(new Runnable() {
        public void run() {
            AbstractCrashHandler.this.sendReport(AbstractCrashHandler.this.buildTitle(AbstractCrashHandler.this.mContext), AbstractCrashHandler.this.buildBody(AbstractCrashHandler.this.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 e = pm.getPackageInfo(ai.packageName, 0);
        sb.append("Version Code: ").append(e.versionCode).append('\n');
        sb.append("Version Name: ").append(e.versionName).append('\n');
    } catch (NameNotFoundException var6) {
        var6.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();
}

public void closeApp(Thread thread, Throwable ex) {
    try {
        this.mFuture.get((long)this.TIMEOUT, TimeUnit.SECONDS);
    } catch (Exception var4) {
        var4.printStackTrace();
    }

    sDefaultHandler.uncaughtException(thread, ex);
}
}

这里写了报告的一种实现,发送邮件。继承自Authenticator

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() {
    this.multipart = new MimeMultipart();
    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);
    this.props = new Properties();
    this.props.put("mail.smtp.host", this.host);
    this.props.put("mail.smtp.auth", "true");
    this.props.put("mail.smtp.port", this.port);
    this.props.put("mail.smtp.socketFactory.port", this.port);
    this.props.put("mail.transport.protocol", "smtp");
    this.props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    this.props.put("mail.smtp.socketFactory.fallback", "false");
}

public boolean send() throws MessagingException {
    if(!this.user.equals("") && !this.pass.equals("") && !this.to.equals("") && !this.from.equals("")) {
        Session session = Session.getDefaultInstance(this.props, this);
        Log.d("SendUtil", this.host + "..." + this.port + ".." + this.user + "..." + this.pass);
        MimeMessage msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress(this.from));
        InternetAddress addressTo = new InternetAddress(this.to);
        msg.setRecipient(RecipientType.TO, addressTo);
        msg.setSubject(this.subject);
        msg.setSentDate(new Date());
        MimeBodyPart messageBodyPart = new MimeBodyPart();
        messageBodyPart.setText(this.body);
        this.multipart.addBodyPart(messageBodyPart, 0);
        msg.setContent(this.multipart);
        Transport.send(msg);
        return true;
    } else {
        return false;
    }
}

public void addAttachment(String filePath, String fileName) throws Exception {
    MimeBodyPart messageBodyPart = new MimeBodyPart();
    ((MimeBodyPart)messageBodyPart).attachFile(filePath);
    this.multipart.addBodyPart(messageBodyPart);
}

public PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication(this.user, this.pass);
}
}

然后是发送报告邮件的类了,它继承自前面所写的AbstractCrashReportHandler

public class CrashEmailReporter extends AbstractCrashHandler {
private String mReceiveEmail;
private String mSendEmail;
private String mSendPassword;
private String mHost;
private String mPort;

public CrashEmailReporter(Context context) {
    super(context);
}

public void setReceiver(String receiveEmail) {
    this.mReceiveEmail = receiveEmail;
}

public void setSender(String email) {
    this.mSendEmail = email;
}

public void setSendPassword(String password) {
    this.mSendPassword = password;
}

public void setSMTPHost(String host) {
    this.mHost = host;
}

public void setPort(String port) {
    this.mPort = port;
}

protected void sendReport(String title, String body, File file) {
    LogMail sender = (new LogMail()).setUser(this.mSendEmail).setPass(this.mSendPassword).setFrom(this.mSendEmail).setTo(this.mReceiveEmail).setHost(this.mHost).setPort(this.mPort).setSubject(title).setBody(body);
    sender.init();

    try {
        sender.addAttachment(file.getPath(), file.getName());
        sender.send();
        file.delete();
    } catch (Exception var6) {
        var6.printStackTrace();
    }

}
}

这样就把发送日志的功能完成了。然后在MainActivity 中模拟任何崩溃的情况,日志就会保存下来,并能自动发送日志到邮箱了。发送到服务器的先不讲了。
给个最终发送邮箱成功的图。

应用崩溃后发送错误日志_第1张图片
1.png
应用崩溃后发送错误日志_第2张图片
2.png

你可能感兴趣的:(应用崩溃后发送错误日志)