应用崩溃后发送错误日志

上篇文章只是粗略的讲到到异常捕获的处理机制。当我们应用奔溃时,不一定能复现或者查看到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("xxxxxx@qq.com");//接收日志的邮箱
    reporter.setSender("xxxxx@163.com");//发送日志的邮箱
    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.png
2.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容