Android程序crash处理

时间 2014-11-24 13:45:37  CSDN博客
主题 Android

在实际项目开发中,会出现很多的异常直接导致程序crash掉,在开发中我们可以通过logcat查看错误日志,Debug出现的异常,让程序安全的运行,但是在开发中有些异常隐藏的比较深,直到项目发布后,由于各种原因,譬如android设备不一致等等,android版本不同,实际上我们在测试的时候不可能在市场上所有的Android设备上都做了测试,当用户安装使用时被暴露出来,导致程序直接crash掉,这显然对于用户是不OK的!这些在用户设备上导致crash的异常我们是不知道的,要想知道这些异常出现的一些信息,我们还是得自己通过程序捕获到异常,并且将其记录下来(本地保存或者上传服务器),方便项目维护。

先来看一下,我自己“故意”定义出来的一个异常,在MainActivity,java中:

package com.example.crash;

import android.os.Bundle;
import android.app.Activity; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int i = 1;
System.out.println(i/0);
}
}

以上程序报出一个数学运算的除0异常,显然程序被“崩溃”,不能继续执行的。看一下运行效果截图:

运行结果如上图所示,这种直接崩溃的效果对于用户来说是很不OK的,用户不知道发生了什么,程序就停止了,会让用户对程序有种不想继续使用的想法。 对于程序中未捕获的异常,我们可以做哪些操作呢!我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因,这是一种最佳实践,那么我们接下来就必须要熟悉两个类别,一个是android提供的Application,另一个是Java提供的Thread.UncaughtExceptionHandler。

Application:这是android程序管理全局状态的类,Application在程序启动的时候首先被创建出来,它被用来统一管理activity、service、broadcastreceiver、contentprovider四大组件以及其他android元素,这里可以打开android工程下的Mainifest.xml文件查看一下。我们除了使用android默认的Application来处理程序,也可以自定义一个Application处理一些需要在全局状态下控制程序的操作,例如本文讲到的处理程序未知异常时,这是一种最佳实践。

Thread.UncaughtExceptionHandler:关于这个概念的解释,我在JDK1.6的文档中找到一些科学的解释。

 当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。 
      当某一线程因未捕获的异常而即将终止时,Java 虚拟机将使用 Thread.getUncaughtExceptionHandler() 查询该线程以获得其 UncaughtExceptionHandler 的线程,并调用处理程序的 uncaughtException 方法,将线程和异常作为参数传递。如果某一线程没有明确设置其 UncaughtExceptionHandler,则将它的 ThreadGroup 对象作为其 UncaughtExceptionHandler。如果 ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给默认的未捕获异常处理程序。 

Thread.UncaughtExceptionHandler是一个接口,它提供如下的方法,让我们自定义处理程序。

void uncaughtException(Thread t,Throwable e)

当给定线程因给定的未捕获异常而终止时,调用该方法。Java 虚拟机将忽略该方法抛出的任何异常。参数:t - 线程  e - 异常

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

package com.example.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; 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;
}
}

完成了这个CrashHandler类之后,还需要自定义一个全局Application来启动管理异常收集,以下是自定义的Application类,很简单:

package com.example.crash;

import android.app.Application;

public class CrashApplication extends Application {

	@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
} }

最后,为了让程序在启动时使用我们自定义的Application,必须在Mainifest.xml的Application节点上,声明出我们自定义的Application:

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

配置SDCard写文件的权限:

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

运行以下程序:

在SD卡中找到crash文件夹,打开文件夹:

到处这个log日志,用notepad打开,查看内容如下:

TIME=1385535270000
......
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.crash/com.example.crash.MainActivity}:
java.lang.ArithmeticException: divide by zero
......
Caused by: java.lang.ArithmeticException: divide by zero
at com.example.crash.MainActivity.onCreate(MainActivity.java:13)
at android.app.Activity.performCreate(Activity.java:5243)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)
... 11 more
java.lang.ArithmeticException: divide by zero
at com.example.crash.MainActivity.onCreate(MainActivity.java:13)
......

好了,程序中未捕获的异常被及时捕捉到,保存在SD卡中,并且给用户良好的提示信息,被没有一下子crash掉,通过SD卡中的错误日志,我们可以很快定义到错误的根源,方便我们及时对程序进行修正。当然了,这里我由于做的是个Demo,所以相关错误日志仅仅保存在了SD卡上,其实好的做法是将错误日志上传到服务器中,以便我们收集来自四面八方用户的日志,为程序进行更新迭代升级。

注:该文是我学习笔记,里面会有一些Bug。程序仅作为参考实例,不能直接使用到真实项目中,请谅解!

参考资料: http://www.cjsdn.net/Doc/JDK60/java/lang/Thread.UncaughtExceptionHandler.html

