现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,这对于下一个版本的bug修复帮助极大,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序。

首先看一下效果图:

一。在MainActivity.java代码中

代码是这样写的:

  1. public class MainActivity extends Activity {
  2. private TextView tv;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. tv.setText("我没初始化");
  8. }
  9. @Override
  10. public boolean onCreateOptionsMenu(Menu menu) {
  11. // Inflate the menu; this adds items to the action bar if it is present.
  12. getMenuInflater().inflate(R.menu.main, menu);
  13. return true;
  14. }
  15. }

遇到软件没有捕获的异常之后,系统会弹出这个默认的强制关闭对话框。

我们当然不希望用户看到这种现象,简直是对用户心灵上的打击,而且对我们的bug的修复也是毫无帮助的。我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。

接下来我们就来实现这一机制,不过首先我们还是来了解以下两个类:android.app.Application和Java.lang.Thread.UncaughtExceptionHandler。

Application:用来管理应用程序的全局状态。在应用程序启动时Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity和Service。本示例中将在自定义加强版的Application中注册未捕获异常处理器。

Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。

大家刚才在项目的结构图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:

二:CrashHandler.java

  1. public class CrashHandler implements UncaughtExceptionHandler {
  2. public static String TAG = "MyCrash";
  3. // 系统默认的UncaughtException处理类
  4. private Thread.UncaughtExceptionHandler mDefaultHandler;
  5. private static CrashHandler instance = new CrashHandler();
  6. private Context mContext;
  7. // 用来存储设备信息和异常信息
  8. private Map<String, String> infos = new HashMap<String, String>();
  9. // 用于格式化日期,作为日志文件名的一部分
  10. private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
  11. /** 保证只有一个CrashHandler实例 */
  12. private CrashHandler() {
  13. }
  14. /** 获取CrashHandler实例 ,单例模式 */
  15. public static CrashHandler getInstance() {
  16. return instance;
  17. }
  18. /**
  19. * 初始化
  20. *
  21. * @param context
  22. */
  23. public void init(Context context) {
  24. mContext = context;
  25. // 获取系统默认的UncaughtException处理器
  26. mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
  27. // 设置该CrashHandler为程序的默认处理器
  28. Thread.setDefaultUncaughtExceptionHandler(this);
  29. autoClear(5);
  30. }
  31. /**
  32. * 当UncaughtException发生时会转入该函数来处理
  33. */
  34. @Override
  35. public void uncaughtException(Thread thread, Throwable ex) {
  36. if (!handleException(ex) && mDefaultHandler != null) {
  37. // 如果用户没有处理则让系统默认的异常处理器来处理
  38. mDefaultHandler.uncaughtException(thread, ex);
  39. } else {
  40. SystemClock.sleep(1000);
  41. // 退出程序
  42. android.os.Process.killProcess(android.os.Process.myPid());
  43. System.exit(1);
  44. }
  45. }
  46. /**
  47. * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
  48. *
  49. * @param ex
  50. * @return true:如果处理了该异常信息; 否则返回false.
  51. */
  52. private boolean handleException(Throwable ex) {
  53. if (ex == null)
  54. return false;
  55. try {
  56. // 使用Toast来显示异常信息
  57. new Thread() {
  58. @Override
  59. public void run() {
  60. Looper.prepare();
  61. Toast.makeText(mContext, "很抱歉,程序出现异常,即将重启.",
  62. Toast.LENGTH_LONG).show();
  63. Looper.loop();
  64. }
  65. }.start();
  66. // 收集设备参数信息
  67. collectDeviceInfo(mContext);
  68. // 保存日志文件
  69. saveCrashInfoFile(ex);
  70. // 重启应用(按需要添加是否重启应用)
  71. //            Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName());
  72. //            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  73. //            mContext.startActivity(intent);
  74. //            SystemClock.sleep(1000);
  75. } catch (Exception e) {
  76. e.printStackTrace();
  77. }
  78. return true;
  79. }
  80. /**
  81. * 收集设备参数信息
  82. *
  83. * @param ctx
  84. */
  85. public void collectDeviceInfo(Context ctx) {
  86. try {
  87. PackageManager pm = ctx.getPackageManager();
  88. PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
  89. PackageManager.GET_ACTIVITIES);
  90. if (pi != null) {
  91. String versionName = pi.versionName + "";
  92. String versionCode = pi.versionCode + "";
  93. infos.put("versionName", versionName);
  94. infos.put("versionCode", versionCode);
  95. }
  96. } catch (NameNotFoundException e) {
  97. Log.e(TAG, "an error occured when collect package info", e);
  98. }
  99. Field[] fields = Build.class.getDeclaredFields();
  100. for (Field field : fields) {
  101. try {
  102. field.setAccessible(true);
  103. infos.put(field.getName(), field.get(null).toString());
  104. } catch (Exception e) {
  105. Log.e(TAG, "an error occured when collect crash info", e);
  106. }
  107. }
  108. }
  109. /**
  110. * 保存错误信息到文件中
  111. * @param ex
  112. * @return 返回文件名称,便于将文件传送到服务器
  113. * @throws Exception
  114. */
  115. private String saveCrashInfoFile(Throwable ex) throws Exception {
  116. StringBuffer sb = new StringBuffer();
  117. try {
  118. SimpleDateFormat sDateFormat = new SimpleDateFormat(
  119. "yyyy-MM-dd HH:mm:ss");
  120. String date = sDateFormat.format(new java.util.Date());
  121. sb.append("\r\n" + date + "\n");
  122. for (Map.Entry<String, String> entry : infos.entrySet()) {
  123. String key = entry.getKey();
  124. String value = entry.getValue();
  125. sb.append(key + "=" + value + "\n");
  126. }
  127. Writer writer = new StringWriter();
  128. PrintWriter printWriter = new PrintWriter(writer);
  129. ex.printStackTrace(printWriter);
  130. Throwable cause = ex.getCause();
  131. while (cause != null) {
  132. cause.printStackTrace(printWriter);
  133. cause = cause.getCause();
  134. }
  135. printWriter.flush();
  136. printWriter.close();
  137. String result = writer.toString();
  138. sb.append(result);
  139. String fileName = writeFile(sb.toString());
  140. return fileName;
  141. } catch (Exception e) {
  142. Log.e(TAG, "an error occured while writing file...", e);
  143. sb.append("an error occured while writing file...\r\n");
  144. writeFile(sb.toString());
  145. }
  146. return null;
  147. }
  148. private String writeFile(String sb) throws Exception {
  149. String time = formatter.format(new Date());
  150. String fileName = "crash-" + time + ".log";
  151. if (FileUtil.hasSdcard()) {
  152. String path = getGlobalpath();
  153. File dir = new File(path);
  154. if (!dir.exists())
  155. dir.mkdirs();
  156. FileOutputStream fos = new FileOutputStream(path + fileName, true);
  157. fos.write(sb.getBytes());
  158. fos.flush();
  159. fos.close();
  160. }
  161. return fileName;
  162. }
  163. public static String getGlobalpath() {
  164. return Environment.getExternalStorageDirectory().getAbsolutePath()
  165. + File.separator + "crash" + File.separator;
  166. }
  167. public static void setTag(String tag) {
  168. TAG = tag;
  169. }
  170. /**
  171. * 文件删除
  172. * @param day 文件保存天数
  173. */
  174. public void autoClear(final int autoClearDay) {
  175. FileUtil.delete(getGlobalpath(), new FilenameFilter() {
  176. @Override
  177. public boolean accept(File file, String filename) {
  178. String s = FileUtil.getFileNameWithoutExtension(filename);
  179. int day = autoClearDay < 0 ? autoClearDay : -1 * autoClearDay;
  180. String date = "crash-" + DateUtil.getOtherDay(day);
  181. return date.compareTo(s) >= 0;
  182. }
  183. });
  184. }
  185. }

