Android从捕获全局异常说起
半路出家进入IT界也有两年了,从来只会做伸手党,纯属一个只会复制粘贴代码界码渣,想想让人很是羞愧啊,合租的大神教育我说应该写写博客,这样, 当我们失去了青春的时候,起码我们还留下了代码。 说的多好!!!上学就没记过笔记的学渣深受鼓舞,立马提起了三分钟的热度,于是就有了这个博客,能坚持多久还不知道,但起码要有个开始是不是。
是不是我太笨了,这个MarkDown在线编辑器不太会用啊,需要学习的实在太多
IT界有那么一句流传已久的话 代码没有bug的程序员还没出生呢,我觉得十分在理,作为一个Android的程序员最尴尬的事莫过于在给老板或者客户演示app的时候程序crash掉了,菜鸟如我crash更是家常便饭,在我手机上跑的好好的给测试一测立马全是bug。特别是在我们大天朝各种改版rom什么的crash的情况特别常见,于是一个异常记录的日志就显得特别重要,现在第三方的错误日志上报工具非常多,比如友盟,听云,bugly什么的数不胜数,也是特别的简单实用,但是知其然也要知其所以然,自己动手写一个或许未尝不可,首先要从战略上藐视它,这功能我能写。
以上都是废话下面进入正题
- 捕获全局异常
方法就是在Application内注册UncaughtExceptionHandler
在application的onCreate()方法里面调用Thread.setDefaultUncaughtExceptionHandler()方法
代码如下:
@Override
public void onCreate() {
super.onCreate();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
//这里处理漏网的未捕获异常ex
handleEeption(ex);
}
});
}
- 将异常信息写到log文件
拿到这个exception后我们想要记录的信息就是平时使用的ex.printStackTrace()打印出来的信息。
首先将将ex的内容转换成我们想要的字符串。
/**
* 获取exception的堆栈信息
* @param ex
* @return 堆栈信息字符串
*/
private String exceptionToString(Throwable ex){
Writer write = new StringWriter();
PrintWriter pw = new PrintWriter(write);
ex.printStackTrace(pw);
pw.close();
String error = pw.toString();
return error;
}
如果Log一下得到的error字符串也许你会发现得到的东西不完整甚至错误信息都涉及不到我们自己的代码,这不是我想要的那样,也许关键的causeby都没有,但是这个异常确实又是我们的代码错误引起的。这是因为我们自己代码的异常可能被系统捕获到然后继续往上抛出来,这时候我们需要把所以关联的异常信息一起打印出来。修正如下
/**
* 获取exception的堆栈信息
* @param ex
* @return 堆栈信息字符串
*/
private String exceptionToString(Throwable ex){
Writer write = new StringWriter();
PrintWriter pw = new PrintWriter(write);
ex.printStackTrace(pw);
Throwable cause = ex.getCause();
while (cause!=null){
cause.printStackTrace(pw);
cause = cause.getCause();
}
pw.close();
return pw.toString();
}
这时获得的才是我们想要的堆栈信息。
- 格式化异常信息
仅仅获得了异常的堆栈信息往往是不够的,往往还要加上 时间、手机型号、Android版本号、Rom信息、软件版本信息等等这些我觉得可以做个小工具类,就当是下篇blog的内容吧。
这里我们先预留个方法在这。
/**
* 格式化Log信息
* @param error
* @return
*/
private String formatLogMessage(String error){
return error;
}
- 将准备好的日志信息写到文件或者上传到服务器
上传到服务器我就不写了,咱们就写把日志写到sd卡。首先声明sd卡读写的权限这个没的说,下面是才是正事
/**
* 将log信息写到sd卡
* @param log
*/
private void writeLogToFile(String log){
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File sdCardDir = Environment.getExternalStorageDirectory();//获取SDCard目录
File sdFile = new File(sdCardDir, "log.txt");
try {
FileOutputStream fos = new FileOutputStream(sdFile,true);
fos.write(log.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
当然这里要注意的地方也非常多
1、sd卡是否存在
2、是否有写入的权限 ,剩余空间是否足够
3、log文件的路径、文件名应该都是可配置的
4、如果log文件已存在并且文件的大小已经很大了应该要将重新生成个新的log文件,或者将原来的log文件重命名保存
我们修改下代码留几个方法方便扩展
private void writeLogToFile(String log){
if(isSdAvailable() && isWritable()){
File logFile = getLogFile();
try {
FileOutputStream fos = new FileOutputStream(logFile,true);
fos.write(log.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* sd卡是否可用
* @return
*/
private boolean isSdAvailable(){
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
/**
* 是否有写的权限和空间是否充足
* @return
*/
private boolean isWritable(){
return true;
}
/**
* 获取log文件的File对象 可以在这里设置文件名路径 对以前的log文件做处理什么的
* @return
*/
private @NonNull File getLogFile(){
File sdCardDir = Environment.getExternalStorageDirectory();
File logFile = new File(sdCardDir, "log.txt");
return logFile;
}
当然写入文件的部分也可以单独提出来个工具类,咱们也放到下次blog里再说吧,总要给自己留点事做不然没有坚持下去的动力。
下面上全部代码 将SApplication作为应用的application就行了
public class SApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
SExceptionHandler.getInstance().init(this);
}
}
public class SExceptionHandler implements Thread.UncaughtExceptionHandler {
private static SExceptionHandler instance = new SExceptionHandler();
private SExceptionHandler(){
}
public static SExceptionHandler getInstance(){
return instance;
}
public void init(Context context){
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
String log = exceptionToString(ex);
writeLogToFile(log);
android.os.Process.killProcess(android.os.Process.myPid());
}
/**
* 获取exception的堆栈信息
* @param ex
* @return 堆栈信息字符串
*/
private String exceptionToString(Throwable ex){
Writer write = new StringWriter();
PrintWriter pw = new PrintWriter(write);
ex.printStackTrace(pw);
Throwable cause = ex.getCause();
while (cause!=null){
cause.printStackTrace(pw);
cause = cause.getCause();
}
pw.close();
return pw.toString();
}
/**
* 格式化Log信息
* @param error
* @return
*/
private String formatLogMessage(String error){
return error;
}
/**
* 将log信息写到sd卡
* @param log
*/
private void writeLogToFile(String log){
if(isSdAvailable() && isWritable()){
File logFile = getLogFile();
try {
FileOutputStream fos = new FileOutputStream(logFile,true);
fos.write(log.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* sd卡是否可用
* @return
*/
private boolean isSdAvailable(){
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
/**
* 是否有写的权限和空间是否充足
* @return
*/
private boolean isWritable(){
return true;
}
/**
* 获取log文件的File对象 可以在这里设置文件名路径 对以前的log文件做处理什么的
* @return
*/
private @NonNull File getLogFile(){
File sdCardDir = Environment.getExternalStorageDirectory();
File logFile = new File(sdCardDir, "log.txt");
return logFile;
}
}