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

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);
        }
    }
});

总结

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

相关推荐
鸿蒙布道师1 分钟前
鸿蒙NEXT开发正则工具类RegexUtil(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
Anger重名了28 分钟前
🌟 一篇文章搞懂Kotlin协程:比线程更轻量的并发神器
android
缘来的精彩32 分钟前
adb常用的20个命令
android·adb·kotlin
tangweiguo030519871 小时前
Android kotlin通知功能完整实现指南:从基础到高级功能
android·kotlin
KimLiu1 小时前
适合Android开发者的Flutter学习指南 : 一、搭建Flutter环境
android·前端·flutter
我最厉害。,。1 小时前
PHP 反序列化&原生类 TIPS&字符串逃逸&CVE 绕过漏洞&属性类型特征
android·开发语言·php
二流小码农2 小时前
鸿蒙开发:如何更新对象数组
android·ios·harmonyos
Billy_Zuo2 小时前
Android Studio中创建第一个Flutter项目
android·flutter·android studio
RabbitYao2 小时前
Google TextToSpeech apk 添加离线语音包再重新编译
android
w23617346014 小时前
Android四大核心组件
android·四大组件