崩溃日志显示Toast并输出至SD Card - 适配Android10
1、工具类
异常捕获Handler
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private Context mContext;
// 用来存储应用信息和设备信息
private Map<String, String> mInfo = new LinkedHashMap<>();
// 默认的未捕获异常处理器
private Thread.UncaughtExceptionHandler mDefaultHandler;
/**
* instance 实例
* INSTANCE_LOCK 互斥锁
*/
private static CrashHandler instance = null;
private static final Object INSTANCE_LOCK = new Object();
/**
* 获取实例
*
* @return
*/
public static CrashHandler getInstance() {
if (instance == null) {
synchronized (INSTANCE_LOCK) {
if (instance == null) {
instance = new CrashHandler();
}
}
}
return instance;
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context.getApplicationContext();
// 获取默认的未捕获异常处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置CrashHandler为默认的未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 捕获异常
*
* @param t
* @param ex
*/
@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable ex) {
// 处理异常
handleException(ex);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 结束应用
if (mDefaultHandler != null) {
mDefaultHandler.uncaughtException(t, ex);
} else {
System.exit(1);
}
}
/**
* 处理异常 - 输出 Toast 及 输出到 sd card
*
* @param ex
*/
private void handleException(Throwable ex) {
final Throwable e = ex;
// Toast提示出现异常
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
// 收集应用信息和设备信息
collectInfo();
// 保存崩溃信息到SD卡
AndroidUtils.saveErrInfoToGalleryByScopedStorage(mContext, outputErrorLogInfo(ex));
}
/**
* 错误日志
*
* @param ex
* @return
*/
private String outputErrorLogInfo(Throwable ex) {
String deviceInfo = "";
for (String key : mInfo.keySet()) {
deviceInfo += key + " : " + mInfo.get(key) + "\n";
}
return "Error Info : \n" + ex.getLocalizedMessage() + "\n\n" + "Device Info : \n" + deviceInfo;
}
/**
* 收集信息
*/
private void collectInfo() {
// 收集BuildConfig类信息
// collectClassInfo(BuildConfig.class);
// 收集Build.VERSION类信息
collectClassInfo(Build.VERSION.class);
// 收集Build类信息
collectClassInfo(Build.class);
}
/**
* 收集信息
*
* @param cls
*/
private void collectClassInfo(Class cls) {
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
mInfo.put(field.getName(), field.get(null).toString());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
崩溃日志输出至sd card
public class AndroidUtils {
public static final String TAG = "AndroidUtils";
/**
* ERROR_LOG_FILE_PATH_1 --- Android 10 及以上错误日志存储路径
* ERROR_LOG_FILE_PATH_2 --- Android 10 以下错误日志存储路径
* ERROR_LOG_FILE_NAME --- 错误日志文件名
*/
public static final String ERROR_LOG_FILE_PATH_1 = "Download/UAT Error Logs";
public static final String ERROR_LOG_FILE_PATH_2 = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "UAT Error Logs";
public static final String ERROR_LOG_FILE_NAME = "errLog";
/**
* 错误日志输出到 sdk card
* <p>
* Android 10 及以上日志路径 --- /storage/sdcard/Download/UAT Error Logs
* Android 10 以下日志路径 --- /storage/emulated/0/UAT Error Logs
*
* @param context
* @param logContent
* @return
*/
public static boolean saveErrInfoToGalleryByScopedStorage(Context context, String logContent) {
long timestamp = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(timestamp));
String fileName = ERROR_LOG_FILE_NAME + "_" + time + ".txt";
FileOutputStream fos = null;
OutputStream os = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//Android 10 及以上
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
//设置文件类型
values.put(MediaStore.Downloads.MIME_TYPE, "text/plain");
//这个方法只可在Android10的手机上执行,设置路径
values.put(MediaStore.Downloads.RELATIVE_PATH, ERROR_LOG_FILE_PATH_1);
Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
// 写入文件
Uri uri = context.getContentResolver().insert(external, values);
if (uri != null) {
os = context.getContentResolver().openOutputStream(uri);
os.write(logContent.getBytes());
os.flush();
return true;
}
} else {
//Android 10 以下
File dir = new File(ERROR_LOG_FILE_PATH_2);
if (!dir.exists()) {
dir.mkdir();
}
File file = new File(dir, fileName);
if (!file.exists()) {
file.createNewFile();
}
fos = new FileOutputStream(file);
fos.write(logContent.getBytes());
fos.flush();
return true;
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, Log.getStackTraceString(e));
} finally {
closeIO(fos);
closeIO(os);
}
return false;
}
/**
* 关闭流,普通不严重的可采用此函数关闭
*/
public static void closeIO(Closeable... closeables) {
if (null == closeables || closeables.length <= 0) {
return;
}
for (Closeable cb : closeables) {
try {
if (null == cb) {
continue;
}
cb.close();
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
}
}
2、使用
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//崩溃日志
CrashHandler.getInstance().init(this);
}
}
程序崩溃闪退之后,在文件管理器中搜索UAT Error Logs文件夹