android 异常收集

当开发了一个APP,发布出去之后难免会碰到系统异常的情况,而且大多数的异常是不可重现的。经常遇到的情况就是平台迁移之后,数据与原来存在差异,比如说实际情况种

的数据和开发和测试不一样。还有就是测试覆盖面不全,市面上的手机型号那么多,而有的公司并不会为没款手机都适配,跑不同的手机出现异常在所难免。这个时候如果出现

了异常调试起来时比较麻烦的,因为出现异常的环境不一定就能重现出来。

这里主要总结一下开发中用到的异常捕获和调试方法,不同于开发中使用的logcat。思路和原理都很简单,就是当应用发生 异常的时候,可以把异常信息收集并保存起来,我这

里是把它保存到了一个固定目录下的文件中,出现异常时可以通过异常记录文件跟踪定位异常,分析出错原因。

捕获异常的方法是通过实现UncaughtExceptionHandler接口,当异常发生的时候,会回调uncaughtException()方法,在此方法中完成我们需要处理的工作,此处仅把出错信息

保存到固定目录中。

1.首先定义一个自己的异常处理类,实现UncaughtExceptionHandler接口,代码如下:

public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
	
	private static ExceptionHandler instance = new ExceptionHandler();
	private Context context;
	private String infoPath = "/ErrorLog/";
	private Thread.UncaughtExceptionHandler defaultHandler;
	private Map<String, String> devInfos = new HashMap<String, String>();
	private DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());

	public static ExceptionHandler getInstance() {
		return instance;
	}

	public void setCustomCrashHanler(Context ctx) {
		context = ctx;
		defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		Thread.setDefaultUncaughtExceptionHandler(this);
	}

	/**
	 * @name uncaughtException(Thread thread, Throwable ex)
	 * @description 当发生UncaughtException时会回调此函数
	 * @param thread 发生异常的线程
	 * @param ex 抛出的异常
	 * @return void
	 */
	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		boolean isDone = doException(ex);
		if (!isDone && defaultHandler != null) {
			// 如果用户没有处理则让系统默认的异常处理器来处理
			defaultHandler.uncaughtException(thread, ex);
		} else {
			// 如果自己处理了异常,则不会弹出错误对话框,则需要手动退出app
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {

			}
		}
		android.os.Process.killProcess(android.os.Process.myPid());
		System.exit(0);
	}

	/**
	 * @name doException(Throwable ex)
	 * @description 处理异常
	 * @param ex 抛出的异常
	 * @return 异常处理标志
	 */
	private boolean doException(Throwable ex) {
		if (ex == null) {
			return true;
		}
		
		new Thread() {  
            @Override  
            public void run() {  
                Looper.prepare();  
                Toast.makeText(context, "程序出现错误退出!", Toast.LENGTH_LONG).show();  
                Looper.loop();  
            }  
        }.start(); 

		collectDeviceInfo(context);
		saveExceptionToFile(ex);
		return true;
	}


	/**
	 * @name collectDeviceInfo(Context ctx)
	 * @description 收集必须的设备信息
	 * @param ctx 
	 * @return void
	 */
	public void collectDeviceInfo(Context ctx) {
		try {
			PackageManager pm = ctx.getPackageManager();
			PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
			if (pi != null) {
				devInfos.put("versionName", pi.versionName);
				devInfos.put("versionCode", "" + pi.versionCode);
				devInfos.put("MODEL", "" + Build.MODEL);
				devInfos.put("SDK_INT", "" + Build.VERSION.SDK_INT);
				devInfos.put("PRODUCT", "" + Build.PRODUCT);
				devInfos.put("TIME", "" + getCurrentTime());
			}
		} catch (NameNotFoundException e) {
			
		}
		Field[] fields = Build.class.getDeclaredFields();
		for (Field field : fields) {
			try {
				field.setAccessible(true);
				devInfos.put(field.getName(), field.get(null).toString());
			} catch (Exception e) {
			}
		}
	}

	/**
	 * @name saveExceptionToFile(Throwable ex)
	 * @description 保存异常信息到文件中
	 * @param ex 抛出的异常
	 * @return void
	 */
	private void saveExceptionToFile(Throwable ex) {
		StringBuffer sb = new StringBuffer();
		for (Map.Entry<String, String> entry : devInfos.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 {
			String time = df.format(new Date());
			String fileName = time + ".txt";

			if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
				String path = Environment.getExternalStorageDirectory() + infoPath;
				File dir = new File(path);
				if (!dir.exists()) {
					dir.mkdirs();
				}
				FileOutputStream fos = new FileOutputStream(path + fileName);
				fos.write(sb.toString().getBytes());
				fos.close();
			}
		} catch (Exception e) {
			
		}
	}
	
	/**
	 * @name getCurrentTime()
	 * @description 获取当前时间
	 * @param void
	 * @return 当前时间
	 */
	public static String getCurrentTime() {
		SimpleDateFormat sdf = null;
		sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String CurrentTime = sdf.format(new Date());
		return CurrentTime;
	}
}



2.在自定义Application初始化异常捕获对象,并设置异常出现是处理的对象:

/**
 * @name MyApplication
 * @description 自定义Application
 * @author cold
 * @date 2016.05.18 15:30
 * @copyright 
 */
public class MyApplication extends Application {

	@Override
	public void onCreate() {
		super.onCreate();
		ExceptionHandler mCustomCrashHandler = ExceptionHandler.getInstance();
		mCustomCrashHandler.setCustomCrashHanler(this);
	}

}

3.在MainActivity里模拟出现空指针的异常

public class MainActivity extends Activity {

	private Button btnTest = null;
	private Button btnError = null;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		btnTest = (Button)findViewById(R.id.btn_test);
		btnTest.setOnClickListener(listener);
	}

	private OnClickListener listener = new OnClickListener() {

		@Override
		public void onClick(View v) {
			// TODO Auto-generated method stub
			btnError.setText("error");
		}
		
	};
}
代码中btnError并未初始化,所以会报出空指针异常。

4.当出现异常时,可以查看/storage/sdcard0/ErrorLog目录下的异常文件,根据此文件分析异常原因:

文件内容截取:

java.lang.NullPointerException
	at com.example.exceptiontest.MainActivity$1.onClick(MainActivity.java:26)
	at android.view.View.performClick(View.java:4496)
	at android.view.View$PerformClick.run(View.java:18603)
	at android.os.Handler.handleCallback(Handler.java:733)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:136)
	at android.app.ActivityThread.main(ActivityThread.java:5426)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
	at dalvik.system.NativeStart.main(Native Method)

可以看出这和我们使用logcat调试一样,只不过是多了一步文件存储的过程。

4.需要在AndroidManifest.xml中添加sd卡权限

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

例子代码:

点击打开链接

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值