为了方便测试,程序每次崩溃的时候,我都让他跳转新页面,把日志显示出来

1、为什么要这样做

我们公司的设备在工厂测试和生产,而我写代码的地方和工厂不在一个地方,为了方便测试。

2、Thead.setDefaultUncaughtExceptionHandler

Thread.setDefaultUncaughtExceptionHandler 是 Java 中用于设置全局默认未捕获异常处理器的方法。当线程因未捕获异常而终止时,若该线程未单独设置异常处理器,JVM 会调用此默认处理器处理异常。以下是关键点解析:

方法作用

  • 功能:为所有线程设置一个统一的未捕获异常处理逻辑。
  • 适用场景 :当某个线程未通过 Thread.setUncaughtExceptionHandler() 设置专属处理器时,默认使用此全局处理器。
  • 典型用途:集中记录日志、发送报警、清理资源或优雅终止程序。

方法签名

java 复制代码
public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
  • 参数eh 是实现了 Thread.UncaughtExceptionHandler 接口的对象。
  • 接口方法 :需实现 void uncaughtException(Thread t, Throwable e),其中:
    • t:抛出异常的线程。
    • e:未捕获的异常对象。

使用示例

java 复制代码
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
    System.err.println("全局异常捕获 - 线程 " + thread.getName() + " 崩溃:");
    throwable.printStackTrace();
});

new Thread(() -> {
    throw new RuntimeException("测试异常");
}).start();

// 输出:
// 全局异常捕获 - 线程 Thread-0 崩溃:
// java.lang.RuntimeException: 测试异常

优先级规则

  1. 线程专属处理器 :若线程通过 setUncaughtExceptionHandler() 设置了专属处理器,则优先调用。
  2. 线程组处理器 :若未设置专属处理器,且线程属于某个线程组,则调用线程组的 uncaughtException 方法。
  3. 全局默认处理器 :若上述均未设置,最终调用 setDefaultUncaughtExceptionHandler 设置的处理器。

注意事项

  • 线程终止:无论是否处理异常,抛出异常的线程终将终止。
  • 线程池中的线程 :线程池(如 ExecutorService)的任务异常通常由 Future 对象或提交任务时的 try-catch 捕获。若任务未处理异常,线程池可能使用默认处理器。
  • 避免静默忽略:务必在处理器中记录或处理异常,防止异常被"吞没",导致调试困难。

通过合理使用此方法,可提升程序的健壮性,确保未捕获异常得到妥善处理,避免意外崩溃。

3、搞一个Provider,进行初始化

xml 复制代码
<provider
    android:name=".crash.CaocInitProvider"
    android:authorities="${applicationId}.customactivityoncrashinitprovider"
    android:exported="false"
    android:initOrder="101" />

以下是针对 <provider> 元素的参数解析,结合崩溃处理库(如 CustomActivityOnCrash)的典型配置场景:


参数详解

1. android:name=".crash.CaocInitProvider"

  • 作用 :指定 ContentProvider 的实现类路径。
  • 说明
    • .crash.CaocInitProvider 表示该类位于当前应用包名下的 crash 包中。
    • 用于在应用启动时自动初始化异常处理逻辑

2. android:authorities="${applicationId}.customactivityoncrashinitprovider"

  • 作用:定义 Provider 的唯一标识符(URI Authority)。
  • 说明
    • ${applicationId} 是 Gradle 变量,替换为应用包名(如 com.example.app),确保 Authority 全局唯一。
    • 格式示例:com.example.app.customactivityoncrashinitprovider
    • 此 Authority 通常无需直接使用,仅作为 Provider 的内部标识,避免与其他应用冲突。

3. android:exported="false"

  • 作用:控制是否允许其他应用访问此 Provider。
  • 说明
    • false 表示该 Provider 仅限当前应用内部使用,禁止外部应用通过 URI 访问。
    • 崩溃处理库的 Provider 通常无需对外暴露,设为 false 是安全最佳实践。

4. android:initOrder="101"

  • 作用:指定 Provider 的初始化顺序(数值越大,优先级越高)。
  • 说明
    • Android 系统启动时,会按 initOrder 从高到低依次初始化所有 Provider。
    • 崩溃处理库需尽早初始化 (如设为 101),确保在应用其他组件(如 Activity)启动前完成异常处理配置。
    • 默认情况下,initOrder0,设为 101 可使其优先于大多数 Provider 初始化。

4、搞一个Activity,进行错误显示

ini 复制代码
<activity
    android:theme="@style/Theme.BullHeadController"
    android:name=".crash.DefaultErrorActivity"

    android:process=":error_activity" />

在 Android 中,android:process=":error_activity"<activity> 标签的一个属性,用于指定该 Activity 运行在独立的进程中。以下是详细解析:


作用

  • 隔离进程 :该 Activity 会在名为 :error_activity独立进程中运行,与默认的主进程(即应用包名对应的进程)分离。
  • 场景用途 :常用于崩溃处理高稳定性需求的界面(如错误弹窗),确保主进程崩溃时,该 Activity 仍能存活并显示。

参数格式

  • 进程命名规则
    • : 开头 (如 :error_activity):表示这是一个私有进程,仅当前应用可访问。
    • 以小写字母开头 (如 com.example.error):表示这是一个全局进程 ,其他应用可通过权限访问(需声明 android:exported)。

典型使用场景

1. 崩溃处理(Crash Handling)

  • 目标:当主进程因未捕获异常崩溃时,通过独立进程的 Activity 显示错误界面。
  • 优势
    • 主进程崩溃不会影响独立进程的 Activity。
    • 用户仍能看到友好的错误提示,而非直接退出应用。

