开篇就借用一下清代黄宗羲的《明儒学案》中的一句话与各位共勉——“学则智,不学则愚”。我们一起加油ヾ(◍°∇°◍)ノ゙!!
在做项目的时候,当程序出现异常时,如果能够及时捕获到并上传到服务器,那么这样我们就能够看到异常日志信息了。本文就来实现这个功能。
如果某个应用安装的Thread.UncaughtExceptionHandler未移交给默认的Thread.UncaughtExceptionHandler,则当出现未捕获的异常时,系统不会终止应用,即不会出现系统默认的“抱歉,xxx应用已停止运行”。因此,我们就可以对应用的异常进行全局捕获,从而将异常保存下来。首先,新建CrashHandler类并实现Thread.UncaughtExceptionHandler接口,并重写public void uncaughtException(Thread thread, Throwable ex)方法,当产生异常时就会转入该函数处理。
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
//退出程序
ExitAppUtils.getInstance().exit();
}
最后一行代码用来退出程序,之前在网上查找资料,退出程序的方式多为:android.os.Process.killProcess(android.os.Process.myPid())、System.exit(0)或者System.exit(2),但在实际操作中,程序并不会完美退出,而是会多次执行handleException()方法。本文的ExitAppUtils()类使用List来对APP的Activity进行管理,现将所有的Activity销毁,最后实现程序的退出,该类的代码将在本文末尾给出。
正常情况下,uncaughtException()方法会调用handleException()方法对异常进行处理,之后睡两秒程序退出。我们也可以在handleException()方法中进行设置,从而给出某种特殊情况交由系统默认的异常处理器来处理。
我们来看一下handleException()方法是怎样对异常进行处理的:
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*
* @param ex
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
new Thread() {
@Override
public void run() {
Looper.prepare();
CustomToast.showLongError(mContext, "很抱歉,程序出现异常,即将退出");
Looper.loop();
}
}.start();
collectDeviceInfo(ex);//收集设备参数等信息
saveCrashInfo2Json();//保存日志文件
return true;
}
新开的线程用来对用户进行提示,时长为之前sleep的长度。关于收集设备参数信息,上一篇博客Android 获取手机及APP信息实例详解已经进行了说明,本文不再赘述。saveCrashInfo2Json()方法的代码如下:
/*
生成JSON
*/
private void saveCrashInfo2Json() {
String json = JSON.toJSONString(errorPram);
String fileName = "crash.txt";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
try {
FileOutputStream fos = new FileOutputStream(path + fileName);
fos.write(json.getBytes());
fos.close();
} catch (Exception e) {
}
}
}
在这里借助了第三方库fastjson将JavaBean对象转换为JsonString,存入text文档中。fastjson可以实现JsonObject、JavaBean以及JsonString三者之间的相互转化。同理,我们也可以使用它来进行Json文本的解析,这个在再次启动APP,需要将错误日志上传的时候用到。代码如下:
/*
解析JSON,上传错误日志
*/
private void newThread() {
new Thread() {
@Override
public void run() {
String pathname = Environment.getExternalStorageDirectory().getAbsolutePath() + "/救灾通/" + "crash.txt";
final File file = new File(pathname); // 要读取以上路径的input。txt文件
String text = "";
if (file.exists()) {
try {
String encoding = "utf-8";
if (file.isFile() && file.exists()) { //判断文件是否存在
InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);//考虑到编码格式
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt = "";
while ((lineTxt = bufferedReader.readLine()) != null) {
text += lineTxt;
}
read.close();
} else {
return;
}
} catch (Exception e) {
e.printStackTrace();
}
myError = JSON.parseObject(text, ErrorPram.class);
} else {
return;
}
}
}.start();
}
Fastjson API入口类是com.alibaba.fastjson.JSON,常用的序列化操作都可以在JSON类上的静态方法直接完成。
public static final Object parse(String text); // 把JSON文本parse为JSONObject或者JSONArray
public static final JSONObject parseObject(String text); // 把JSON文本parse成JSONObject
public static final
public static final JSONArray parseArray(String text); // 把JSON文本parse成JSONArray
public static final
public static final String toJSONString(Object object); // 将JavaBean序列化为JSON文本
public static final String toJSONString(Object object, boolean prettyFormat); // 将JavaBean序列化为带格式的JSON文本
public static final Object toJSON(Object javaObject); //将JavaBean转换为JSONObject或者JSONArray。
最后是ExitAppUtils类:
import android.app.Activity;
import java.util.LinkedList;
import java.util.List;
/**
* android退出程序的工具类,使用单例模式
* 1.在Activity的onCreate()的方法中调用addActivity()方法添加到mActivityList
* 2.你可以在Activity的onDestroy()的方法中调用delActivity()来删除已经销毁的Activity实例
* 这样避免了mActivityList容器中有多余的实例而影响程序退出速度
* @author xiaanming
*
*/
public class ExitAppUtils {
/**
* 转载Activity的容器
*/
private List mActivityList = new LinkedList();
private static ExitAppUtils instance = new ExitAppUtils();
/**
* 将构造函数私有化
*/
private ExitAppUtils(){};
/**
* 获取ExitAppUtils的实例,保证只有一个ExitAppUtils实例存在
* @return
*/
public static ExitAppUtils getInstance(){
return instance;
}
/**
* 添加Activity实例到mActivityList中,在onCreate()中调用
* @param activity
*/
public void addActivity(Activity activity){
mActivityList.add(activity);
}
/**
* 从容器中删除多余的Activity实例,在onDestroy()中调用
* @param activity
*/
public void delActivity(Activity activity){
mActivityList.remove(activity);
}
/**
* 退出程序的方法
*/
public void exit(){
for(Activity activity : mActivityList){
activity.finish();
}
System.exit(0);
}
}
Android退出程序的工具类,使用单例模式。在Activity的onCreate()的方法中调用addActivity()方法添加到mActivityList;你可以在Activity的onDestroy()的方法中调用delActivity()来删除已经销毁的Activity实例,这样避免了mActivityList容器中有多余的实例而影响程序退出速度。
到这里我们就可以实现全局捕获异常并上传的功能了。但我仍然有个问题不太懂,按道理说程序产生异常时直接上传就可以了,上传不成功时才会保存至本地。可是在实际过程中有时上传成功的,服务器返回的状态码也是200没问题,但前端死活接收不到服务器端的response,最后只能作罢,如有哪位大神知道恳请指点。