public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = CrashHandler.class.getSimpleName();
private Context mContext;
private String mPath;
private static final String sFileName = "crash";
private static final String sFileNameSuffix = ".trace";
private DateFormat mDateFormat;
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
//private ExecutorService mExecutor = Executors.newCachedThreadPool();
private static CrashHandler mInstance;
private UncaughtExceptionHandler mUncaughtExceptionHandler;
private ConcurrentHashMap mInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mPackageInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mDeviceInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mSysInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mSecureInfoMap = new ConcurrentHashMap<>();
private String mMemInfo;
private String mThrowableInfo;
/** 异常信息 */
private static final String sThrowableInfoString = "throwable_info";
/** 应用包信息 */
private static final String sPackageInfoMap = "package_info_map";
/** 设备数据信息 */
private static final String sBuildInfoMap = "build_info_map";
/** 系统常规配置信息 */
private static final String sSystemInfoMap = "system_info_map";
/** 手机安全配置信息 */
private static final String sSecureInfoMap = "secure_info_map";
/** 内存情况信息 */
private static final String sMemoryInfoString = "memory_info_map";
private CrashUploader mCrashUploader;
public static CrashHandler getInstance() {
if (mInstance == null) {
synchronized (CrashHandler.class) {
if (mInstance == null) {
mInstance = new CrashHandler();
}
}
}
return mInstance;
}
public void init(Context context, CrashUploader crashUploader) {
mContext = context.getApplicationContext();
mCrashUploader = crashUploader;
mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
File crashDir = mContext.getExternalFilesDir(sFileName);
if (crashDir != null){
if (!crashDir.exists()) {
crashDir.mkdirs();
}
mPath = crashDir.getAbsolutePath();
}
}
public void deleteCrashFile(){
clearInfo();
deleteDirectory(mPath);
}
/**
* 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用uncaughtException方法
*
* @param t 出现未捕获异常的线程
* @param e 未捕获的异常,有了这个ex,我们就可以得到异常信息
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
if (!handleException(e) && mUncaughtExceptionHandler != null) {
//如果用户没有处理则让系统默认的异常处理器来处理
mUncaughtExceptionHandler.uncaughtException(t, e);
}else {
killProcess();
}
}
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*
* @param ex
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "Sorry, The program is abnormal and will exit soon.", Toast.LENGTH_SHORT).show();
Looper.loop();
}
});
collectInfo(ex);
saveCrashInfoToFile();
uploadCrashMessage();
return true;
}
/**
* 退出应用
*/
private void killProcess() {
MLog.e(TAG, "killProcess");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Intent startMain = new Intent(Intent.ACTION_MAIN);
startMain.addCategory(Intent.CATEGORY_HOME);
startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(startMain);
//Process.killProcess(Process.myPid());
System.exit(1);
}
/**
* 收集设备参数信息
*
* @param ex
*/
private void collectInfo(Throwable ex){
clearInfo();
collectThrowableInfo(ex);
collectPackageInfo();
collectBuildInfo();
collectSystemInfo();
collectSecureInfo();
collectMemInfo();
mInfoMap.put(sThrowableInfoString, mThrowableInfo);
mInfoMap.put(sPackageInfoMap, mPackageInfoMap);
mInfoMap.put(sBuildInfoMap, mDeviceInfoMap);
mInfoMap.put(sSystemInfoMap, mSysInfoMap);
mInfoMap.put(sSecureInfoMap, mSecureInfoMap);
mInfoMap.put(sMemoryInfoString, mMemInfo);
}
private void clearInfo(){
mMemInfo = "";
mPackageInfoMap.clear();
mDeviceInfoMap.clear();
mSysInfoMap.clear();
mSecureInfoMap.clear();
mInfoMap.clear();
mThrowableInfo = "";
}
/**
* 保存日志文件
*/
private void saveCrashInfoToFile(){
StringBuffer mStringBuffer = infoMapToStrBuffer(mPackageInfoMap);
mStringBuffer.append(infoMapToStrBuffer(mDeviceInfoMap));
mStringBuffer.append(infoMapToStrBuffer(mSysInfoMap));
mStringBuffer.append(infoMapToStrBuffer(mSecureInfoMap));
mStringBuffer.append(mMemInfo);
mStringBuffer.append(mThrowableInfo);
String mTime = mDateFormat.format(new Date());
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
try {
String filePath = addSlash(mPath) + mTime + sFileName + sFileNameSuffix;
FileOutputStream mFileOutputStream = new FileOutputStream(filePath);
mFileOutputStream.write(mStringBuffer.toString().getBytes());
mFileOutputStream.close();
MLog.d(TAG, filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 上传崩溃信息
*/
private void uploadCrashMessage(){
new Thread(new Runnable() {
@Override
public void run() {
//http 上传服务端
//deleteCrashFile();
//成功
Looper.prepare();
mCrashUploader.uploadSuccess();
Looper.loop();
//失败
Looper.prepare();
mCrashUploader.uploadError("");
Looper.loop();
}
}).start();
}
/**
* 获取捕获异常的信息
*
* @param ex
*/
private void collectThrowableInfo(Throwable ex) {
Writer mWriter = new StringWriter();
PrintWriter mPrintWriter = new PrintWriter(mWriter);
ex.printStackTrace(mPrintWriter);
ex.printStackTrace();
Throwable throwable = ex.getCause();
while (throwable != null) {
throwable.printStackTrace(mPrintWriter);
mPrintWriter.append("\r\n");
throwable = throwable.getCause();
}
mPrintWriter.close();
mThrowableInfo = mWriter.toString();
}
/**
* 获取APP进程内存信息
*/
private void collectMemInfo() {
BufferedReader bufferedReader = null;
StringBuffer stringBuffer = new StringBuffer();
ArrayList commandLine = new ArrayList<>();
commandLine.add("dumpsys");
commandLine.add("meminfo");
commandLine.add(Integer.toString(Process.myPid()));
try {
java.lang.Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()]));
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 4 * 1024);
while (true) {
String line = bufferedReader.readLine();
if (line == null) {
break;
}
stringBuffer.append(line);
stringBuffer.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
mMemInfo = stringBuffer.toString();
}
/**
* 获取系统安全设置信息
*/
private void collectSecureInfo() {
Field[] fields = Settings.Secure.class.getFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class && field.getName().startsWith("WIFI_AP")) {
try {
String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));
if (value != null) {
mSecureInfoMap.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取系统常规设定属性
*/
private void collectSystemInfo() {
Field[] fields = Settings.System.class.getFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class) {
try {
String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));
if (value != null) {
mSysInfoMap.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 从系统属性中提取设备硬件和版本信息
*
* 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因
*
*/
private void collectBuildInfo() {
Field[] mFields = Build.class.getDeclaredFields();
for (Field field : mFields) {
try {
field.setAccessible(true);
mDeviceInfoMap.put(field.getName(), field.get("").toString());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 获取应用包参数信息
*/
private void collectPackageInfo() {
try {
PackageManager mPackageManager = mContext.getPackageManager();
PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
if (mPackageInfo != null) {
mPackageInfoMap.put("PackageName", TextUtils.isEmpty(mPackageInfo.packageName) ? "unknown" : mPackageInfo.packageName);
mPackageInfoMap.put("VersionName", TextUtils.isEmpty(mPackageInfo.versionName) ? "unknown" : mPackageInfo.versionName);
mPackageInfoMap.put("VersionCode", mPackageInfo.versionCode + "");
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
/**
* 将HashMap遍历转换成StringBuffer
*/
@NonNull
public static StringBuffer infoMapToStrBuffer(ConcurrentHashMap infoMap) {
StringBuffer mStringBuffer = new StringBuffer();
for (Map.Entry entry : infoMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
mStringBuffer.append(key + "=" + value + "\r\n");
}
return mStringBuffer;
}
private String addSlash(String pPath) {
if (!TextUtils.isEmpty(pPath)) {
if (!pPath.endsWith(File.separator)) {
pPath = pPath + File.separator;
}
}else {
pPath = File.separator;
}
return pPath;
}
private boolean deleteDirectory(String pDir){
File dirFile = new File(addSlash(pDir));
if (!dirFile.exists() || !dirFile.isDirectory()) {
return false;
}
boolean delFlag = true;
File[] files = dirFile.listFiles();
for (File file : files) {
if (file.isFile()) {
delFlag = deleteFile(file.getAbsolutePath());
if (!delFlag) {
break;
}
} else {
delFlag = deleteDirectory(file.getAbsolutePath());
if (!delFlag) {
break;
}
}
}
if (!delFlag) {
return false;
}
if (dirFile.delete()) {
return true;
} else {
return false;
}
}
private boolean deleteFile(String pFileName){
try {
File file = new File(pFileName);
if (file.isFile() && file.exists()) {
file.delete();
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
/**
* 崩溃信息上传接口回调
*/
public interface CrashUploader {
void uploadSuccess();
void uploadError(String error);
}
}
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = CrashHandler.class.getSimpleName();
private Context mContext;
private String mPath;
private static final String sFileName = "crash";
private static final String sFileNameSuffix = ".trace";
private DateFormat mDateFormat;
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
//private ExecutorService mExecutor = Executors.newCachedThreadPool();
private static CrashHandler mInstance;
private UncaughtExceptionHandler mUncaughtExceptionHandler;
private ConcurrentHashMap mInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mPackageInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mDeviceInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mSysInfoMap = new ConcurrentHashMap<>();
private ConcurrentHashMap mSecureInfoMap = new ConcurrentHashMap<>();
private String mMemInfo;
private String mThrowableInfo;
/** 异常信息 */
private static final String sThrowableInfoString = "throwable_info";
/** 应用包信息 */
private static final String sPackageInfoMap = "package_info_map";
/** 设备数据信息 */
private static final String sBuildInfoMap = "build_info_map";
/** 系统常规配置信息 */
private static final String sSystemInfoMap = "system_info_map";
/** 手机安全配置信息 */
private static final String sSecureInfoMap = "secure_info_map";
/** 内存情况信息 */
private static final String sMemoryInfoString = "memory_info_map";
private CrashUploader mCrashUploader;
public static CrashHandler getInstance() {
if (mInstance == null) {
synchronized (CrashHandler.class) {
if (mInstance == null) {
mInstance = new CrashHandler();
}
}
}
return mInstance;
}
public void init(Context context, CrashUploader crashUploader) {
mContext = context.getApplicationContext();
mCrashUploader = crashUploader;
mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
File crashDir = mContext.getExternalFilesDir(sFileName);
if (crashDir != null){
if (!crashDir.exists()) {
crashDir.mkdirs();
}
mPath = crashDir.getAbsolutePath();
}
}
public void deleteCrashFile(){
clearInfo();
deleteDirectory(mPath);
}
/**
* 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用uncaughtException方法
*
* @param t 出现未捕获异常的线程
* @param e 未捕获的异常,有了这个ex,我们就可以得到异常信息
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
if (!handleException(e) && mUncaughtExceptionHandler != null) {
//如果用户没有处理则让系统默认的异常处理器来处理
mUncaughtExceptionHandler.uncaughtException(t, e);
}else {
killProcess();
}
}
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*
* @param ex
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "Sorry, The program is abnormal and will exit soon.", Toast.LENGTH_SHORT).show();
Looper.loop();
}
});
collectInfo(ex);
saveCrashInfoToFile();
uploadCrashMessage();
return true;
}
/**
* 退出应用
*/
private void killProcess() {
MLog.e(TAG, "killProcess");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Intent startMain = new Intent(Intent.ACTION_MAIN);
startMain.addCategory(Intent.CATEGORY_HOME);
startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(startMain);
//Process.killProcess(Process.myPid());
System.exit(1);
}
/**
* 收集设备参数信息
*
* @param ex
*/
private void collectInfo(Throwable ex){
clearInfo();
collectThrowableInfo(ex);
collectPackageInfo();
collectBuildInfo();
collectSystemInfo();
collectSecureInfo();
collectMemInfo();
mInfoMap.put(sThrowableInfoString, mThrowableInfo);
mInfoMap.put(sPackageInfoMap, mPackageInfoMap);
mInfoMap.put(sBuildInfoMap, mDeviceInfoMap);
mInfoMap.put(sSystemInfoMap, mSysInfoMap);
mInfoMap.put(sSecureInfoMap, mSecureInfoMap);
mInfoMap.put(sMemoryInfoString, mMemInfo);
}
private void clearInfo(){
mMemInfo = "";
mPackageInfoMap.clear();
mDeviceInfoMap.clear();
mSysInfoMap.clear();
mSecureInfoMap.clear();
mInfoMap.clear();
mThrowableInfo = "";
}
/**
* 保存日志文件
*/
private void saveCrashInfoToFile(){
StringBuffer mStringBuffer = infoMapToStrBuffer(mPackageInfoMap);
mStringBuffer.append(infoMapToStrBuffer(mDeviceInfoMap));
mStringBuffer.append(infoMapToStrBuffer(mSysInfoMap));
mStringBuffer.append(infoMapToStrBuffer(mSecureInfoMap));
mStringBuffer.append(mMemInfo);
mStringBuffer.append(mThrowableInfo);
String mTime = mDateFormat.format(new Date());
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
try {
String filePath = addSlash(mPath) + mTime + sFileName + sFileNameSuffix;
FileOutputStream mFileOutputStream = new FileOutputStream(filePath);
mFileOutputStream.write(mStringBuffer.toString().getBytes());
mFileOutputStream.close();
MLog.d(TAG, filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 上传崩溃信息
*/
private void uploadCrashMessage(){
new Thread(new Runnable() {
@Override
public void run() {
//http 上传服务端
//deleteCrashFile();
//成功
Looper.prepare();
mCrashUploader.uploadSuccess();
Looper.loop();
//失败
Looper.prepare();
mCrashUploader.uploadError("");
Looper.loop();
}
}).start();
}
/**
* 获取捕获异常的信息
*
* @param ex
*/
private void collectThrowableInfo(Throwable ex) {
Writer mWriter = new StringWriter();
PrintWriter mPrintWriter = new PrintWriter(mWriter);
ex.printStackTrace(mPrintWriter);
ex.printStackTrace();
Throwable throwable = ex.getCause();
while (throwable != null) {
throwable.printStackTrace(mPrintWriter);
mPrintWriter.append("\r\n");
throwable = throwable.getCause();
}
mPrintWriter.close();
mThrowableInfo = mWriter.toString();
}
/**
* 获取APP进程内存信息
*/
private void collectMemInfo() {
BufferedReader bufferedReader = null;
StringBuffer stringBuffer = new StringBuffer();
ArrayList commandLine = new ArrayList<>();
commandLine.add("dumpsys");
commandLine.add("meminfo");
commandLine.add(Integer.toString(Process.myPid()));
try {
java.lang.Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()]));
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 4 * 1024);
while (true) {
String line = bufferedReader.readLine();
if (line == null) {
break;
}
stringBuffer.append(line);
stringBuffer.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
mMemInfo = stringBuffer.toString();
}
/**
* 获取系统安全设置信息
*/
private void collectSecureInfo() {
Field[] fields = Settings.Secure.class.getFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class && field.getName().startsWith("WIFI_AP")) {
try {
String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));
if (value != null) {
mSecureInfoMap.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取系统常规设定属性
*/
private void collectSystemInfo() {
Field[] fields = Settings.System.class.getFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class) {
try {
String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));
if (value != null) {
mSysInfoMap.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 从系统属性中提取设备硬件和版本信息
*
* 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因
*
*/
private void collectBuildInfo() {
Field[] mFields = Build.class.getDeclaredFields();
for (Field field : mFields) {
try {
field.setAccessible(true);
mDeviceInfoMap.put(field.getName(), field.get("").toString());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 获取应用包参数信息
*/
private void collectPackageInfo() {
try {
PackageManager mPackageManager = mContext.getPackageManager();
PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
if (mPackageInfo != null) {
mPackageInfoMap.put("PackageName", TextUtils.isEmpty(mPackageInfo.packageName) ? "unknown" : mPackageInfo.packageName);
mPackageInfoMap.put("VersionName", TextUtils.isEmpty(mPackageInfo.versionName) ? "unknown" : mPackageInfo.versionName);
mPackageInfoMap.put("VersionCode", mPackageInfo.versionCode + "");
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
/**
* 将HashMap遍历转换成StringBuffer
*/
@NonNull
public static StringBuffer infoMapToStrBuffer(ConcurrentHashMap infoMap) {
StringBuffer mStringBuffer = new StringBuffer();
for (Map.Entry entry : infoMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
mStringBuffer.append(key + "=" + value + "\r\n");
}
return mStringBuffer;
}
private String addSlash(String pPath) {
if (!TextUtils.isEmpty(pPath)) {
if (!pPath.endsWith(File.separator)) {
pPath = pPath + File.separator;
}
}else {
pPath = File.separator;
}
return pPath;
}
private boolean deleteDirectory(String pDir){
File dirFile = new File(addSlash(pDir));
if (!dirFile.exists() || !dirFile.isDirectory()) {
return false;
}
boolean delFlag = true;
File[] files = dirFile.listFiles();
for (File file : files) {
if (file.isFile()) {
delFlag = deleteFile(file.getAbsolutePath());
if (!delFlag) {
break;
}
} else {
delFlag = deleteDirectory(file.getAbsolutePath());
if (!delFlag) {
break;
}
}
}
if (!delFlag) {
return false;
}
if (dirFile.delete()) {
return true;
} else {
return false;
}
}
private boolean deleteFile(String pFileName){
try {
File file = new File(pFileName);
if (file.isFile() && file.exists()) {
file.delete();
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
/**
* 崩溃信息上传接口回调
*/
public interface CrashUploader {
void uploadSuccess();
void uploadError(String error);
}
}