2. 资源隔离

  • 目标:将高内存消耗或高风险的 Activity 隔离到独立进程,避免主进程被系统杀死。
  • 示例:游戏中的渲染界面、视频播放器等。

3. 多进程架构

  • 目标:利用多进程提升性能(如 WebView 隔离)或实现特定功能(如双进程保活)。

注意事项

  1. 进程生命周期

    • 独立进程的 Activity 会启动一个新的进程实例,与主进程不共享内存
    • 进程内单例、静态变量等数据在独立进程中重新初始化
  2. 跨进程通信

    • 若需与主进程交互,需使用 IPC 机制 (如 IntentMessengerAIDLContentProvider)。
  3. 性能开销

    • 每个独立进程会增加内存和 CPU 开销,过度使用可能导致应用整体性能下降。

5、开干,在ContentProvider初始化的时候,设置全局异常捕获

kotlin 复制代码
public class CaocInitProvider extends ContentProvider {

    public boolean onCreate() {
        CustomActivityOnCrash.install(getContext());
        return false;
    }
 }
kotlin 复制代码
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread thread, final Throwable throwable) {
        if (config.isEnabled()) {
            Log.e(TAG, "App has crashed, executing CustomActivityOnCrash's UncaughtExceptionHandler", throwable);

            if (hasCrashedInTheLastSeconds(application)) {
                Log.e(TAG, "App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?", throwable);
                if (oldHandler != null) {
                    oldHandler.uncaughtException(thread, throwable);
                    return;
                }
            } else {
                setLastCrashTimestamp(application, new Date().getTime());

                Class<? extends Activity> errorActivityClass = config.getErrorActivityClass();

                if (errorActivityClass == null) {
                    errorActivityClass = guessErrorActivityClass(application);
                }

                if (isStackTraceLikelyConflictive(throwable, errorActivityClass)) {
                    Log.e(TAG, "Your application class or your error activity have crashed, the custom activity will not be launched!");
                    if (oldHandler != null) {
                        oldHandler.uncaughtException(thread, throwable);
                        return;
                    }
                } else if (config.getBackgroundMode() == CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM || !isInBackground) {

                    final Intent intent = new Intent(application, errorActivityClass);
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    throwable.printStackTrace(pw);
                    String stackTraceString = sw.toString();

                    //Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
                    //The limit is 1MB on Android but some devices seem to have it lower.
                    //See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html
                    //And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171
                    if (stackTraceString.length() > MAX_STACK_TRACE_SIZE) {
                        String disclaimer = " [stack trace too large]";
                        stackTraceString = stackTraceString.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer;
                    }
                    intent.putExtra(EXTRA_STACK_TRACE, stackTraceString);

                    if (config.isTrackActivities()) {
                        String activityLogString = "";
                        while (!activityLog.isEmpty()) {
                            activityLogString += activityLog.poll();
                        }
                        intent.putExtra(EXTRA_ACTIVITY_LOG, activityLogString);
                    }

                    if (config.isShowRestartButton() && config.getRestartActivityClass() == null) {
                        //We can set the restartActivityClass because the app will terminate right now,
                        //and when relaunched, will be null again by default.
                        config.setRestartActivityClass(guessRestartActivityClass(application));
                    }

                    intent.putExtra(EXTRA_CONFIG, config);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                    if (config.getEventListener() != null) {
                        config.getEventListener().onLaunchErrorActivity();
                    }
                    application.startActivity(intent);
                } else if (config.getBackgroundMode() == CaocConfig.BACKGROUND_MODE_CRASH) {
                    if (oldHandler != null) {
                        oldHandler.uncaughtException(thread, throwable);
                        return;
                    }
                    //If it is null (should not be), we let it continue and kill the process or it will be stuck
                }
                //Else (BACKGROUND_MODE_SILENT): do nothing and let the following code kill the process
            }
            final Activity lastActivity = lastActivityCreated.get();
            if (lastActivity != null) {
                //We finish the activity, this solves a bug which causes infinite recursion.
                //See: https://github.com/ACRA/acra/issues/42
                lastActivity.finish();
                lastActivityCreated.clear();
            }
            killCurrentProcess();
        } else if (oldHandler != null) {
            oldHandler.uncaughtException(thread, throwable);
        }
    }
});

总结

这样,错误就显示到了,如果你的程序整好死给你看了,工厂的测试人员,就可以把日志发给你,看了。除了显示出来,你想怎么处理就怎么处理,通过接口,传到服务器啦,或者保存到本地的文件了。传到服务器,给你手机发短信。等等。

相关推荐
恋猫de小郭32 分钟前
Flutter 3.38 发布,快来看看有什么更新吧
android·前端·flutter
百锦再6 小时前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
会跑的兔子7 小时前
Android 16 Kotlin协程 第二部分
android·windows·kotlin
键来大师7 小时前
Android15 RK3588 修改默认不锁屏不休眠
android·java·framework·rk3588
江上清风山间明月10 小时前
Android 系统超级实用的分析调试命令
android·内存·调试·dumpsys
百锦再10 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang
用户693717500138414 小时前
Kotlin 协程基础入门系列:从概念到实战
android·后端·kotlin
SHEN_ZIYUAN14 小时前
Android 主线程性能优化实战:从 90% 降至 13%
android·cpu优化
曹绍华14 小时前
android 线程loop
android·java·开发语言
雨白14 小时前
Hilt 入门指南:从 DI 原理到核心用法
android·android jetpack