您的位置:首页 > 移动开发

App发生崩溃保存崩溃日志在本地,并发送邮件给开发人员

2017-11-30 00:41 585 查看
App在客户手中时不时会出现闪退,崩溃等现象。但蛋疼的时有时候无法重现崩溃原因处理。于是,崩溃保存日志出来了,但保存在用户本地也看不到啊,于是,发邮件又来了。效果如图



再说个蛋疼的问题,我在公司Android stadio 2.3.3版本UncaughtExceptionHandler不会跳到这个奔溃的提示页面,但我在家里的Android stadio 3.0.1版本有没问题。默默的问问,2.3.3-3.0.1更新了什么。

好了,不哔哔,上代码。

其中主要的是实现UncaughtExceptionHandler这个接口,这个接口有什么用呢?简单点来说,会针对,某段代码做try … catch 没有catch到的代码,发生异常的时候,就会由setDefaultUncaughtExceptionHandler来处理

/**
* Created by supper on 2017/11/22.
* UncaughtExceptionHandler做全局的catch
* 通常来讲,会针对,某段代码做try … catch 没有catch到的代码,发生异常的时候,就会由setDefaultUncaughtExceptionHandler来处理。
* 当程序崩溃时,由这些代码接管
* 将报错文件报错到本地,超出日志的大小删除掉旧日志
*/

public class CrashHandler implements Thread.UncaughtExceptionHandler {
public static final String TAG = "APP>>CrashHandler";

//系统默认的UncaughtException处理类
private static Thread.UncaughtExceptionHandler mDefaultUncaughtException;
//CarshHandler的单例实例
private static CrashHandler instance;
//程序的Context对象
private Context mContext;
//用来存储设备信息和异常信息
private Map<String, String> infos = new HashMap<String, String>();

private String errorLog = "";
private String errorData = "";

private String logName = "error";

private long FileSize = 1024 * 1024 * 10;//1个文件只保存10M的日志,超出重新创建日志文件

Handler handler = new Handler(Looper.getMainLooper());

/** 获取CrashHandler实例 ,单例模式 */
public static CrashHandler getInstance() {
if(instance == null)
instance = new CrashHandler();
return instance;
}

//初始化
public void init(Context context){
Log.d(TAG, "init: 初始化");
mContext = context;
//获取系统默认的UncaughtException处理器
mDefaultUncaughtException = Thread.getDefaultUncaughtExceptionHandler();
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
cleanLog(3);
}

@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultUncaughtException != null) {
//以对代码做try … catch处理的代码,继续让系统默认的异常处理器来处理
mDefaultUncaughtException.uncaughtException(thread, ex);
} else {
//uncaughtException无法调出弹窗,只能跳到Activity,询问用户是否发送邮件
Intent intent = new Intent(mContext, CrashDialog.class);
intent.putExtra("errorData",errorData);
intent.putExtra("errorLog",errorLog);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
System.exit(0);
android.os.Process.killProcess(android.os.Process.myPid());

//            //App重启处理
//            Intent intent = new Intent(mContext, MainActivity.class);
//            PendingIntent restartIntent = PendingIntent.getActivity(mContext, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
//            //退出程序
//            AlarmManager mgr = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
//            mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,
//                    restartIntent); // 1秒钟后重启应用
//            System.exit(0);
//            android.os.Process.killProcess(android.os.Process.myPid());
}
}

private boolean handleException(Throwable ex){
if(ex == null){
return  false;
}
//收集设备参数信息
collectDeviceInfo(mContext);
try{
if(FileUtil.hasSdcard()){//SD卡可用
saveCrashInfoFile(ex);
}
}catch (Exception e){
Log.e(TAG,e.getMessage());
}
return true;
}

private void collectDeviceInfo(Context ctx){
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),PackageManager.GET_ACTIVITIES);
if(pi != null){
//获取版本
String versionName = pi.versionName == null ? "null" : pi.versionName;
String versionCode = pi.versionCode + "";
infos.put("versionName", versionName);
infos.put("versionCode", versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "获取版本信息不成功》》" + e);
}
//获取手机的配置信息
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);//成员变量为private,故必须进行此操
infos.put(field.getName(), field.get(null).toString());
Log.d(TAG, field.getName() + " : " + field.get(null));
} catch (Exception e) {
Log.e(TAG, "获取配置信息不成功》》", e);
}
}
}

