1.引子
最近在做一个社交app的过程中,用户总是反映app在跳转到分享页面的时候App无故退出。
我在我的手机上实验了几下,都能成功,神奇的安卓啊,最后想到了一个办法,
记录用户app的崩溃日志来解决。
可是用户就是用户,提交错误日志,总不能给用户说,你到xx路径下面,把xx文件发给我吧。
所以想到了这种方法:
1.如果app退出,则将app的崩溃日志记录在某个文件下面;
2.当用户再次打开app的时候,提示,用户是否上传错误日志;
3.如果用户选择是,就将错误日志以附件的形式,添加到发送的邮件中;
4.选择否,就直接删除错误日志;
2.知识点讲解
1.如何记录App的崩溃日志

/*** UncaughtException处理类,当程序发生Uncaught异常的时候* * @author user 注意修改文件的路径和文件名,在Manifest中添加文件读写权限;* */
public class CrashHandler implements UncaughtExceptionHandler {// 错误日志文件夹的位置private String mCrashLogDirPath = "";public static final String TAG = CrashHandler.class.getSimpleName();// 系统默认的UncaughtException处理类private Thread.UncaughtExceptionHandler mDefaultHandler;// CrashHandler实例private static CrashHandler INSTANCE = new CrashHandler();private Context mContext;// 用来存储设备信息和异常信息private Map<String, String> infos = new HashMap<String, String>();// 用于格式化日期,作为日志文件名的一部分private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss",Locale.CHINA);private CrashHandler() {}public static CrashHandler getInstance() {return INSTANCE;}public void init(Context context) {mContext = context;mCrashLogDirPath = context.getExternalCacheDir() + File.separator+ MainActivity.Error_DIR_NAME + File.separator;// 获取系统默认的UncaughtException处理器mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 设置该CrashHandler为程序的默认处理器Thread.setDefaultUncaughtExceptionHandler(this);}/*** 当UncaughtException发生时会转入该函数来处理 如果导入项目@Override报错,请修改project编译的jdk版本到1.5以上*/@Overridepublic void uncaughtException(Thread thread, Throwable ex) {if (!handleException(ex) && mDefaultHandler != null) {// 如果用户没有处理则让系统默认的异常处理器来处理mDefaultHandler.uncaughtException(thread, ex);} else {// 退出程序android.os.Process.killProcess(android.os.Process.myPid());System.exit(1);}}/*** 自定义错误处理,收集错误信息* * @param ex* @return true:如果处理了该异常信息;否则返回false.*/private boolean handleException(Throwable ex) {if (ex == null) {return false;}// 收集设备参数信息collectDeviceInfo(mContext);saveCrashInfo2File(ex);// 向配置文件中写入标识位flagCrash();return true;}public void flagCrash() {SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(mContext).edit();editor.putBoolean(MainActivity.TAG_OCCURRED_ERROR, true);editor.commit();}/*** 收集设备参数信息* * @param ctx*/public 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 (NameNotFoundException e) {Log.e(TAG, "an error occured when collect package info", e);}Field[] fields = Build.class.getDeclaredFields();for (Field field : fields) {try {field.setAccessible(true);infos.put(field.getName(), field.get(null).toString());Log.d(TAG, field.getName() + " : " + field.get(null));} catch (Exception e) {Log.e(TAG, "an error occured when collect crash info", e);}}}/*** 保存错误信息到文件中* * @param ex* @return Boolean 判断文件保存到本地是否成功*/private Boolean saveCrashInfo2File(Throwable ex) {Boolean saveFlag = false;StringBuffer sb = new StringBuffer();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.close();String result = writer.toString();// TODOSystem.err.println("*****下面打印错误信息*****");System.err.println(result);sb.append(result);try {long timestamp = System.currentTimeMillis();String time = formatter.format(new Date());String fileName = "crash-" + time + "-" + timestamp + ".log";if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {File dir = new File(mCrashLogDirPath);if (!dir.exists()) {dir.mkdirs();}FileOutputStream fos = new FileOutputStream(mCrashLogDirPath+ fileName);fos.write(sb.toString().getBytes());fos.close();}saveFlag = true;} catch (Exception e) {Log.e(TAG, "an error occured while writing file...", e);}return saveFlag;}

和这片相似的代码,只要稍微搜索一下随处可见。注意相似不是雷同。在这里阐述一下我的小聪明:
1.在这里我使用的路径为this.getCacheDir(),在这个路径下面创建和删除文件是不需要任何权限的;
2.在将app崩溃信息保存到文件之前,我对错误信息进行了控制台打印。因为如果仅仅将错误信息保存到本地文件的话,对于程序员来说,每一次向在控制台打印错误日志,都必须在Application中将手机错误信息的日志功能关闭掉,但是下一次打包的时候,就很有可能把这件事情忘了(亲身经历啊);
3.app崩溃字段的保存。这里使用 PreferenceManager
.getDefaultSharedPreferences(mContext),获取App默认的sharedPreference文件,省略了自定义文件的麻烦,当然还需要自定义一个字段。当用户下次进入app后,对app之前是否发生过崩溃进行判断。
3.大家注意一下错误信息的差别:
1.在没有添加错误日志的时候,打印的错误信息如下:

打印信息为鲜红色,这个大家应该都比较熟悉。
2.看一下添加记录App崩溃日志后的效果:

这种红的颜色就淡了好多,就不是太惹眼了。
3.看一下报错的错误日志在手机上的效果吧:

下面贴上打开App检测是否发生过崩溃,上传崩溃(通过调用手机的邮件系统),清除崩溃字段和崩溃日志的完整代码


/*** * @author guchuanhang * */
public class MainActivity extends Activity implementsandroid.view.View.OnClickListener {public static final int TAG_ERROR_CODE = 10;/*** 错误日志在cachedir下面的crash文件夹下面*/public static final String Error_DIR_NAME = "crash";/*** 通过该字段,进行判断,程序是否发生过crash ,使用getDefaultSharedPreferences*/public static final String TAG_OCCURRED_ERROR = "crashed";private Dialog upErrorDialog;private Button mbtnMakeError = null;private String errorString;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mbtnMakeError = (Button) findViewById(R.id.btn_make_error);mbtnMakeError.setOnClickListener(this);// 判断上次是否发生过崩溃SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);if (preferences.getBoolean(TAG_OCCURRED_ERROR, false)) {uploadErrorDialog();}}public void onClick(View v) {switch (v.getId()) {case R.id.btn_make_error:System.out.println(errorString.equals("xxx"));break;default:break;}}// 第一上传错误日志的对话框样式protected void uploadErrorDialog() {AlertDialog.Builder builder = new Builder(MainActivity.this);builder.setMessage("上次程序异常退出,\n上传错误日志?");builder.setTitle("提示");builder.setPositiveButton("确定", new OnClickListener() {public void onClick(DialogInterface dialog, int which) {setAttachment(MainActivity.this);dialog.dismiss();}});builder.setNegativeButton("取消", new OnClickListener() {public void onClick(DialogInterface dialog, int which) {deleteErrorLogAndRemoveTag();dialog.dismiss();}});upErrorDialog = builder.create();upErrorDialog.show();}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (upErrorDialog != null && upErrorDialog.isShowing()) {upErrorDialog.dismiss();}if (requestCode == TAG_ERROR_CODE) {deleteErrorLogAndRemoveTag();} else {super.onActivityResult(requestCode, resultCode, data);}}public void deleteErrorLogAndRemoveTag(){File file = new File(getExternalCacheDir(), Error_DIR_NAME);File[] errorFiles = file.listFiles();for (int i = 0; i < errorFiles.length; i++) {errorFiles[i].delete();}SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();editor.remove(TAG_OCCURRED_ERROR);editor.commit();}public void setAttachment(Context conext) {Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);String[] tos = { "guchuanhang@qq.com" };String[] ccs = { "1162834643@qq.com" };intent.putExtra(Intent.EXTRA_EMAIL, tos);intent.putExtra(Intent.EXTRA_CC, ccs);intent.putExtra(Intent.EXTRA_TEXT, "我很生气,你要尽快解决。\n 否则后果不堪设想!");intent.putExtra(Intent.EXTRA_SUBJECT, "叮咚FM崩溃日志");ArrayList<Uri> imageUris = new ArrayList<Uri>();File srcDir = new File(this.getExternalCacheDir(), "crash");File[] logFiles = srcDir.listFiles();for (int i = 0; i < logFiles.length; i++) {imageUris.add(Uri.parse("file://" + logFiles[i].getAbsolutePath()));}intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);intent.setType("image/*");intent.setType("message/rfc882");Intent.createChooser(intent, "Choose Email Client");((Activity) conext).startActivityForResult(intent, TAG_ERROR_CODE);}}

下面贴上Application中打开错误日志的方法:


import pan.gch.demo.util.CrashHandler;
import android.app.Application;
/*** @author guchuanhang**/
public class MyApplication extends Application {  @Override  public void onCreate() {  super.onCreate();  CrashHandler crashHandler = CrashHandler.getInstance();  crashHandler.init(getApplicationContext());  }
}  

3.附上Demo
我啰嗦了折磨多,相信聪明的你,一定一看就会。Demo下载地址:
http://download.csdn.net/detail/guchuanhang/9148729

记录并通过邮件上传App崩溃日志相关推荐

