應(yīng)用崩潰后發(fā)送錯(cuò)誤日志

上篇文章只是粗略的講到到異常捕獲的處理機(jī)制。當(dāng)我們應(yīng)用奔潰時(shí),不一定能復(fù)現(xiàn)或者查看到log。所以需要將錯(cuò)誤信息保存到日志,并在保存后發(fā)送日志到服務(wù)器或者郵箱。在此我們需要3個(gè)jar包。activation.jar additionnal.jar mail.jar。附上鏈接下載鏈接。http://download.csdn.net/download/android_cmos/9493514

下載依賴(lài)之后。在自定義一個(gè)類(lèi)繼承于Application,并在AndroidManifest.xml中的application節(jié)點(diǎn)中使用name屬性,將類(lèi)名添加進(jìn)去,這樣當(dāng)程序一啟動(dòng)就會(huì)先執(zhí)行繼承自application類(lèi)里面的配置,最后要?jiǎng)e忘了添加權(quán)限,一個(gè)是網(wǎng)絡(luò)權(quán)限,一個(gè)是往sd卡寫(xiě)的權(quán)限

public class CrashApplication extends Application {

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

/**
 * 使用EMAIL發(fā)送日志
 */
private void initEmailReporter() {
    CrashEmailReporter reporter = new CrashEmailReporter(this);
    reporter.setReceiver("xxxxxx@qq.com");//接收日志的郵箱
    reporter.setSender("xxxxx@163.com");//發(fā)送日志的郵箱
    reporter.setSendPassword("xxxxx");//說(shuō)到這個(gè)密碼,你可以設(shè)置一個(gè)客戶(hù)端授權(quán)碼,它是登錄第三方客戶(hù)端的專(zhuān)用密碼,和主登錄密碼不沖突
    reporter.setSMTPHost("smtp.163.com");//這里使用的是163發(fā)送郵件的服務(wù),所以主機(jī)名是163的,有需要修改的,也可以更改對(duì)應(yīng)的主機(jī)名
    reporter.setPort("465");//端口號(hào),可選25端口號(hào),具體的看是否使用ssl安全協(xié)議
    AndroidCrash.getInstance().setCrashReporter(reporter).init(this);
}

/**
 * 使用HTTP發(fā)送日志
 */
private void initHttpReporter() {
    CrashHttpReporter reporter = new CrashHttpReporter(this) {
        /**
         * 重寫(xiě)此方法,可以彈出自定義的崩潰提示對(duì)話框,而不使用系統(tǒng)的崩潰處理。
         *
         * @param thread
         * @param ex
         */
        @Override
        public void closeApp(Thread thread, Throwable ex) {
            //  final Activity activity = AppManager.currentActivity();
            //   Toast.makeText(activity, "發(fā)生異常,正在退出", Toast.LENGTH_SHORT).show();
            // 自定義彈出對(duì)話框
            new AlertDialog.Builder(getApplicationContext()).
                    setMessage("程序發(fā)生異常,現(xiàn)在退出").
                    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);
}
}

上面郵箱中需要有一個(gè)授權(quán)碼,這個(gè)授權(quán)碼是可以不同于郵箱的登錄密碼的。對(duì)于自動(dòng)發(fā)送郵件的,一定要郵箱開(kāi)啟SMTP服務(wù)功能,否則程序會(huì)報(bào)一個(gè)自動(dòng)驗(yàn)證失敗的異常,發(fā)送不了郵件。具體怎么設(shè)置SMTP服務(wù),請(qǐng)點(diǎn)擊下面的鏈接 http://jingyan.baidu.com/article/0aa223755d15dc88cd0d6473.html

先寫(xiě)個(gè)保存好日志之后的回調(diào)

  public interface CrashListener {
      void sendFile(File var1);

      void closeApp(Thread var1, Throwable var2);
  }

然后是用于處理崩潰異常的類(lèi),它要實(shí)現(xiàn)UncaughtExceptionHandler接口。實(shí)現(xiàn)它之后,將它設(shè)為默認(rèn)的線程異常的處理者,這樣程序崩潰之后,就會(huì)調(diào)用它了。但是在調(diào)用它之前,還需要先獲取保存之前默認(rèn)的handler,用于在我們收集了異常之后對(duì)程序進(jìn)行處理,比如默認(rèn)的彈出“程序已停止運(yùn)行”的對(duì)話框(當(dāng)然你也可以自己實(shí)現(xiàn)一個(gè)),終止程序,打印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的類(lèi)。就是在發(fā)生未能捕獲的異常之后,保存LOG到文件,然后 調(diào)用前面定義的接口,對(duì)日志文件進(jìn)行處理。其中LogWriter是我實(shí)現(xiàn)的保存LOG到文件的類(lèi)。代碼如下:

  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) {
            ;
        }
    }
}
}

最終在日志保存之后,需要生成一個(gè)報(bào)告,并發(fā)送給服務(wù)器。報(bào)告的方法,可以是發(fā)送到郵箱,或者h(yuǎn)ttp請(qǐng)求發(fā)送給服務(wù)器。所以寫(xiě)了一個(gè)抽象類(lèi),實(shí)現(xiàn)了生成標(biāo)題和內(nèi)容,設(shè)置日志路徑等。代碼如下

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);
}
}

這里寫(xiě)了報(bào)告的一種實(shí)現(xiàn),發(fā)送郵件。繼承自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);
}
}

然后是發(fā)送報(bào)告郵件的類(lèi)了,它繼承自前面所寫(xiě)的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();
    }

}
}

這樣就把發(fā)送日志的功能完成了。然后在MainActivity 中模擬任何崩潰的情況,日志就會(huì)保存下來(lái),并能自動(dòng)發(fā)送日志到郵箱了。發(fā)送到服務(wù)器的先不講了。
給個(gè)最終發(fā)送郵箱成功的圖。

1.png
2.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,511評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,256評(píng)論 6 342
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,825評(píng)論 25 709
  • CGRect mainViewBounds = self.navigationController.view.b...
    leaderleader閱讀 646評(píng)論 0 0
  • 比如定義一個(gè)Logo組件 使用 this.props.onTimeOut(); 調(diào)用了onTimeOut屬性。然...
    taiji1985閱讀 1,531評(píng)論 0 0

友情鏈接更多精彩內(nèi)容