欢迎光临
我们一直在努力

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(\”\\nCrash Stack Trace:\”);
    ex.printStackTrace(pw);
    // 6.4 写入cause异常(避免遗漏根因)
    Throwable cause = ex.getCause();
    while (cause != null) {
    pw.println(\”\\nCause 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

    赞(0)
    未经允许不得转载:171主机测评 » CrashHandler 崩溃处理工具类(兼容 Android 16+ / API 16)捕获未处理异常、本地存储崩溃日志、上传日志到服务器
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址