Android程式崩潰異常收集架構

來源:互聯網
上載者:User

最近在寫Android程式崩潰異常處理,完成之後,稍加封裝與大家分享。

我的思路是這樣的,在程式崩潰之後,將異常資訊儲存到一個記錄檔中,然後對該檔案進行處理,比如發送到郵箱,或發送到伺服器。

所以,第一步是先定義一個介面,用於在儲存好日誌之後的回調。代碼如下:

/* * @(#)CrashListener.java       Project: crash * Date:2014-5-27 * * Copyright (c) 2014 CFuture09, Institute of Software,  * Guangdong Ocean University, Zhanjiang, GuangDong, China. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); *  you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.githang.android.crash;import java.io.File;/** * @author Geek_Soledad  */public interface CrashListener {    /**     * 儲存異常的日誌。     *      * @param file     */    public void afterSaveCrash(File file);}

接下來是用於處理崩潰異常的類,它要實現UncaughtExceptionHandler介面。實現它之後,將它設為預設的線程異常的處理者,這樣程式崩潰之後,就會調用它了。但是在調用它之前,還需要先擷取儲存之前預設的handler,用於在我們收集了異常之後對程式進行處理,比如預設的彈出“程式已停止運行”的對話方塊(當然你也可以自己實現一個),終止程式,列印LOG。

我的實現如下:

/* * @(#)CrashHandler.java       Project: crash * Date:2014-5-26 * * Copyright (c) 2014 CFuture09, Institute of Software,  * Guangdong Ocean University, Zhanjiang, GuangDong, China. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); *  you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.githang.android.crash;import java.io.File;import java.lang.Thread.UncaughtExceptionHandler;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;/** * @author Geek_Soledad  */public class CrashHandler implements UncaughtExceptionHandler {    private static final CrashHandler sHandler = new CrashHandler();    private static final UncaughtExceptionHandler sDefaultHandler = Thread            .getDefaultUncaughtExceptionHandler();    private static final ExecutorService THREAD_POOL = Executors.newSingleThreadExecutor();    private Future future;    private CrashListener mListener;    private File mLogFile;    public static CrashHandler getInstance() {        return sHandler;    }    @Override    public void uncaughtException(Thread thread, Throwable ex) {        CrashLogUtil.writeLog(mLogFile, "CrashHandler", ex.getMessage(), ex);        future = THREAD_POOL.submit(new Runnable() {            public void run() {                if (mListener != null) {                    mListener.afterSaveCrash(mLogFile);                }            };        });        if (!future.isDone()) {            try {                future.get();            } catch (Exception e) {                e.printStackTrace();            }        }        sDefaultHandler.uncaughtException(thread, ex);    }    public void init(File logFile, CrashListener listener) {        mLogFile = logFile;        mListener = listener;    }}