  1. xcode 上传app商店流程记录

    目录 发布流程 疑难问题记录 iOS 上线APP时报错 App Store Connect Operation Error 上传app报错(Unable to download a software ...

  2. app store 注册账号生成证书上传app完整的教程

    app store为开发者提供四种类型的申请: 个人ios开发者计划$99/年 公司ios开发者计划$99/年 企业ios开发者计划$299/年 高校ios开发者计划免费 在这里主要介绍一下公司ios ...

  3. android 内存播放视频播放器,视频流媒体播放器EasyPlayer-RTSP安卓版在RK3399上运行APP崩溃问题...

    原标题:视频流媒体播放器EasyPlayer-RTSP安卓版在RK3399上运行APP崩溃问题 我们的流媒体服务器现在都已经支持H.265编码视频的播放,流媒体播放器EasyPlayer就是目前比较稳 ...

  4. 最新版Google Pay上传App指南

    现在2022年,是时候来个最新版的操作指南 创建应用 使用谷歌市场开发者账号登录 开发者平台. 成功登录后,单击 创建应用. 填写应用的 应用名称. 选择应用的 默认语言. 在应用或游戏处,选择 应用 ...

  5. iOS 开发者账号添加新的管理成员用于上传APP

    由于之前申请app账号的同事离职了,每次用之前的账号上传app如果出现错误不能接收到 错误邮件,重新添加一个管理员用以上传app用. 1.申请一个APPID 账号 2.登录开发者账号,在people中 ...

  6. App Store 上传app后不能构建版本,构建版本发现不了已上传app , 没有➕号 一定要查看App Store账号邮箱

    1.首先要看用什么工具上传的 第一次往App Store上传app最好用Application Loader 不要用xcode直接上传因为 xcode直接上传如果app当中有问题不会报错,比如icon ...

  7. 解决Windows平台通过cURL上传APP到蒲公英pgyer平台时无法使用中文升级描述的问题...

    解决Windows平台通过cURL上传APP到蒲公英pgyer平台时无法使用中文升级描述的问题 官方上传命令 curl -F file=@"315.apk" -F uKey=XXX ...

  8. Mac蒲公英sh脚本上传app

    每次编译完app后,都要打开蒲公英,拖拽app,开始躁起来了 也知道蒲公英有对应的客户端(没用过),也有Android Studio插件(Windows上试过) 想起来之前某位大神给我过一个脚本,在M ...

  9. apple 上传app store

    一.前言:二.准备: 一个已付费的开发者账号 一个已经开发完成的项目. 三.检查: 你的Xcode必须是正式版的,beta版本的Xcode是不能上传项目的. 上传使用的 Mac 的 OS X系统必须也 ...

最新文章

  1. LeetCode 1195. Fizz Buzz Multithreaded--并发系列题目--Java 解法--AtomicInteger/CountDownLatch/CyclicBarrier
  2. 如何设置iframe高度自适应,在跨域的情况下能做到吗?
  3. linux上修改ssh密码和mysql密码
  4. MySQL之单表查询、多表查询(一)
  5. java线程6种状态转换,java6种线程状态
  6. eclipse+android source code
  7. 硬币问题——固定终点的最长路和最短路
  8. JeecgBoot轻松解决ERP项目复杂布局需求,JVXETable高性能行表格效果和项目案例
  9. SQL 2017——新功能
  10. python自定义变量名有哪些_Python学习第161课——自定义变量和导出变量
  11. 如何通过ildasm/ilasm修改assembly的IL代码
  12. 地质图、地质岩性数据、地质灾害分布、土壤理化性质数据集、土地利用数据、土壤重金属含量分布、植被类型分布
  13. 【JavaScript 插件】实现图片倒影效果 - reflex.js
  14. excel相同字段多行合并_EXCEL里如何快速把多行数据合并为一行并以逗号隔开?...
  15. 【刘润五分钟商学院】-163生存,还是灭亡,没有中间态
  16. DDR SDRAM内存优化
  17. 13、Jupyter notebook制作幻灯片PPT
  18. 解密微信电脑版image文件夹下缓存的用户图片
  19. 小区出入登记记录系统java_一种小区出入管理系统的制作方法
  20. nfc ntag21x ultralight 内存结构

热门文章

  1. 2017双十一阿里技术汇总
  2. Virtual Serial Port Driver Pro 10.0.992 下载安装使用说明
  3. 如何拿下阿里巴巴 P8 的前端 Offer
  4. 前端面试必会html标签+属性
  5. PostgreSQL基本数据类型
  6. uniapp关于iphone底部安全区域
  7. HTML关闭页面提示
  8. 创建隐身浏览器-Creating Incognito Browser
  9. Elasticsearch入门、Kibana 索引管理(elasticserch-head 插件使用, Kibanan 安装和使用)
  10. Mac终端下出现bogon的解决方案