/**
* 保存错误信息到文件中
* @param ex
* @return 返回文件名称,便于将文件传送到服务器
* @throws Exception
*/
private String saveCrashInfoFile(Throwable ex) throws Exception {
Log.d(TAG, "saveCrashInfoFile:错误信息" + ex.getMessage());
errorLog = "";
errorData = "";
Log.d(TAG, "saveCrashInfoFile: gg了,保存报错日志到本地");
StringBuffer sb = new StringBuffer();
try {
sb.append("\n\n\n-------------------------------------------我是开始的分割线---------------------------------------------------\n");
SimpleDateFormat sDateFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
errorData = sDateFormat.format(new java.util.Date());
sb.append(errorData + "\n");
//Map.Entry<String, String> 将map里的每一个键值对取出来封装成一个Entry对象在存到一个Set里面
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}

Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.flush();
printWriter.close();
String result = writer.toString();
sb.append(result);
sb.append("-------------------------------------------我是结束的分割线---------------------------------------------------\n\n\n\n");
final String fileName = getFileName();
FileUtil.writeFile(fileName,sb.toString(),true);
errorLog = sb.toString();
Log.d(TAG, "saveCrashInfoFile:日志地址" + fileName);
//            ToastUtil.getInstance().makeText(mContext,"日志地址" + fileName);
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing file...", e);
sb.append("an error occured while writing file...\r\n");
}
return null;
}

/**
* 获取要打印日志的文本
* 未超出指定的文本大小在原日志末尾继续添加,否则重新建个日志文件
* @return
*/
private String getFileName(){
List<String> fileList = FileUtil.getFileNameList(getGlobalpath(),"log");
int num = 0;
String path = getGlobalpath();
String fileName = "";
if(fileList != null && fileList.size() > 0){
for(String str : fileList){
String numStr = str.substring(logName.length() , logName.length() + 1);
try {
int m = Integer.valueOf(numStr);
if(m > num){
num = m;
}
}catch (Exception e){}
}

long size = FileUtil.getFileSize(path + logName + num + ".log");
if(size < FileSize){//判断日志大小是否超过
fileName = path + logName + num + ".log";
return fileName;
}else{
fileName = path + logName + (num + 1) + ".log";
FileUtil.createFile(fileName);
}
}else{
fileName =getGlobalpath() + logName + (num + 1) + ".log";
}
return fileName;
}

/**
* 最多可存放多少日志文件数,超出日志数删除
* @param logNum
*/
private void cleanLog(int logNum){
Log.d(TAG, "cleanLog: 清除日志文件 logNum:" + logNum);
String path = getGlobalpath();
List<String> fileList = FileUtil.getFileNameList(path,"log");
if(fileList == null && fileList.size() < logNum){
return;
}
Log.d(TAG, "cleanLog: 当前日志文件数" + fileList.size());
List<Integer> intlist = new ArrayList<Integer>();
for(String str : fileList){
String numStr = str.substring(logName.length() , logName.length() + 1);
try {
Integer m = Integer.valueOf(numStr);
if(m != null){
intlist.add(m);
}
}catch (Exception e){}
}
if(intlist != null && intlist.size() > logNum){
Collections.sort(intlist);//重新排序
for(int i = 0; i < intlist.size() - logNum;i ++){
FileUtil.delete(path + logName + intlist.get(i) + ".log",null);
Log.d(TAG, "cleanLog: 清除日志文件" + logName + intlist.get(i) + ".log");
}
}
}

/**
* 获取存放日志的文件夹
* @return
*/
public static String getGlobalpath() {
return Environment.getExternalStorageDirectory().getPath()
+ File.separator + "Demo"
+ File.separator + "Log" + File.separator;
}
}


这里面为什么跳转页面询问用户呢,本来我是打算直接发送邮件的,但有个问题,发邮件是个耗时的操作,万一网络不好,发邮件NNNNN久,那岂不是邮件还没发出去,APP已经退出了。而且蛋疼的是在uncaughtException起不来等待框,发邮件的时间久了,岂不是奔溃的APP一直在黑屏页面嘛。

文件读写,忘了是copy网上哪位大牛的了,在次万分抱歉,这里方法较多,就不全部copy出来,就留我用到的吧

