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. [Oracle] CPU/PSU补丁安装详细教程
  2. 选文可以学计算机专业,是不是文理科都可以报计算机专业?
  3. Asp.Net Core对接钉钉群机器人
  4. MAT(Memory Analyzer Tool)工具入门介绍
  5. linux ftp ssl客户端,Linux下ftp+ssl实现ftps-Go语言中文社区
  6. dex、apk完整性校验
  7. 串行异步通信_每天学一点/ 电工:PLC:串行通信
  8. 计算机主板提示ahci,映泰主板设置硬盘模式AHCI或IDE的教程
  9. 2022年计算机二级Access数据库程序设计复习题及答案
  10. t检验的显著性p值python_Python P值
  11. PS之人物高低频磨皮
  12. javascript 经典功能代码和经验教程
  13. 人工智能课程今秋走入高中课堂
  14. SpringBoot统一返回处理出现cannot be cast to java.lang.String异常
  15. SkyWalking服务应用
  16. java白盒测试代码_Java白盒测试
  17. 蓝天学校计算机教学反思,小学美术让我的飞机上蓝天教学反思.docx
  18. 2012年百度实习生招聘-java开发
  19. 【名片制作】深灰色背景·简单名片设计
  20. 如何熟练的运用数学模型在水环境影响评价、防洪评价与排污口论证项目中的方法

热门文章

  1. 1-1Python 初体验—— Hello world
  2. 手把手教你写一份好的技术简历
  3. 信托公司利用境外资金的两大途径
  4. 【Python_Scrapy学习笔记(十三)】基于Scrapy框架的图片管道实现图片抓取
  5. Github快速下载方法
  6. php环境配置步骤,配置PHP服务器环境步骤详解
  7. android 设置全局的页面切换动画问题
  8. android绕过设备锁(device lock)
  9. HTC G21 Sensation XL 官方解锁、刷Recovery刷机图文教程
  10. ThinkPad t440p 左侧滋滋滋滋响