记录并通过邮件上传App崩溃日志
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崩溃日志相关推荐
- xcode 上传app商店流程记录
目录 发布流程 疑难问题记录 iOS 上线APP时报错 App Store Connect Operation Error 上传app报错(Unable to download a software ...
- app store 注册账号生成证书上传app完整的教程
app store为开发者提供四种类型的申请: 个人ios开发者计划$99/年 公司ios开发者计划$99/年 企业ios开发者计划$299/年 高校ios开发者计划免费 在这里主要介绍一下公司ios ...
- android 内存播放视频播放器,视频流媒体播放器EasyPlayer-RTSP安卓版在RK3399上运行APP崩溃问题...
原标题:视频流媒体播放器EasyPlayer-RTSP安卓版在RK3399上运行APP崩溃问题 我们的流媒体服务器现在都已经支持H.265编码视频的播放,流媒体播放器EasyPlayer就是目前比较稳 ...
- 最新版Google Pay上传App指南
现在2022年,是时候来个最新版的操作指南 创建应用 使用谷歌市场开发者账号登录 开发者平台. 成功登录后,单击 创建应用. 填写应用的 应用名称. 选择应用的 默认语言. 在应用或游戏处,选择 应用 ...
- iOS 开发者账号添加新的管理成员用于上传APP
由于之前申请app账号的同事离职了,每次用之前的账号上传app如果出现错误不能接收到 错误邮件,重新添加一个管理员用以上传app用. 1.申请一个APPID 账号 2.登录开发者账号,在people中 ...
- App Store 上传app后不能构建版本,构建版本发现不了已上传app , 没有➕号 一定要查看App Store账号邮箱
1.首先要看用什么工具上传的 第一次往App Store上传app最好用Application Loader 不要用xcode直接上传因为 xcode直接上传如果app当中有问题不会报错,比如icon ...
- 解决Windows平台通过cURL上传APP到蒲公英pgyer平台时无法使用中文升级描述的问题...
解决Windows平台通过cURL上传APP到蒲公英pgyer平台时无法使用中文升级描述的问题 官方上传命令 curl -F file=@"315.apk" -F uKey=XXX ...
- Mac蒲公英sh脚本上传app
每次编译完app后,都要打开蒲公英,拖拽app,开始躁起来了 也知道蒲公英有对应的客户端(没用过),也有Android Studio插件(Windows上试过) 想起来之前某位大神给我过一个脚本,在M ...
- apple 上传app store
一.前言:二.准备: 一个已付费的开发者账号 一个已经开发完成的项目. 三.检查: 你的Xcode必须是正式版的,beta版本的Xcode是不能上传项目的. 上传使用的 Mac 的 OS X系统必须也 ...
最新文章
- [Oracle] CPU/PSU补丁安装详细教程
- 选文可以学计算机专业,是不是文理科都可以报计算机专业?
- Asp.Net Core对接钉钉群机器人
- MAT(Memory Analyzer Tool)工具入门介绍
- linux ftp ssl客户端,Linux下ftp+ssl实现ftps-Go语言中文社区
- dex、apk完整性校验
- 串行异步通信_每天学一点/ 电工:PLC:串行通信
- 计算机主板提示ahci,映泰主板设置硬盘模式AHCI或IDE的教程
- 2022年计算机二级Access数据库程序设计复习题及答案
- t检验的显著性p值python_Python P值
- PS之人物高低频磨皮
- javascript 经典功能代码和经验教程
- 人工智能课程今秋走入高中课堂
- SpringBoot统一返回处理出现cannot be cast to java.lang.String异常
- SkyWalking服务应用
- java白盒测试代码_Java白盒测试
- 蓝天学校计算机教学反思,小学美术让我的飞机上蓝天教学反思.docx
- 2012年百度实习生招聘-java开发
- 【名片制作】深灰色背景·简单名片设计
- 如何熟练的运用数学模型在水环境影响评价、防洪评价与排污口论证项目中的方法