/**
* File工具类
* 主要封装了一些对文件读写的操作
*
*/
public final class FileUtil {

private FileUtil() {
throw new Error(" ̄﹏ ̄");
}

/** 分隔符. */
public final static String FILE_EXTENSION_SEPARATOR = ".";

/**"/"*/
public final static String SEP = File.separator;

/** SD卡根目录 */
public static final String SDPATH = Environment
.getExternalStorageDirectory() + File.separator;
/**
* 向文件中写入数据
* @param filePath 文件目录
* @param content 要写入的内容
* @param append 如果为 true,则将数据写入文件末尾处,而不是写入文件开始处
* @return 写入成功返回true, 写入失败返回false
* @throws IOException
*/
public static boolean writeFile(String filePath, String content,
boolean append) throws IOException {
if (TextUtils.isEmpty(filePath))
return false;
if (TextUtils.isEmpty(content))
return false;
FileWriter fileWriter = null;
try {
createFile(filePath);
fileWriter = new FileWriter(filePath, append);
fileWriter.write(content);
fileWriter.flush();
return true;
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* 获取某个目录下的指定扩展名的文件名称
* @param dirPath 目录
* @return 某个目录下的所有文件名
*/
public static List<String> getFileNameList(String dirPath,
final String extension) {
if (TextUtils.isEmpty(dirPath))
return Collections.emptyList();
File dir = new File(dirPath);
File[] files = dir.listFiles(new FilenameFilter() {

@Override
public boolean accept(File dir, String filename) {
if (filename.indexOf("." + extension) > 0)
return true;
return false;
}
});
if (files == null)
return Collections.emptyList();
List<String> conList = new ArrayList<String>();
for (File file : files) {
if (file.isFile())
conList.add(file.getName());
}
return conList;
}

/**
* 删除指定目录中特定的文件
* @param dir
* @param filter
*/
public static void delete(String dir, FilenameFilter filter) {
if (TextUtils.isEmpty(dir))
return;
File file = new File(dir);
if (!file.exists())
return;
if (file.isFile())
file.delete();
if (!file.isDirectory())
return;

File[] lists = null;
if (filter != null)
lists = file.listFiles(filter);
else
lists = file.listFiles();

if (lists == null)
return;
for (File f : lists) {
if (f.isFile()) {
f.delete();
}
}
}

/**
* 获得文件或文件夹的大小
* @param path 文件或目录的绝对路径
* @return 返回当前目录的大小 ,注:当文件不存在,为空,或者为空白字符串,返回 -1
*/
public static long getFileSize(String path) {
if (TextUtils.isEmpty(path)) {
return -1;
}
File file = new File(path);
return (file.exists() && file.isFile() ? file.length() : -1);
}
}


程序入口,初始化全局变量

/**
* Created by supper on 2017/11/22.
*/

public class BaseApplication extends Application {
private static BaseApplication instance;
public BaseApplication getInstance(){
return instance;
}

private Context mContext;

@Override
public void onCreate() {
super.onCreate();
instance = this;
mContext = this;
CrashHandler.getInstance().init(mContext);//初始化carsh处理类
}
}


这是发送邮件的页面

/**
* Created by supper on 2017/11/29.
*/

public class CrashDialog extends Activity implements View.OnClickListener{
private final String TAG = "APP>>CrashDialog";
private Button btnYes, btnNo;

private String errorData = "",errorLog = "";

private Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash);
context = this;
initView();
setFinishOnTouchOutside(false);
Intent intent = getIntent();
errorData = intent.getStringExtra("errorData");
errorLog = intent.getStringExtra("errorLog");
}

private void initView() {
btnYes = (Button) findViewById(R.id.btn_yes);
btnNo = (Button) findViewById(R.id.btn_no);

btnYes.setOnClickListener(this);
btnNo.setOnClickListener(this);
}

@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_yes:
sendMessage();
break;
case R.id.btn_no:
exit();
break;
}
}

private void exit() {
System.exit(0);
android.os.Process.killProcess(android.os.Process.myPid());
}

private void sendMessage() {

/*****************************************************/
Log.d(TAG, "sendMessage: 开始发送邮件");
// 这个类主要是设置邮件
new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
MailSendVo mailInfo = new MailSendVo();
mailInfo.setMailServerHost(Config.ERROR_MAIL_SERVERHOST);
mailInfo.setMailServerPost(Config.ERROR_MAIL_SERVERPORT);
mailInfo.setValidate(true);
mailInfo.setUserName(Config.ERROR_MAIL_USERNAME);
mailInfo.setPassWord(Config.ERROR_MAIL_PASSWORD);// 您的邮箱密码
mailInfo.setFromAddress(Config.ERROR_MAIL_USERNAME);
mailInfo.setToAddress(Config.ERROR_MAIL_USERNAME);
mailInfo.setSubject(errorData + "错误日志");
mailInfo.setContent(errorLog);
// 这个类主要来发送邮件
EmailUtil sms = new EmailUtil();
boolean isSuccess = sms.sendTextMail(mailInfo);// 发送文体格式
// sms.sendHtmlMail(mailInfo);//发送html格式
DialogThridUtils.getInstance().closeDialog();
if (isSuccess) {
Log.d(TAG, "run: 发送成功");
} else {
Log.d(TAG, "run: 发送失败");
}
exit();
}
}).start();
DialogThridUtils.getInstance().showWaitDialog(context,"正在发送邮件",false);
}

