1. 程式人生 > >優雅的處理Android崩潰(二)

優雅的處理Android崩潰(二)

寫在前面

上一篇部落格中已經介紹了怎麼通過UncaughtExceptionHandler介面實現全域性異常的抓取,以及崩潰日誌的儲存。不太清楚這些操作的可以看我上一篇部落格:優雅的處理Android崩潰(一)
本篇部落格主要解決以下3個問題:
* 1. 實現崩潰專案重啟。
* 2. 實現崩潰日誌讀取。
* 3. 實現日誌上傳對話方塊彈出,並通過email上傳崩潰日誌。

問題分析

初步分析:

初步計劃在專案崩潰時,迴圈讀取異常日誌,然後啟動上傳日誌對話方塊,實現日誌上傳。

發現問題:

在uncaughtException()方法中沒有辦法啟動對話方塊,如果在該方法直接寫startActivity(new Intent(…,…));程式會卡在該程式碼,並報ANR,最終報異常。

解決問題:

在我們設定全域性異常以後,在進入全域性異常時系統就提示儘快收集資訊,程序將被結束,因此不可在此處啟動activity對話方塊。
所以異常發生時,通過SharedPreferences將錯誤日誌的路徑寫入配置檔案中,然後重啟,在啟動的時候先檢測該配置檔案是否有錯誤日誌資訊,如果有則讀取檔案,並彈出對話方塊實現日誌上傳.

程式碼編寫

  • 1.我們實現異常日誌路徑記錄:
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(App.instance);
    Editor editor = preferences.edit
(); editor.putLong(key, value); return editor.commit();
  • 2 . 實現專案重啟:
            // 重啟
            Intent intent = new Intent(mContext.getApplicationContext(), ActivateActivity.class);
            PendingIntent restartIntent = PendingIntent.getActivity(mContext.getApplicationContext(), 0, intent,
                    Intent.FLAG
_ACTIVITY_NEW_TASK); AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); // 設定1毫秒後重啟應 mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1, restartIntent);用 android.os.Process.killProcess(android.os.Process.myPid()); //結束程序 System.exit(1);
  • 3 . 實現自定義對話方塊
public class ExDialog extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ex_dialog);
    }

    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.btn_up: {
            //通過郵件上傳日誌
            new Thread(new Runnable() {

                @Override
                public void run() {
                    EmaliUtil emaliUtil = new EmaliUtil();
                    if (emaliUtil.sendMessage(SharedPF.getSharder().getString(CrashHandler.TAG), ExDialog.this)) {
                        App.instance.toast(true, "傳送成功");
                    }else{
                        App.instance.toast(true, "傳送失敗,請檢測網路環境是否通暢");   
                    }
                    ExDialog.this.finish();
                }
            }).start();
        }
            break;
        case R.id.iv_close: {
            ExDialog.this.finish();
        }
            break;
        default:
            break;
        }
    }

    @Override
    public void finish() {
        //設定異常日誌路徑為空
        SharedPF.getSharder().setString(CrashHandler.TAG, "");
        super.finish();
    }
  • 4 . 修改清單檔案設定為對話方塊主題
        <activity
            android:name=".activity.ExDialog"
            android:theme="@android:style/Theme.DeviceDefault.Dialog.NoActionBar" >
        </activity>
  • 注:前三個類直接複製貼上就好,最後一個類改成你自己的郵箱地址

MailSenderInfo.java

public class MailSenderInfo {
    // 傳送郵件的伺服器的IP和埠
    private String mailServerHost;
    private String mailServerPort = "25";
    // 郵件傳送者的地址
    private String fromAddress;
    // 郵件接收者的地址
    private String toAddress;
    // 登陸郵件傳送伺服器的使用者名稱和密碼
    private String userName;
    private String password;
    // 是否需要身份驗證
    private boolean validate = false;
    // 郵件主題
    private String subject;
    // 郵件的文字內容
    private String content;
    // 郵件附件的檔名
    private String[] attachFileNames;

