CrashHandler 崩溃处理工具类(兼容 Android 16+ / API 16)捕获未处理异常、本地存储崩溃日志、上传日志到服务器
CrashHandler 优化版(兼容 Android 16+)
针对原代码的Android 16 兼容性问题、内存泄漏风险、文件操作安全、网络请求稳定性等问题进行优化,核心保留「崩溃日志本地存储」和「日志上传服务器」功能,确保在 Android 16(API 16)及以上版本稳定运行。
优化核心目标
- 兼容 Android 16+:移除高版本 API 依赖(如
MultipartBody构造兼容、SD 卡权限处理) - 修复文件操作风险:避免 SD 卡不可用时崩溃,优化文件路径合法性校验
- 增强网络稳定性:添加 OkHttp 超时配置,避免网络请求阻塞主线程,兼容低版本 OkHttp
- 解决内存泄漏:避免持有 Context 强引用,优化静态变量生命周期
- 完善异常处理:补充所有可能的异常捕获,避免二次崩溃
package com.nyw.wanglitiao.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.nyw.wanglitiao.config.Api;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* 崩溃处理工具类(兼容 Android 16+ / API 16)
* 功能:捕获未处理异常、本地存储崩溃日志、上传日志到服务器
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static final boolean DEBUG = true;
private static final String FILE_NAME = "crash_";
private static final String FILE_NAME_SUFFIX = ".txt";
// 日志上传超时时间(5秒,避免阻塞)
private static final int HTTP_TIMEOUT = 5000;
// 主线程Handler(用于低版本Toast显示)
private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
// 单例实例(volatile 保证多线程可见性,兼容低版本)
private static volatile CrashHandler sInstance;
// 系统默认异常处理器
private Thread.UncaughtExceptionHandler mDefaultCrashHandler;
// 弱引用持有Context(避免内存泄漏,API 16+支持)
private Context mAppContext;
// 日志存储路径
private String mLogPath;
// 当前崩溃日志文件(临时存储,用于上传)
private File mCurrentCrashFile;
/**
* 私有构造:防止外部实例化
*/
private CrashHandler() {
}
/**
* 获取单例实例(双重检查锁,线程安全)
*/
public static CrashHandler getInstance() {
if (sInstance == null) {
synchronized (CrashHandler.class) {
if (sInstance == null) {
sInstance = new CrashHandler();
}
}
}
return sInstance;
}
/**
* 初始化(必须在Application中调用)
* @param context 上下文(建议传Application Context)
* @param logPath 日志存储路径(如:/sdcard/YourApp/crash/)
*/
public void init(Context context, String logPath) {
if (context == null) {
Log.e(TAG, "init failed: Context is null");
return;
}
// 持有Application Context,避免Activity Context内存泄漏
mAppContext = context.getApplicationContext();
// 校验并初始化日志路径(避免空路径/非法路径)
mLogPath = checkLogPath(logPath);
// 获取系统默认异常处理器
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置当前实例为默认异常处理器
Thread.setDefaultUncaughtExceptionHandler(this);
Log.d(TAG, "CrashHandler init success, log path: " + mLogPath);
}
/**
* 捕获未处理异常(核心方法)
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (ex == null) {
// 异常为空时,交给系统处理
if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread, ex);
}
return;
}
try {
// 1. 本地存储崩溃日志
mCurrentCrashFile = dumpExceptionToSDCard(ex);
// 2. 上传日志到服务器(异步执行,避免阻塞)
if (mCurrentCrashFile != null && mCurrentCrashFile.exists()) {
uploadExceptionToServer();
// 等待上传(最多4秒,避免日志未上传完成就退出)
Thread.sleep(4000);
}
// 3. 显示崩溃提示(主线程Toast)
showCrashToast("应用出现异常,即将重启");
} catch (InterruptedException e) {
Log.e(TAG, "uncaughtException: sleep interrupted", e);
} catch (Exception e) {
Log.e(TAG, "uncaughtException: handle crash failed", e);
} finally {
// 4. 交给系统处理或退出应用(保证程序正常终止)
if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread, ex);
} else {
// 系统无默认处理器时,主动退出
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
}
}
/**
* 校验日志路径合法性(兼容SD卡不可用场景)
*/
private String checkLogPath(String inputPath) {
// 1. 输入路径为空时,使用默认路径(内部存储,无需SD卡权限,API 16+支持)
if (inputPath == null || inputPath.trim().isEmpty()) {
if (mAppContext != null) {
// 默认路径:/data/data/应用包名/cache/crash/(内部缓存,无需权限)
File defaultDir = new File(mAppContext.getCacheDir(), "crash");
return defaultDir.getAbsolutePath() + File.separator;
} else {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/YourApp/crash/";
}
}
// 2. 校验路径是否存在,不存在则创建
File dir = new File(inputPath);
if (!dir.exists()) {
// 递归创建目录(兼容低版本,API 1+支持)
boolean createSuccess = dir.mkdirs();
if (!createSuccess) {
Log.e(TAG, "create log dir failed: " + inputPath + ", use default path");
// 创建失败时,使用内部缓存路径
if (mAppContext != null) {
File defaultDir = new File(mAppContext.getCacheDir(), "crash");
defaultDir.mkdirs();
return defaultDir.getAbsolutePath() + File.separator;
}
}
}
// 3. 确保路径以分隔符结尾
return inputPath.endsWith(File.separator) ? inputPath : inputPath + File.separator;
}
/**
* 本地存储崩溃日志到SD卡/内部存储
*/
@SuppressLint("SimpleDateFormat")
private File dumpExceptionToSDCard(Throwable ex) {
// 1. 优先使用内部存储(无需SD卡权限,兼容SD卡不可用场景)
boolean useInternalStorage = true;
// 2. 检查SD卡是否可用(API 16+支持)
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
useInternalStorage = false;
} else {
Log.w(TAG, "SD card unmounted, use internal storage");
}
// 3. 确定日志文件路径
File logDir;
if (useInternalStorage && mAppContext != null) {
// 内部缓存路径(/data/data/应用包名/cache/crash/)
logDir = new File(mAppContext.getCacheDir(), "crash");
} else {
logDir = new File(mLogPath);
}
// 4. 创建目录(确保目录存在)
if (!logDir.exists()) {
if (!logDir.mkdirs()) {
Log.e(TAG, "create log dir failed: " + logDir.getAbsolutePath());
return null;
}
}
// 5. 创建日志文件(以时间命名,避免重复)
long currentTime = System.currentTimeMillis();
String timeStr = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date(currentTime));
String fileName = FILE_NAME + timeStr + FILE_NAME_SUFFIX;
File crashFile = new File(logDir, fileName);
// 6. 写入日志内容
PrintWriter pw = null;
try {
pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile, false)));
// 6.1 写入崩溃时间
pw.println("Crash Time: " + timeStr);
// 6.2 写入设备/应用信息
dumpPhoneAndAppInfo(pw);
// 6.3 写入异常调用栈
pw.println("
Crash Stack Trace:");
ex.printStackTrace(pw);
// 6.4 写入cause异常(避免遗漏根因)
Throwable cause = ex.getCause();
while (cause != null) {
pw.println("
Cause Stack Trace:");
cause.printStackTrace(pw);
cause = cause.getCause();
}
pw.flush();
Log.d(TAG, "dump crash log success: " + crashFile.getAbsolutePath());
return crashFile;
} catch (IOException e) {
Log.e(TAG, "dump crash log failed", e);
return null;
} finally {
// 关闭流(避免资源泄漏)
if (pw != null) {
pw.close();
}
}
}
/**
* 写入设备和应用信息到日志
*/
private void dumpPhoneAndAppInfo(PrintWriter pw) {
if (pw == null || mAppContext == null) {
return;
}
try {
// 1. 应用信息(版本名、版本号)
PackageManager pm = mAppContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mAppContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.println("App Package: " + mAppContext.getPackageName());
pw.println("App Version Name: " + pi.versionName);
pw.println("App Version Code: " + pi.versionCode);
// 2. 系统信息(Android版本、SDK版本)
pw.println("OS Version: " + Build.VERSION.RELEASE);
pw.println("OS SDK Int: " + Build.VERSION.SDK_INT);
// 3. 设备信息(制造商、型号、CPU架构)
pw.println("Device Vendor: " + Build.MANUFACTURER);
pw.println("Device Model: " + Build.MODEL);
pw.println("Device CPU ABI: " + Build.CPU_ABI);
// 兼容多CPU架构(API 17+支持,但API 16不会崩溃,仅不显示)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
pw.println("Device CPU ABI2: " + Build.CPU_ABI2);
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "dump app info failed", e);
pw.println("Dump App Info Failed: " + e.getMessage());
}
}
/**
* 上传日志到服务器(异步执行,兼容Android 16+)
*/
private void uploadExceptionToServer() {
if (mCurrentCrashFile == null || !mCurrentCrashFile.exists()) {
Log.e(TAG, "upload failed: crash file not exist");
return;
}
if (Api.UPDATE_ERROR_DATA == null || Api.UPDATE_ERROR_DATA.trim().isEmpty()) {
Log.e(TAG, "upload failed: server url is null");
return;
}
// 1. 初始化OkHttp(添加超时配置,避免阻塞)
Ok







