優雅的處理Android崩潰(二)
阿新 • • 發佈:2019-02-19
寫在前面
上一篇部落格中已經介紹了怎麼通過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));
}