You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android实现异常捕获并API上报:UncaughtExceptionHandler问题咨询

我之前在做Android异常上报的时候也遇到过完全一样的问题,给你分享几个经过实践验证的方案,应该能解决你的痛点:

方案一:结合全局异常处理器 + 手动上报工具(最稳妥的基础方案)

这个方案既能覆盖未捕获的全局异常,又能处理try-catch里的异常,同时解决和现有HTTP请求的冲突:

步骤1:实现全局UncaughtExceptionHandler

核心思路是不直接在崩溃时发起网络请求(避免应用退出前网络请求不稳定,或者和现有请求冲突),而是先把异常信息缓存到本地,等下次应用启动时再上报。同时要保留系统默认的异常处理器,确保应用能正常崩溃退出。

代码示例:

public class GlobalCrashHandler implements Thread.UncaughtExceptionHandler {
    private final Thread.UncaughtExceptionHandler defaultHandler;
    private final Context appContext;

    public GlobalCrashHandler(Context context) {
        this.appContext = context.getApplicationContext();
        this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
    }

    @Override
    public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
        // 1. 收集异常信息(堆栈、设备型号、系统版本等)
        String crashDetails = collectCrashInfo(throwable);
        // 2. 保存到本地文件(内部存储,避免权限问题)
        saveCrashToLocal(crashDetails);
        // 3. 交给系统默认处理器处理,保证应用正常崩溃
        if (defaultHandler != null) {
            defaultHandler.uncaughtException(thread, throwable);
        } else {
            Process.killProcess(Process.myPid());
            System.exit(1);
        }
    }

    private String collectCrashInfo(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        throwable.printStackTrace(pw);
        // 遍历所有cause,收集完整堆栈
        Throwable cause = throwable.getCause();
        while (cause != null) {
            cause.printStackTrace(pw);
            cause = cause.getCause();
        }
        pw.close();
        // 可以在这里添加设备信息,比如Build.MODEL、Build.VERSION.RELEASE等
        return "设备型号:" + Build.MODEL + "\n系统版本:" + Build.VERSION.RELEASE + "\n异常堆栈:\n" + sw.toString();
    }

    private void saveCrashToLocal(String crashDetails) {
        File crashFile = new File(appContext.getFilesDir(), "pending_crash.log");
        try (FileWriter writer = new FileWriter(crashFile, true)) {
            writer.write("===== " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " =====\n");
            writer.write(crashDetails + "\n\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

步骤2:实现通用异常上报工具类

这个工具类负责发起HTTP POST请求,使用独立的网络客户端(不要复用应用内全局的OkHttpClient,避免拦截器、Cookie等配置冲突),并且在后台线程执行上报:

public class ExceptionReporter {
    private static final String REPORT_API_URL = "你的上报API端点";
    // 独立的OkHttpClient,避免和应用内其他请求冲突
    private static final OkHttpClient REPORT_CLIENT = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .build();

    // 上报异常(支持手动调用和全局崩溃后启动上报)
    public static void reportException(String exceptionDetails) {
        // 用协程或者线程池执行后台任务
        CoroutineScope(Dispatchers.IO).launch {
            try {
                RequestBody body = RequestBody.create(
                        MediaType.parse("text/plain; charset=utf-8"),
                        exceptionDetails
                );
                Request request = new Request.Builder()
                        .url(REPORT_API_URL)
                        .post(body)
                        .build();
                Response response = REPORT_CLIENT.newCall(request).execute();
                if (response.isSuccessful()) {
                    // 上报成功,删除本地缓存的异常文件
                    deletePendingCrashFile();
                } else {
                    // 上报失败,保留文件下次再试
                    Log.e("ExceptionReporter", "上报失败,状态码:" + response.code());
                }
            } catch (IOException e) {
                e.printStackTrace();
                // 网络异常,保留文件下次再试
            }
        }
    }

    // 手动上报单个异常(用于try-catch块)
    public static void reportException(Exception e) {
        String details = collectExceptionDetails(e);
        reportException(details);
    }

    private static String collectExceptionDetails(Exception e) {
        // 和collectCrashInfo逻辑类似,收集单个异常的信息
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        pw.close();
        return "手动捕获异常:\n" + sw.toString();
    }

    private static void deletePendingCrashFile() {
        File crashFile = new File(App.getInstance().getFilesDir(), "pending_crash.log");
        if (crashFile.exists()) {
            crashFile.delete();
        }
    }
}

步骤3:注册处理器并启动时检查上报

在自定义Application的onCreate方法里注册全局处理器,同时检查本地是否有未上报的异常:

public class App extends Application {
    private static App instance;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        // 注册全局异常处理器
        Thread.setDefaultUncaughtExceptionHandler(new GlobalCrashHandler(this));
        // 检查并上报上次崩溃的异常
        checkPendingCrashReport();
    }

    private void checkPendingCrashReport() {
        File crashFile = new File(getFilesDir(), "pending_crash.log");
        if (crashFile.exists() && crashFile.length() > 0) {
            try (BufferedReader reader = new BufferedReader(new FileReader(crashFile))) {
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                ExceptionReporter.reportException(sb.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static App getInstance() {
        return instance;
    }
}

步骤4:在try-catch块中手动上报

对于现有代码里的try-catch,只需要在catch块里添加一行代码:

try {
    // 你的业务代码,比如发起HTTP请求
} catch (IOException e) {
    // 原来的异常处理逻辑
    e.printStackTrace();
    // 新增:上报异常
    ExceptionReporter.reportException(e);
}
方案二:用AOP自动拦截所有try-catch异常(减少代码侵入)

如果你的项目里try-catch块很多,不想手动修改每一处,可以用AspectJ实现切面编程,自动拦截所有catch块中的异常并上报,完全不需要修改现有业务代码。

步骤1:添加AspectJ依赖

在项目根目录的build.gradle中添加:

buildscript {
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.9.9.1'
    }
}

在模块的build.gradle中添加:

plugins {
    id 'com.android.application'
}

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

android {
    // 你的项目配置
}

dependencies {
    implementation 'org.aspectj:aspectjrt:1.9.9.1'
}

// 配置AspectJ编译流程
final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("跳过非Debug构建:${variant.buildType.name}")
        return
    }

    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = [
                "-showWeaveInfo",
                "-1.8",
                "-inpath", javaCompile.destinationDir.toString(),
                "-aspectpath", javaCompile.classpath.asPath,
                "-d", javaCompile.destinationDir.toString(),
                "-classpath", javaCompile.classpath.asPath,
                "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
        ]
        log.debug("AspectJ编译参数:${Arrays.toString(args)}")

        MessageHandler handler = new MessageHandler(true)
        new Main().run(args, handler)
        // 打印编译日志
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT, IMessage.ERROR, IMessage.FAIL:
                    log.error(message.message, message.thrown)
                    break
                case IMessage.WARNING:
                    log.warn(message.message, message.thrown)
                    break
                case IMessage.INFO:
                    log.info(message.message, message.thrown)
                    break
                case IMessage.DEBUG:
                    log.debug(message.message, message.thrown)
                    break
            }
        }
    }
}

步骤2:编写切面类

@Aspect
public class ExceptionCatchAspect {

    // 定义切点:拦截所有包下的Exception捕获
    @Pointcut("handler(java.lang.Exception) && within(com.your.package..*)")
    public void allExceptionCatch() {}

    // 切点触发后执行上报
    @AfterThrowing(pointcut = "allExceptionCatch()", throwing = "e")
    public void afterExceptionCaught(Exception e) {
        ExceptionReporter.reportException(e);
    }
}

这样所有try-catch块捕获的Exception都会自动调用上报方法,完全不需要手动修改业务代码。

方案三:补充说明:解决HTTP请求冲突的关键

不管用哪种方案,要避免和现有HTTP请求冲突,核心注意两点:

  1. 使用独立的网络客户端:上报请求不要复用应用内全局的OkHttpClient(尤其是带有自定义拦截器、CookieJar、证书配置的),单独创建一个极简的客户端,避免互相干扰。
  2. 在后台线程执行上报:永远不要在主线程发起上报请求,用协程、线程池或者IntentService(不推荐)执行,避免阻塞主线程或者ANR。

内容的提问来源于stack exchange,提问作者wap

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:06:23