這個類很簡單,就是在發生未能捕獲的異常之後,儲存LOG到檔案,然後 調用前面定義的介面,對記錄檔進行處理。其中CrashLogUtil是我實現的儲存LOG到檔案的類。代碼如下:
/* * @(#)LogUtil.java       Project: crash * Date:2014-5-27 * * Copyright (c) 2014 CFuture09, Institute of Software,  * Guangdong Ocean University, Zhanjiang, GuangDong, China. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); *  you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.githang.android.crash;import java.io.BufferedWriter;import java.io.Closeable;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Locale;/** * @author Geek_Soledad  */public class CrashLogUtil {    private static final SimpleDateFormat timeFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS",            Locale.getDefault());    /**     * 將日誌寫入檔案。     *      * @param tag     * @param message     * @param tr     */    public static synchronized void writeLog(File logFile, String tag, String message, Throwable tr) {        logFile.getParentFile().mkdirs();        if (!logFile.exists()) {            try {                logFile.createNewFile();            } catch (IOException e) {                e.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 e) {                closeQuietly(fileWriter);                closeQuietly(bufdWriter);                closeQuietly(printWriter);            }        }    }    public static void closeQuietly(Closeable closeable) {        if (closeable != null) {            try {                closeable.close();            } catch (IOException ioe) {                // ignore            }        }    }}

在日誌儲存之後,我們還需要產生一個報告,並發送給伺服器。報告的方法,可以是發送到郵箱,或者http請求發送給伺服器。所以這裡寫了一個抽象類別,實現了產生標題和內容,設定日誌路徑等。代碼如下:

/* * @(#)AbstractReportHandler.java       Project: crash * Date:2014-5-27 * * Copyright (c) 2014 CFuture09, Institute of Software,  * Guangdong Ocean University, Zhanjiang, GuangDong, China. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); *  you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.githang.android.crash;import java.io.File;import android.content.Context;import android.content.pm.ApplicationInfo;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.os.Build;/** * @author Geek_Soledad  */public abstract class AbstractCrashReportHandler implements CrashListener {    private Context mContext;    public AbstractCrashReportHandler(Context context) {        mContext = context;        CrashHandler handler = CrashHandler.getInstance();        handler.init(getLogDir(context), this);        Thread.setDefaultUncaughtExceptionHandler(handler);    }    protected File getLogDir(Context context) {        return new File(context.getFilesDir(), "crash.log");    }    protected abstract void sendReport(String title, String body, File file);    @Override    public void afterSaveCrash(File file) {        sendReport(buildTitle(mContext), buildBody(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 pi = pm.getPackageInfo(ai.packageName, 0);            sb.append("Version Code: ").append(pi.versionCode).append('\n');            sb.append("Version Name: ").append(pi.versionName).append('\n');        } catch (PackageManager.NameNotFoundException e) {            e.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();    }}

這樣一個架構就算基本完成了。

當然,下面我還給出了報告的一種實現,發送郵件。

如何發送郵箱,網上已有不少資料,這裡不再簡而言之。

首先需要用到三個jar包: activation.jar, additionnal.jar, mail.jar。

然後 寫一個類,繼承自Authenticator。代碼如下:

/* * @(#)Snippet.java       Project: CrashHandler * Date: 2014-5-27 * * Copyright (c) 2014 CFuture09, Institute of Software,  * Guangdong Ocean University, Zhanjiang, GuangDong, China. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); *  you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.githang.android.crash;import android.util.Log;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.Authenticator;import javax.mail.BodyPart;import javax.mail.MessagingException;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;/** * Author: msdx (645079761@qq.com) Time: 14-5-27 上午9:07 */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() {        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);        props = new Properties();        props.put("mail.smtp.host", host);        props.put("mail.smtp.auth", "true");        props.put("mail.smtp.port", port);        props.put("mail.smtp.socketFactory.port", port);        props.put("mail.transport.protocol", "smtp");        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");        props.put("mail.smtp.socketFactory.fallback", "false");    }    public boolean send() throws MessagingException {        if (!user.equals("") && !pass.equals("") && !to.equals("") && !from.equals("")) {            Session session = Session.getDefaultInstance(props, this);            Log.d("SendUtil", host + "..." + port + ".." + user + "..." + pass);            MimeMessage msg = new MimeMessage(session);            msg.setFrom(new InternetAddress(from));            InternetAddress addressTo = new InternetAddress(to);            msg.setRecipient(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 filePath, String fileName) throws Exception {        BodyPart messageBodyPart = new MimeBodyPart();        DataSource source = new FileDataSource(filePath);        messageBodyPart.setDataHandler(new DataHandler(source));        messageBodyPart.setFileName(fileName);        multipart.addBodyPart(messageBodyPart);    }    @Override    public PasswordAuthentication getPasswordAuthentication() {        return new PasswordAuthentication(user, pass);    }}
然後是發送報告郵件的類了,它繼承自前面所寫的AbstractCrashReportHandler,實現如下:

/* * @(#)CrashEmailReport.java       Project: CrashHandler * Date: 2014-5-27 * * Copyright (c) 2014 CFuture09, Institute of Software,  * Guangdong Ocean University, Zhanjiang, GuangDong, China. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); *  you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *     http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.githang.android.crash;import java.io.File;import android.content.Context;/** * @author Geek_Soledad  */public class CrashEmailReport extends AbstractCrashReportHandler {    private String mReceiveEmail;    public CrashEmailReport(Context context) {        super(context);    }    public void setReceiver(String receiveEmail) {        mReceiveEmail = receiveEmail;    }        @Override    protected void sendReport(String title, String body, File file) {        LogMail sender = new LogMail().setUser("irain_log@163.com").setPass("xxxxxxxx")                .setFrom("irain_log@163.com").setTo(mReceiveEmail).setHost("smtp.163.com")                .setPort("465").setSubject(title).setBody(body);        sender.init();        try {            sender.addAttachment(file.getPath(), file.getName());            sender.send();            file.delete();        } catch (Exception e) {            e.printStackTrace();        }    }}

這樣,一個完整的程式崩潰異常架構就完成了。對於日誌報告,可自己繼承AbstractCrashReportHandler來擴充實現。

使用的時候,需要寫一個繼承自Application的類,在onCreate方法中加上如下代碼,即設定接收郵箱。

  new CrashEmailReport(this).setReceiver("log@msdx.pw");
然後在AndroidManifest.xml中配置這個類。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.