转载请注明出处:https://juejin.im/post/5de4bcf7f265da06041eeffd
写在前面
上一篇博客中已经介绍了怎么通过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>
- 5 . 集成邮件发送包并编写发送代码。
邮件发送的jar下载链接:链接:http://pan.baidu.com/s/1i5DZR8p 密码:7tvt - 注:前三个类直接复制粘贴就好,最后一个类改成你自己的邮箱地址
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));
}