在做应用线上维护的时候,需要将用户在使用APP的过程中发生的错误信息及当时的手机的一些数据发送到服务器,方便开发人员分析和修改,进行后续迭代;这样就需要捕获应用发生的异常,好在Google给我们提供了一个UncaughtExceptionHandler这么一个接口,可以实现这个需求
本文所含代码随时更新,可从这里下载最新代码
传送门
实现步骤就是定义一个类ExceptionHandler,实现UncaughtExceptionHandler接口,然后重写uncaughtException方法;接下来就是根据该方法中的线程参数和异常参数将它们的信息保存起来;同时异常可能跟设备有关,所以将设备信息也收集起来;而且有的异常有可能是内存有关的,所以需要将此时的内存信息收集;最后将发生异常的线程信息收集
/**
* @Description TODO(崩溃日志信息上报)
* @author cxy
* @Date 2018/11/28 10:37
*/
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
private String TAG = ExceptionHandler.class.getSimpleName();
//默认异常处理对象
private Thread.UncaughtExceptionHandler mDefaultHandler;
private Context mContext;
//用来存储设备信息和异常信息
private StringBuffer sbInfo = new StringBuffer();
//默认日志文件存储路径
public static String CRASH_FILE_PATH ;
public ExceptionHandler(Context context) {
this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
this.mContext = context;
CRASH_FILE_PATH = "/Android/data/"+mContext.getPackageName()+"/crash";
}
@Override
public void uncaughtException(Thread t, Throwable e) {
//如果没有处理就交给系统处理
if (mDefaultHandler != null && !handleException(t,e)) {
mDefaultHandler.uncaughtException(t, e);
} else {
//用作自杀操作
Process.killProcess(Process.myPid());
//终止当前正在运行的Java虚拟机,导致程序终止,0表示正常终止,其它表示异常结束
//在部分机型中,当退出应用后弹出应用程序崩溃的对话框,有时退出后还会再次启动,少部分的用户体验不太好
// System.exit(0);
//重启应用
/*Intent intent = new Intent(mContext, SplashActivity.class);
PendingIntent restartIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager mgr = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, 0, restartIntent);
Process.killProcess(Process.myPid());*/
}
}
private boolean handleException(Thread t,Throwable ex){
if (t == null || ex == null) return false;
collectDeviceInfo();
collectRunningMsg();
saveMsg2File(t,ex);
return true;
}
/**
* 收集设备参数信息
*/
private void collectDeviceInfo() {
sbInfo.append("======设备参数信息======" + "\n");
//版本信息
Object[] pack = DevicesTools.getAppVersion(mContext);
sbInfo.append("versionName" + " = " + pack[0] + "\n");
sbInfo.append("versionCode" + " = " + pack[1] + "\n");
//获取设备信息
//sdk版本
sbInfo.append("SDK_INT" + " = " + Build.VERSION.SDK_INT + "\n");
//Android版本
sbInfo.append("RELEASE" + " = " + Build.VERSION.RELEASE + "\n");
sbInfo.append("PRODUCT" + " = " + Build.PRODUCT + "\n");
sbInfo.append("MODEL" + " = " + Build.MODEL + "\n");
sbInfo.append("DEVICE" + " = " + Build.DEVICE + "\n");
sbInfo.append("DISPLAY" + " = " + Build.DISPLAY + "\n");
sbInfo.append("BRAND" + " = " + Build.BRAND + "\n");
sbInfo.append("BOARD" + " = " + Build.BOARD + "\n");
sbInfo.append("FINGERPRINT" + " = " + Build.FINGERPRINT + "\n");
sbInfo.append("ID" + " = " + Build.ID + "\n");
sbInfo.append("MANUFACTURER" + " = " + Build.MANUFACTURER + "\n");
sbInfo.append("USER" + " = " + Build.USER + "\n");
String[] cpu = Build.SUPPORTED_ABIS;
int size = cpu == null ? 0 : cpu.length;
for (int i=0; i
这里有一点是我并没有在收集完信息后立即上传到服务器,而是在每次应用打开时去检索文件然后上报;因为前面逻辑是发生异常时收集异常信息完就立即杀死进程,给用户造成闪退的现象(稍微挽救下用户体验),如果这时候还起一个线程去上传,可能是上传不成功的,因为进程都被杀死了,线程也就没办法工作了
这里涉及到一个工具类
/**
* @Description TODO(获取设备信息辅助类)
* @author cxy
* @Date 2018/11/28 14:04
*/
public class DevicesTools {
/**
* 获取手机ram内存,即应用运行所用内存
* @param context
* @return [0] 手机ram总内存, [1] 当前ram可用内存 , [2] 系统是否处于低内存运行模式
*/
public static Object[] getRAMMemory(Context context) {
Object[] mem = new Object[3];
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
mem[0] = reviseFileSize(mi.totalMem);
mem[1] = reviseFileSize(mi.availMem);
mem[2] = mi.lowMemory;
return mem;
}
/**
* 获取软件版本信息
* @param context
* @return
*/
public static Object[] getAppVersion(Context context){
Object[] msg = new Object[2];
PackageManager manager = context.getPackageManager();
try {
PackageInfo info = manager.getPackageInfo(context.getPackageName(),PackageManager.GET_ACTIVITIES);
msg[0] = info.versionName == null ? "unknow" : info.versionName;
msg[1] = info.versionCode ;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return msg;
}
/**
* 获取应用虚拟机能获取使用的最大内存
* @return
*/
public static String getAPPMaxMemory(){
return reviseFileSize(Runtime.getRuntime().maxMemory());
}
/**
* 获取应用虚拟机已开辟内存
* @return
*/
public static String getAPPAllocatedMemory(){
return reviseFileSize(Runtime.getRuntime().totalMemory());
}
/**
* 获取应用虚拟机已释放的内存
* 调用gc()会使该值增大
* @return
*/
public static String getAPPFreeMemory(){
return reviseFileSize(Runtime.getRuntime().freeMemory());
}
//获取sd卡总大小
public static String getSDTotalSize(){
if(isSdCardMount()){
File file = Environment.getExternalStorageDirectory();
StatFs statFs = new StatFs(file.getPath());
long blockSize = statFs.getBlockSizeLong();
long totalBlocks = statFs.getBlockCountLong();
return reviseFileSize(totalBlocks*blockSize);
}else {
return null;
}
}
//获取sd卡可用大小
public static String getSDAvailableSize(){
if(isSdCardMount()){
File file = Environment.getExternalStorageDirectory();
StatFs statFs = new StatFs(file.getPath());
long blockSize = statFs.getBlockSizeLong();
long availableBlocks = statFs.getFreeBlocksLong();
return reviseFileSize(availableBlocks*blockSize);
}else {
return null;
}
}
public static String reviseFileSize(long size){
String str="KB";
float reviseSize = 0f;
if(size>1024){
reviseSize = size/1024f;
if(reviseSize>1024){
str="M";
reviseSize = reviseSize/1024f;
if (reviseSize>1024) {
str="G";
reviseSize = reviseSize/1024f;
}
}
}
DecimalFormat formatter=new DecimalFormat();
formatter.setGroupingSize(3);
String result = formatter.format(reviseSize) + str;
return result;
}
}
最后在Application类里将其设置为应用的异常处理器
@Override
public void onCreate() {
super.onCreate();
ExceptionHandler handler = new ExceptionHandler(this);
Thread.setDefaultUncaughtExceptionHandler(handler);
}
参考文章
https://blog.csdn.net/su749520/article/details/79083879