转自:http://my.eoe.cn/817027/archive/17997.html

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

我们先建立一个crash项目,项目结构如图:

在MainActivity.java代码中,代码是这样写的:

  1. package com.scott.crash;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. public class MainActivity extends Activity {
  5. private String s;
  6. @Override
  7. public void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. System.out.println(s.equals("any string"));
  10. }
  11. }
package com.scott.crash;  

import android.app.Activity;
import android.os.Bundle; public class MainActivity extends Activity { private String s; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println(s.equals("any string"));
}
}

我们在这里故意制造了一个潜在的运行期异常,当我们运行程序时就会出现以下界面:

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

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

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

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

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

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

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

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast; /**
* UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
*
* @author user
*
*/
public class CrashHandler implements UncaughtExceptionHandler { public static final String TAG = "CrashHandler"; //系统默认的UncaughtException处理类
private Thread.UncaughtExceptionHandler mDefaultHandler;
//CrashHandler实例
private static CrashHandler INSTANCE = new CrashHandler();
//程序的Context对象
private Context mContext;
//用来存储设备信息和异常信息
private Map<String, String> infos = new HashMap<String, String>(); //用于格式化日期,作为日志文件名的一部分
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); /** 保证只有一个CrashHandler实例 */
private CrashHandler() {
} /** 获取CrashHandler实例 ,单例模式 */
public static CrashHandler getInstance() {
return INSTANCE;
} /**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context;
//获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
} /**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(TAG, "error : ", e);
}
//退出程序
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;
}
//使用Toast来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
//收集设备参数信息
collectDeviceInfo(mContext);
//保存日志文件
saveCrashInfo2File(ex);
return true;
} /**
* 收集设备参数信息
* @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 返回文件名称,便于将文件传送到服务器
*/
private String saveCrashInfo2File(Throwable ex) { 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();
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)) {
String path = "/sdcard/crash/";
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path + fileName);
fos.write(sb.toString().getBytes());
fos.close();
}
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing file...", e);
}
return null;
}
}

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

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

  1. package com.scott.crash;
  2. import android.app.Application;
  3. public class CrashApplication extends Application {
  4. @Override
  5. public void onCreate() {
  6. super.onCreate();
  7. CrashHandler crashHandler = CrashHandler.getInstance();
  8. crashHandler.init(getApplicationContext());
  9. }
  10. }
package com.scott.crash;  

import android.app.Application;  

public class CrashApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
}
}

最后,为了让我们的CrashApplication取代android.app.Application的地位,在我们的代码中生效,我们需要修改AndroidManifest.xml:

  1. <application android:name=".CrashApplication" ...>
  2. </application>
<application android:name=".CrashApplication" ...>
</application>

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

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

搞定了上边的步骤之后,我们来运行一下这个项目:

看以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息。

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

用文本编辑器打开日志文件,看一段日志信息:

  1. CPU_ABI=armeabi
  2. CPU_ABI2=unknown
  3. ID=FRF91
  4. MANUFACTURER=unknown
  5. BRAND=generic
  6. TYPE=eng
  7. ......
  8. Caused by: java.lang.NullPointerException
  9. at com.scott.crash.MainActivity.onCreate(MainActivity.java:13)
  10. at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
  11. at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
  12. ... 11 more
CPU_ABI=armeabi
CPU_ABI2=unknown
ID=FRF91
MANUFACTURER=unknown
BRAND=generic
TYPE=eng
......
Caused by: java.lang.NullPointerException
at com.scott.crash.MainActivity.onCreate(MainActivity.java:13)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
... 11 more

这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器,有关文件上传的技术,请参照Android中使用HTTP服务相关介绍。

不过在使用HTTP服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:

  1. /**
  2. * 网络是否可用
  3. *
  4. * @param context
  5. * @return
  6. */
  7. public static boolean isNetworkAvailable(Context context) {
  8. ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
  9. NetworkInfo[] info = mgr.getAllNetworkInfo();
  10. if (info != null) {
  11. for (int i = 0; i < info.length; i++) {
  12. if (info[i].getState() == NetworkInfo.State.CONNECTED) {
  13. return true;
  14. }
  15. }
  16. }
  17. return false;
  18. }

Android中处理崩溃异常的更多相关文章

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

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

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

    大家都知道,现在安装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. 如何将网页的title前面的图标替换成自己的图标

    首先要准备自己的图标,图标必须是.ico格式的图片,网上有很多在线工具可以将自己的图片转换成ico格式的图片,这里给大家介绍两个网站 在线ico转换工具:生成的图标是可以选尺寸的,原图片的大小不限制 ...

  2. docker学习(5) 在mac中创建mysql docker容器

    github上有一个专门的docker-libary项目,里面有各种各样常用的docker镜像,可以做为学习的示例,今天研究下其中mysql镜像的用法,国内镜像daocloud.io也能找到mysql ...

  3. [LeetCode] Find Median from Data Stream 找出数据流的中位数

    Median is the middle value in an ordered integer list. If the size of the list is even, there is no ...

  4. Java正则表达式入门——转自RUNOOB.COM

    Java 正则表达式 正则表达式定义了字符串的模式. 正则表达式可以用来搜索.编辑或处理文本. 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别. Java正则表达式和Perl的是最为相似 ...

  5. 常用Linux命令

    1.mkdir 建立目录 $ mkdir testdir 2.ls   列出目录下的内容的详细信息  ls -al testdir 3.cd  更换当前工作目录   cd testdir 4.pwd  ...

  6. Sprng ecache

    Ehcache是一种广泛使用的开源java分布式缓存,它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点. 主要的 ...

  7. Redis-Sentinel(Redis集群监控管理)

    Redis的高可用方案的实现:主从切换以及虚拟IP或客户端 从Redis 2.8开始加入对Sentinel机制从而实现了服务器端的主从切换,但目前尚未发现实现虚拟IP或客户端切换方案 Redis-Se ...

  8. BZOJ 1176: [Balkan2007]Mokia

    1176: [Balkan2007]Mokia Time Limit: 30 Sec  Memory Limit: 162 MBSubmit: 2012  Solved: 896[Submit][St ...

  9. ASE周会记录

    本周Sprint Master Atma Hou 一. 本周会议概要 本次会议的主要任务是明确和老师讨论后的数据库设计定稿,同时为我们接下来的连接工作确定包含实现细节的story和接口. 二. 会议内 ...

  10. CGI, FastCGI, WSGI, uWSGI, uwsgi简述

    CGI 通用网关接口(Common Gateway Interface/CGI)是一种重要的互联网技术,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据.CGI描述了服务器和请求处理程 ...