在收集异常信息时,朋友们也可以使用Properties,因为Properties有一个很便捷的方法properties.store(OutputStream out, String comments),用来将Properties实例中的键值对外输到输出流中,但是在使用的过程中发现生成的文件中异常信息打印在同一行,看起来极为费劲,所以换成Map来存放这些信息,然后生成文件时稍加了些操作。

完成这个CrashHandler后,我们需要在一个Application环境中让其运行,为此,我们继承android.app.Application,添加自己的代码,CrashApplication.java代码如下:

三.MyAppcation

  1. public class MyAppcation extends Application{
  2. @Override
  3. public void onCreate() {
  4. super.onCreate();
  5. CrashHandler.getInstance().init(this);
  6. }
  7. }

因为我们上面的CrashHandler中,遇到异常后要保存设备参数和具体异常信息到SDCARD,所以我们需要在AndroidManifest.xml中加入读写SDCARD权限:

  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后看一下SDCARD生成的文件:

源码下载地址:http://download.csdn.net/detail/u014608640/9626028

转自:

Android中处理崩溃异常和记录日志

Android中处理崩溃异常和记录日志(转)的更多相关文章

  1. Android中处理崩溃异常和记录日志

    大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了 ...

  2. Android中处理崩溃异常

    转自:http://my.eoe.cn/817027/archive/17997.html 大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不 ...

  3. Android中处理崩溃异常CrashHandler

    来源:http://blog.csdn.net/liuhe688/article/details/6584143 大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程 ...

  4. 【转】Android中处理崩溃异常

    大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了 ...

  5. Android中处理崩溃异常 (转)

    大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了 ...

  6. 【转】Android 中处理崩溃异常并重启程序出现页面重叠的问题

    原文地址:http://blog.csdn.net/jiang547860818/article/details/53641113 android开发中经常会遇到程序异常,而已常常会遇到一出现异常AP ...

  7. 【Android实战】Android中处理崩溃异常

    public class MainActivity extends ActionBarActivity { public CrashApplication application; @Override ...

  8. Android中处理崩溃闪退错误

    Android中处理崩溃闪退异常 大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试, ...

  9. Android application捕获崩溃异常

    Java代码 .收集所有 avtivity 用于彻底退出应用 .捕获崩溃异常,保存错误日志,并重启应用 , intent, , restartIntent); // 关闭当前应用 finishAllA ...