@Override
public void onBackPressed() {
super.onBackPressed();
exit();
}
}


我这边发送邮件用的是java的mail.jar,activation.jar,additionnal.jar这三个类,用这三个的原因主要是因为切到java web开发也可以用。其实我不是特别明白additionnal这个jar是干嘛的,但缺了这个jar会报错,没细研究。有懂的小伙伴顺道告知下。

顺道附上我的jar包地址:

http://download.csdn.net/download/feiniyan4944/10138558

发送邮件的基础类,这里要说一下的是,QQ邮箱和腾讯企业邮必须加上这段

MailSSLSocketFactory sf = null;

try {

sf = new MailSSLSocketFactory();

sf.setTrustAllHosts(true);

} catch (GeneralSecurityException e1) {

e1.printStackTrace();

}

//开启安全协议

p.put(“mail.smtp.ssl.enable”, “true”);

p.put(“mail.smtp.ssl.socketFactory”, sf);

刚开始测试的时候,比较蛋疼的是我用163邮箱没事,转到QQ邮箱死命发不出去了,百度了下才知识QQ邮箱是使用SSL的

public class MailSendVo {

/**
* 发送邮件的服务器的IP和端口
*/
private String mailServerHost;
private String mailServerPort;
/**
* 邮件发送者的地址
*/
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;

/**
* 获取邮件会话属性
* @return
*/
public Properties getProperties(){
Properties p = new Properties();
p.put("mail.smtp.host", this.mailServerHost);
p.put("mail.smtp.port", this.mailServerPort);
p.put("mail.transport.protocol", "smtp");
p.put("mail.smtp.auth", validate ? "true" : "false");
//使用SSL,企业邮箱必需!
MailSSLSocketFactory sf = null;
try {
sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
} catch (GeneralSecurityException e1) {
e1.printStackTrace();
}
//开启安全协议
p.put("mail.smtp.ssl.enable", "true");
p.put("mail.smtp.ssl.socketFactory", sf);
return p;
}

//一堆get set就不占位置了
}


邮件工具类没多写,其实还有附件,抄送,多图片等等可以扩展,但懒,就写了个最简单的发送邮件,百度有很多邮件的Demo

/**
* 发送邮件工具类
* Created by supper on 2017/11/23.
*/

public class EmailUtil {

/**
* 以文本格式发送邮件
* @param mailInfo 待发送的邮件的信息
*/
public boolean sendTextMail(MailSendVo 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) {
Log.e("",ex.getMessage());
}
return false;
}
}


主页面

public class MainActivity extends Activity implements View.OnClickListener{
public static final String TAG = "APP>>MainActivity";
private Button btn_error_test;
private Button btn_log;
private Button btn_dialog;

private Integer i;
private Context context;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;

initData();
}

private void initData(){
btn_error_test = (Button)findViewById(R.id.btn_error_test);
btn_log = (Button)findViewById(R.id.btn_log);
btn_dialog = (Button)findViewById(R.id.btn_dialog);

btn_error_test.setOnClickListener(this);
btn_log.setOnClickListener(this);
btn_dialog.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_error_test:
if(true){
throw new NullPointerException();
}
break;
case R.id.btn_log:
Log.d(TAG, "onClick: 测试" );
try {
sendMail();//
}catch (Exception e){
e.getMessage();
}
break;
case R.id.btn_dialog:
handler.removeCallbacks(gotoDialog);
handler.postDelayed(gotoDialog,500);
break;
}
}

Runnable gotoDialog = new Runnable() {
@Override
public void run() {

// 跳转到崩溃提示Activity
Intent intent = new Intent(context, CrashDialog.class);
intent.putExtra("errorData",new Date().getTime());
intent.putExtra("errorLog","测试");
//            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
};

private void sendMail(){
new AlertDialog.Builder(context)
.setTitle("提示")
.setMessage("APP不幸发生崩溃,是否要发送错误报告给开发人员?")
.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendMessage();
dialog.dismiss();
}
})
.setNegativeButton("否", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
}

