在APP上线后,可能会出现一些BUG导致了APP的闪退,用户体验就非常致命,我们一定要第一时间找到问题的所在
1、我们需要自定义一个异常收集类(创建一个Thread.UncaughtExceptionHandler的继承类);
2、替换掉APP本身的异常处理(在Thread.UncaughtExceptionHandler实现类中使用Thread.setDefaultUncaughtExceptionHandler(this)方法替换);
3、在类中收集信息,这个信息最好包括手机的一些信息,比如:厂商、型号、cup型号、内存大小等...,因为安卓手机的多样性导致我们在适配的时候非常麻烦,也是因为有些问题的出现是因为个别的硬件差异造成的,所以这些信息最好收集;
整体思路就是,自定义一个异常收集类替换到本来的异常处理类,在这个类中去收集一些必要的信息回传到我们的后台,我们可以在崩溃信息发生的第一时间去处理
以下是异常收集类代码,可以用作参考
/**
* Created by zsk on 2019/6/11.
*/
public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler {
private static final String TAG = "CrashHandlerUtil";
//创建CrashHandlerUtil实列
private static CrashHandlerUtil crashHandlerUtil = new CrashHandlerUtil();
//系統默认UncaughtException处理类
private Thread.UncaughtExceptionHandler mDefaultHandler;
//程序Context对象
private Context mContext;
//用来存储设备信息和异常信息
private Map<String, String> infos = new HashMap<>();
//用于格式化日期,作为日志名的一部分
private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss",
Locale.CHINA);
private static String error = "程序崩溃!!!";
// 外置存储卡默认预警临界值
private static final long THRESHOLD_WARNING_SPACE = 100 * M;
// 保存文件时所需的最小空间的默认值
public static final long THRESHOLD_MIN_SPCAE = 60 * M;
public static CrashHandlerUtil getInstance() {
return crashHandlerUtil;
}
public void init(Context context){
mContext=context;
//获取系统默认uncaughtException处理器
mDefaultHandler=Thread.getDefaultUncaughtExceptionHandler();
//设置CrashHandler为程序默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 程序异常调用该方法
*@param thread 线程
* @param throwable 异常
*/
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
StringBuilder sb = new StringBuilder();
// 1.获取当前应用程序的版本号.
PackageManager pm = mContext.getPackageManager();
try {
PackageInfo packinfo = pm.getPackageInfo(mContext.getPackageName(),
0);
sb.append("程序的版本号为" + packinfo.versionName);
sb.append("\n");
// 2.获取手机的硬件信息.
Field[] fields = Build.class.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
// 暴力反射,获取私有的字段信息
fields[i].setAccessible(true);
String name = fields[i].getName();
sb.append(name + " = ");
String value = fields[i].get(null).toString();
sb.append(value);
sb.append("\n");
}
// 3.获取程序错误的堆栈信息 .
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
throwable.printStackTrace(printWriter);
String result = writer.toString();
sb.append(result);
// 4将错误信息转成文件上传到服务器
uploadFile(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* url 完整的接口地址
* file 需要上传的文件
* fileUploadObserver 上传回调
*/
private void uploadFile(String s) {
try {
File file = string2file(s);
RetrofitHelp.getIns().upLoadFile("", file, new
FileUploadObserver<ResponseBody>() {
@Override
public void onUpLoadSuccess(ResponseBody responseBody) {
//上传成功
}
@Override
public void onUpLoadFail(Throwable e) {
//上传失败
}
@Override
public void onProgress(int progress) {
//上传进度
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
*
* @param ex
* @return true:如果处理了该异常信息;否则返回 false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
// 收集设备参数信息
collectDeviceInfo(mContext);
// 保存日志文件
saveCrashInfo2File(ex);
// 使用 Toast 来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, error, Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
return true;
}
/**
* 收集设备参数信息
*
* @param context 上下文
*/
public void collectDeviceInfo(Context context){
try {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
if(packageInfo!=null){
String versionName= packageInfo.versionName == null ? "null" : packageInfo.versionName;
String versionCode = packageInfo.versionCode + "";
infos.put("versionName",versionName);
infos.put("versionCode",versionCode);
}
} catch (PackageManager.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 (IllegalAccessException e) {
Log.e(TAG, "an error occured when collect crash info", e);
}
}
}
/**
* 保存错误信息到文件中 *
*
* @param ex
* @return 返回文件名称,便于将文件传送到服务器
*/
private String saveCrashInfo2File(Throwable ex) {
StringBuffer sb = getTraceInfo(ex);
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 = dateFormat.format(new Date());
String fileName = "crash-" + time + "-" + timestamp + ".log";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory() + "/crash/";
Log.i("TAG","路径======="+path);
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
else if(!hasEnoughSpaceForWrite(path))
{
deleteDirectory(path);
}
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;
}
/**
* 整理异常信息
* @param e
* @return
*/
public static StringBuffer getTraceInfo(Throwable e) {
StringBuffer sb = new StringBuffer();
Throwable ex = e.getCause() == null ? e : e.getCause();
StackTraceElement[] stacks = ex.getStackTrace();
for (int i = 0; i < stacks.length; i++) {
sb.append("class: ").append(stacks[i].getClassName())
.append("; method: ").append(stacks[i].getMethodName())
.append("; line: ").append(stacks[i].getLineNumber())
.append("; Exception: ").append(ex.toString() + "\n");
}
return sb;
}
/**
* 删除文件夹下的文件
* @param filePath 被删除目录下文件的路径
* @return 目录删除成功返回true,否则返回false
*/
public boolean deleteDirectory(String filePath) {
boolean flag = false;
//如果filePath不以文件分隔符结尾,自动添加文件分隔符
if (!filePath.endsWith(File.separator)) {
filePath = filePath + File.separator;
}
File dirFile = new File(filePath);
if (!dirFile.exists() || !dirFile.isDirectory()) {
return false;
}
flag = true;
File[] files = dirFile.listFiles();
//遍历删除文件夹下的所有文件(包括子目录)
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
//删除子文件
flag = deleteFile(files[i].getAbsolutePath());
if (!flag) break;
}
}
if (!flag)
return false;
return true;
}
/**
* 删除单个文件
* @param filePath 被删除文件的文件名
* @return 文件删除成功返回true,否则返回false
*/
public boolean deleteFile(String filePath) {
File file = new File(filePath);
if (file.isFile() && file.exists()) {
return file.delete();
}
return false;
}
/**
* 判断外部存储是否存在,以及是否有足够空间保存指定类型的文件
*
* @param directoryPath
* @return false: 无存储卡或无空间可写, true: 表示ok
*/
public boolean hasEnoughSpaceForWrite(String directoryPath) {
long residual = getAvailableExternalSize(directoryPath);
if (residual < THRESHOLD_MIN_SPCAE) {
return false;
} else if (residual < THRESHOLD_WARNING_SPACE) {
}
return true;
}
/**
* 获取外置存储卡剩余空间
* @return
*/
public long getAvailableExternalSize(String directoryPath) {
try {
StatFs sf = new StatFs(directoryPath);
long blockSize = sf.getBlockSize();
long availCount = sf.getAvailableBlocks();
long availCountByte = availCount * blockSize;
return availCountByte;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 将String转成file
* @param s
* @return
*/
private File string2file (String s) throws Exception {
File fileDir = new File(Environment.getExternalStorageDirectory() .getAbsolutePath() +"/log");
if(!fileDir.exists()){
fileDir.mkdirs();
}
Date date = new Date();
File file = new File(fileDir, SPHelper.getInstence(mContext).getUid() + "-" + date.getYear()+date.getMonth()+date.getDay()
+date.getHours()+date.getMinutes()+ ".txt");
if(!file.exists()){
file.createNewFile();
}
ByteArrayInputStream ins = new ByteArrayInputStream(s.getBytes());
OutputStream os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
ins.close();
return file;
}
}