随机推荐

  1. 长沙理工校赛I题题解-连续区间的最大公约数

    题目来源https://www.nowcoder.com/acm/contest/96/I 解题前们需要先知道几个结论: 首先,gcd是有区单调性的: gcd(L,R)>=gcd(L,R+d)  ...

  2. Selenium2+python自动化(学习笔记3)

    1.加载firefox配置 参考代码: # coding=utf-8from selenium import webdriver# 配置文件地址,打开Firefox点右上角设置--帮助--故障排除信息 ...

  3. 腾讯云使用liveRoom开启直播时,报“房间已存在”错误?

    利用腾讯云roomService服务,移动直播,创建房间api,CreateRoom时有时报“房间已存在”错误. 分析流程发现,CreateRoom会传入roomId到roomService后台,后台 ...

  4. Codeforces 629 B. Far Relative’s Problem

      B. Far Relative’s Problem   time limit per test 2 seconds memory limit per test 256 megabytes inpu ...

  5. luogu P2423 双塔

    题目描述 2001年9月11日,一场突发的灾难将纽约世界贸易中心大厦夷为平地,Mr. F曾亲眼目睹了这次灾难.为了纪念“911”事件,Mr. F决定自己用水晶来搭建一座双塔.Mr. F有N块水晶,每块 ...

  6. log4j(一)

    一.基础知识 Log4j有三个重要组件:Logger-日志信息的级别,appenders-日志信息的输出目的地,layouts-日志信息的输出格式. Logger-日志信息的级别:level 是日志记 ...

  7. How To Commit Just One Data Block Changes In Oracle Forms

    You have an Oracle Form in which you have multiple data blocks and requirement is to commit just one ...

  8. Android Studio +MAT 分析内存泄漏实战

    对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的. 首先什么是内存泄漏? 内存泄漏就是一些已经不使用的对 ...

  9. 窗体皮肤实现 - 增加Toolbar的交互性

    稍微改造一下,让交互性更好点.增加提示和动态效果. 控件实现内容: 1.加入Hint提示 2.加入了简易动画效果,鼠标进入和离开会有个渐变效果. 实现方案: 1.基类选用 2.Action的关联 3. ...

  10. 2016.6.21 maven:Failure to transfer ... from ....

    问题描述: 才刚新建的工程,什么都没做,就显示pom.xml有问题,在第一行的标签上 有如下错误: 点击详情: Failure to transfer org.apache.maven:maven-p ...