private void sendMessage() {
/*****************************************************/
Log.d(TAG, "sendMessage: 开始发送邮件");
//为什么handler.post()发送邮件会崩溃
// 这个类主要是设置邮件
new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
MailSendVo mailInfo = new MailSendVo();
mailInfo.setMailServerHost(Config.ERROR_MAIL_SERVERHOST);
mailInfo.setMailServerPost(Config.ERROR_MAIL_SERVERPORT);
mailInfo.setValidate(true);
mailInfo.setUserName(Config.ERROR_MAIL_USERNAME);
mailInfo.setPassWord(Config.ERROR_MAIL_PASSWORD);// 您的邮箱密码
mailInfo.setFromAddress(Config.ERROR_MAIL_USERNAME);
mailInfo.setToAddress(Config.ERROR_MAIL_USERNAME);
mailInfo.setSubject("错误日志");
mailInfo.setContent("测试");
// 这个类主要来发送邮件
EmailUtil sms = new EmailUtil();
boolean isSuccess = sms.sendTextMail(mailInfo);// 发送文体格式
// sms.sendHtmlMail(mailInfo);//发送html格式
DialogThridUtils.getInstance().closeDialog();
if (isSuccess) {
Log.d(TAG, "run: 发送成功");
} else {
Log.d(TAG, "run: 发送失败");
}
}
}).start();
DialogThridUtils.getInstance().showWaitDialog(context,"正在发送邮件",false);
}

//
//    private void sendMessage(final String msg) {
//
//        /*****************************************************/
//        Log.d(TAG, "sendMessage: 开始发送邮件");
//        // 这个类主要是设置邮件
//        new Thread(new Runnable() {
//
//            @Override
//            public void run() {
//                // TODO Auto-generated method stub
//                MailSendVo mailInfo = new MailSendVo();
//                mailInfo.setMailServerHost("smtp.163.com");
//                mailInfo.setMailServerPost("25");
//                mailInfo.setValidate(true);
//                mailInfo.setUserName("***");
//                mailInfo.setPassWord("***");// 您的邮箱密码
//                mailInfo.setFromAddress("***");
//                mailInfo.setToAddress("***");
//                mailInfo.setSubject("这是标题");
//                mailInfo.setContent(msg);
//                // 这个类主要来发送邮件
//                EmailUtil sms = new EmailUtil();
//                boolean isSuccess = sms.sendTextMail(mailInfo);// 发送文体格式
//                // sms.sendHtmlMail(mailInfo);//发送html格式
//                DialogThridUtils.getInstance().closeDialog();
//                if (isSuccess) {
//                    Log.d(TAG,"发送成功");
////                    ToastUtil.getInstance().makeText(context,"发送成功");
//                } else {
//                    Log.d(TAG,"发送失败");
////                    ToastUtil.getInstance().makeText(context,"发送失败");
//                }
//            }
//        }).start();
//        DialogThridUtils.getInstance().showWaitDialog(context,"正在发送邮件",false);
//    }

}


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="100"
android:versionName="1.0.0"
package="com.supper.main">

<application
android:name=".BaseApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- 导航 unspecified -->
<activity
android:name=".ui.CrashDialog"
android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/alert_dialog" />
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

</manifest>


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>

<Button
android:id="@+id/btn_error_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="测试闪退日志保存"/>

<Button
android:id="@+id/btn_log"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="邮件测试"/>

<Button
android:id="@+id/btn_dialog"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="弹窗"/>
</LinearLayout>


activity_crash.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical">

<LinearLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#F6F6F6"
android:orientation="vertical">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:text="APP不幸发生崩溃,是否要发送错误报告给开发人员?"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_yes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="10dp"
android:text="是"/>
<Button
android:id="@+id/btn_no"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:text="否"/>
</LinearLayout>
</LinearLayout>

</LinearLayout>


这里面的代码还有个发送邮件的等待框,和因为Toast只能在子线程显示但很多地方很烦总报线程错,而写的工具类,但目前总是报错导致APP奔溃,目前问题还不是很清楚是什么原因,还有比较蛋疼的是我在真机跑的时候logcat不知道为什么没用日志打印,而在虚拟机没有这样的问题。

最后附上我的源码地址:

http://download.csdn.net/download/feiniyan4944/10138555

咸鱼开发,有bug处望大佬纠正
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  异常 邮件 android app
相关文章推荐