记录并通过邮件上传App崩溃日志
来源:互联网 发布:c语言for的用法 编辑:程序博客网 时间:2024/05/17 20:00
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以上 */ @Override public 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(); // TODO System.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 implements android.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; @Override public 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(); } @Override protected 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崩溃日志
- App发生崩溃保存崩溃日志在本地,并发送邮件给开发人员
- android app崩溃日志收集以及上传
- android--app崩溃日志收集以及上传
- android app崩溃日志收集以及上传
- Day9 APP中抓取崩溃日志与邮件通知
- iOS通过邮件获取APP异常崩溃信息
- Android APP崩溃上传日志到服务器并且重启!
- Android APP崩溃上传日志到服务器并且重启!
- Android APP崩溃上传日志到服务器并且重启
- Android APP崩溃上传日志到服务器并且重启!
- Android App崩溃上传日志到服务器并且重启!
- Android开发之app崩溃日志收集以及上传
- Android APP崩溃上传日志到服务器并且重启!
- 如何通过友盟分析发布后App崩溃日志
- 如何通过友盟分析发布后App崩溃日志
- 如何通过友盟分析发布后App崩溃日志
- 如何通过友盟分析发布后App崩溃日志
- 静态路由
- leetcode 231 Power of Two(难易度:Easy)
- 家用轿车轮胎多久更换一次?
- java应用fullgc时如何排查问题
- Xcode:添加自定义代码块
- 记录并通过邮件上传App崩溃日志
- 初识MVVM
- iOS开发UIScrollView使用详解
- Linux(Ubuntu)下如何安装JDK
- 将一个十进制数字转化为x进制/将x进制的字符串转换成10进制
- R字首类破解补丁速查
- IOS UILabe及UIFont用法总结
- Mysql的安装
- 很果断地选择了黑马