http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html

http://blog.csdn.net/liuhe688/article/details/6584143

Android程序crash处理的更多相关文章

  1. Android程序Crash时的异常上报

    转载请注明来源:http://blog.csdn.net/singwhatiwanna/article/details/17289479 前言 大家都知道,android应用不可避免的会发生crash ...

  2. android 程序崩溃crash日志的捕捉

    android 程序崩溃crash日志的捕捉 之前在项目开发过程中,一直会遇到程序崩溃了,但是测试組的哥哥们又没及时的导出日志.... 后来在诳群的时候听别人说起,腾讯有那么一个叫bugly的东西 将 ...

  3. 【Bugly干货分享】手把手教你逆向分析 Android 程序

    很多人写文章,喜欢把什么行业现状啊,研究现状啊什么的写了一大通,感觉好像在写毕业论文似的,我这不废话,先直接上几个图,感受一下. 第一张图是在把代码注入到地图里面,启动首页的时候弹出个浮窗,下载网络的 ...

  4. 捕android程序崩溃日志

    主要类别: package com.example.callstatus; import java.io.File; import java.io.FileOutputStream; import j ...

  5. Android程序捕获未处理异常,处理与第三方方法冲突时的异常传递

    自己的android程序对异常进行了处理,用的也是网上比较流行的CrashHandler,代码如下,就是出现了未处理的异常程序退出,并收集收集设备信息和错误信息仪器保存到SD卡,这里没有上传到服务器. ...

  6. Android程序崩溃异常收集框架

    最近在写Android程序崩溃异常处理,完成之后,稍加封装与大家分享. 我的思路是这样的,在程序崩溃之后,将异常信息保存到一个日志文件中,然后对该文件进行处理,比如发送到邮箱,或发送到服务器. 所以, ...

  7. Android Native crash日志分析

    在Android应用crash的类型中,native类型crash应该是比较难的一种了,因为大家接触的少,然后相对也要多转几道工序,所有大部分对这个都比较生疏.虽然相关文章也有很多了,但是我在刚开始学 ...

  8. Android程序backtrace分析方法

    如何分析Android程序的backtrace 最近碰到Android apk crash的问题,单从log很难定位.从tombstone里面得到下面的backtrace. *** *** *** * ...

  9. 【定有惊喜】android程序员如何做自己的API接口?php与android的良好交互(附环境搭建),让前端数据动起来~

    一.写在前面 web开发有前端和后端之分,其实android还是有前端和后端之分.android开发就相当于手机app的前端,一般都是php+android或者jsp+android开发.androi ...

随机推荐

  1. C#读写者线程(用AutoResetEvent实现同步)(转载)

    C#读写者线程(用AutoResetEvent实现同步) 1. AutoResetEvent简介 通知正在等待的线程已发生事件.无法继承此类. 常用方法简介: AutoResetEvent(bool ...

  2. 项目中遇到的各种bug和踩过的坑

    zepto 赋值时单位转换问题 zepto 的 animate 方法移动某个元素的位置时,例如修改某个绝对定位的元素的 left 值,要与修改前的值单位一致,修改前如果是像素值,修改后也要是像素值,否 ...

  3. flag--命令行参数解析之StringVar

    func StringVar func StringVar(p *string, name string, value string, usage string) StringVar定义了一个有指定名 ...

  4. JavaBean,POJO,VO,DTO的区别和联系

    JavaBeans A JavaBean is a class that follows the JavaBeans conventions as defined by Sun. Wikipedia ...

  5. php面试题目

     PHP测试小例 1. 禁用COOKIE 后 SEESION 还能用吗? Cookie 是保存在浏览器 1.cookie是保存在本地的,而seesion是保存在服务器上的.所以两者没有直接的关系,禁用 ...

  6. Generate transparent shape on image

    Here is an example code to generate transparent shape on image. Need to pay attention can not use cv ...

  7. 利用cubieboard设置samba打印服务器

    #注意安装下面软件前,先将cubieboard的动态地址改为静态地址! apt-get install samba #安装samba vi /etc/samba/smb.conf //配置 workg ...

  8. PHP存储blob示例(转)

    原文:http://www.mysqltutorial.org/php-mysql-blob/ <?php /* CREATE TABLE files ( id INT AUTO_INCREME ...

  9. Elasticsearch常用配置及性能参数

    cluster.name: estest   集群名称node.name: “testanya”  节点名称 node.master: false  是否主节点node.data: true   是否 ...

  10. python基础知识9——模块2——常见内置模块

    内置模块 内置模块是Python自带的功能,在使用内置模块相应的功能时,需要[先导入]再[使用] 1.sys 用于提供对Python解释器相关的操作: sys.argv 命令行参数List,第一个元素 ...