    /**
     * 獲得郵件會話屬性
     */
    public Properties getProperties() {
        Properties p = new Properties();
        p.put("mail.smtp.host", this.mailServerHost);
        p.put("mail.smtp.port", this.mailServerPort);
        p.put("mail.smtp.auth", validate ? "true" : "false");
        return p;
    }

    public String getMailServerHost() {
        return mailServerHost;
    }

    public void setMailServerHost(String mailServerHost) {
        this.mailServerHost = mailServerHost;
    }

    public String getMailServerPort() {
        return mailServerPort;
    }

    public void setMailServerPort(String mailServerPort) {
        this.mailServerPort = mailServerPort;
    }

    public boolean isValidate() {
        return validate;
    }

    public void setValidate(boolean validate) {
        this.validate = validate;
    }

    public String[] getAttachFileNames() {
        return attachFileNames;
    }

    public void setAttachFileNames(String[] fileNames) {
        this.attachFileNames = fileNames;
    }

    public String getFromAddress() {
        return fromAddress;
    }

    public void setFromAddress(String fromAddress) {
        this.fromAddress = fromAddress;
    }

    public String getPassword() {
        return password;
    }

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

    public String getToAddress() {
        return toAddress;
    }

    public void setToAddress(String toAddress) {
        this.toAddress = toAddress;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String textContent) {
        this.content = textContent;
    }
}

MyAuthenticator.java

public class MyAuthenticator extends Authenticator {
    String userName = null;
    String password = null;

    public MyAuthenticator() {
    }

    public MyAuthenticator(String username, String password) {
        this.userName = username;
        this.password = password;
    }

    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication(userName, password);
    }
}

SimpleMailSender.java

public class SimpleMailSender {
    /**
     * 以文字格式傳送郵件
     * 
     * @param mailInfo
     *            待發送的郵件的資訊
     */
    public boolean sendTextMail(MailSenderInfo mailInfo) {
        // 判斷是否需要身份認證
        MyAuthenticator authenticator = null;
        Properties pro = mailInfo.getProperties();
        if (mailInfo.isValidate()) {
            // 如果需要身份認證,則建立一個密碼驗證器
            authenticator = new MyAuthenticator(mailInfo.getUserName(), mailInfo.getPassword());
        }
        // 根據郵件會話屬性和密碼驗證器構造一個傳送郵件的session
        Session sendMailSession = Session.getDefaultInstance(pro, authenticator);
        try {
            // 根據session建立一個郵件訊息
            Message mailMessage = new MimeMessage(sendMailSession);
            // 建立郵件傳送者地址
            Address from = new InternetAddress(mailInfo.getFromAddress());
            // 設定郵件訊息的傳送者
            mailMessage.setFrom(from);
            // 建立郵件的接收者地址,並設定到郵件訊息中
            Address to = new InternetAddress(mailInfo.getToAddress());
            mailMessage.setRecipient(Message.RecipientType.TO, to);
            // 設定郵件訊息的主題
            mailMessage.setSubject(mailInfo.getSubject());
            // 設定郵件訊息傳送的時間
            mailMessage.setSentDate(new Date());
            // 設定郵件訊息的主要內容
            String mailContent = mailInfo.getContent();
            mailMessage.setText(mailContent);
            // 傳送郵件
            Transport.send(mailMessage);
            return true;
        } catch (MessagingException ex) {
            ex.printStackTrace();
        }
        return false;
    }

    /**
     * 以HTML格式傳送郵件
     * 
     * @param mailInfo
     *            待發送的郵件資訊
     */
    public static boolean sendHtmlMail(MailSenderInfo mailInfo) {
        // 判斷是否需要身份認證
        MyAuthenticator authenticator = null;
        Properties pro = mailInfo.getProperties();
        // 如果需要身份認證,則建立一個密碼驗證器
        if (mailInfo.isValidate()) {
            authenticator = new MyAuthenticator(mailInfo.getUserName(), mailInfo.getPassword());
        }
        // 根據郵件會話屬性和密碼驗證器構造一個傳送郵件的session
        Session sendMailSession = Session.getDefaultInstance(pro, authenticator);
        try {
            // 根據session建立一個郵件訊息
            Message mailMessage = new MimeMessage(sendMailSession);
            // 建立郵件傳送者地址
            Address from = new InternetAddress(mailInfo.getFromAddress());
            // 設定郵件訊息的傳送者
            mailMessage.setFrom(from);
            // 建立郵件的接收者地址,並設定到郵件訊息中
            Address to = new InternetAddress(mailInfo.getToAddress());
            // Message.RecipientType.TO屬性表示接收者的型別為TO
            mailMessage.setRecipient(Message.RecipientType.TO, to);
            // 設定郵件訊息的主題
            mailMessage.setSubject(mailInfo.getSubject());
            // 設定郵件訊息傳送的時間
            mailMessage.setSentDate(new Date());
            // MiniMultipart類是一個容器類,包含MimeBodyPart型別的物件
            Multipart mainPart = new MimeMultipart();
            // 建立一個包含HTML內容的MimeBodyPart
            BodyPart html = new MimeBodyPart();
            // 設定HTML內容
            html.setContent(mailInfo.getContent(), "text/html; charset=utf-8");
            mainPart.addBodyPart(html);
            // 將MiniMultipart物件設定為郵件內容
            mailMessage.setContent(mainPart);
            // 傳送郵件
            Transport.send(mailMessage);
            return true;
        } catch (MessagingException ex) {
            ex.printStackTrace();
        }
        return false;
    }
}

EmaliUtil.java

public class EmaliUtil {
    private Context mcontext;

    public boolean sendMessage(String fileName, Context context) {
        mcontext = context;
        // 這個類主要是設定郵件
        MailSenderInfo mailInfo = new MailSenderInfo();
        mailInfo.setMailServerHost("smtp.163.com");
        mailInfo.setMailServerPort("25");
        mailInfo.setValidate(true);
        mailInfo.setUserName("******@163.com");
        mailInfo.setPassword("*******");// 您的郵箱密碼
        mailInfo.setFromAddress("*******@163.com");
        mailInfo.setToAddress("*******@163.com");
        mailInfo.setSubject("郵件標題");
       //讀取日誌
        String contentTemp = readTxtByStringBuffer(fileName);
        mailInfo.setContent(contentTemp);
        boolean isSuccess = false;
        try {
            // 這個類主要來發送郵件
            SimpleMailSender sms = new SimpleMailSender();
            isSuccess = sms.sendTextMail(mailInfo);// 傳送文體格式
        } catch (Exception e) {
            Log.e("shuxinshuxin", e.toString());
        }
        // sms.sendHtmlMail(mailInfo);//傳送html格式
        return isSuccess;

    }

    // 使用cache進行讀取
    private String readTxtByStringBuffer(String fileName) {
        File file = new File(fileName);
        if (file.exists()) {

            BufferedReader reader = null;

            try {
                reader = new BufferedReader(new FileReader(file), 10 * 1024 * 1024);
                String stringMsg = null;
                StringBuffer buffer = new StringBuffer();
                while ((stringMsg = reader.readLine()) != null) {
                    buffer.append(stringMsg);
                    buffer.append("\n");
                }
                reader.close();
                return buffer.toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "";
        } else {
            return "系統沒有找到日誌檔案";
        }
    }

    private String getVersionName() {
        try {
            // 獲取packagemanager的例項
            PackageManager packageManager = mcontext.getPackageManager();
            // getPackageName()是你當前類的包名,0代表是獲取版本資訊
            PackageInfo packInfo;
            packInfo = packageManager.getPackageInfo(mcontext.getPackageName(), 0);
            String version = packInfo.versionCode + "";
            return version;
        } catch (NameNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "";
    }
}
  • 6 . 程式啟動時,首先判斷上次是否有崩潰日誌,如果有則啟動上傳日誌對話方塊,呼叫EmaliUtil.sendMessage(“”,”“);實現日誌上傳,該方法已經封裝到了自定義的對話方塊中。
        String strTemp = SharedPF.getSharder().getString(CrashHandler.TAG);
        if (!strTemp.equals("")) {
            //如果有異常則啟動異常上傳對話方塊;
            startActivity(new Intent(this, ExDialog.class));
        }