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: 测试异常
优先级规则
- 线程专属处理器 :若线程通过
setUncaughtExceptionHandler()
设置了专属处理器,则优先调用。 - 线程组处理器 :若未设置专属处理器,且线程属于某个线程组,则调用线程组的
uncaughtException
方法。 - 全局默认处理器 :若上述均未设置,最终调用
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
)启动前完成异常处理配置。 - 默认情况下,
initOrder
为0
,设为101
可使其优先于大多数 Provider 初始化。
- Android 系统启动时,会按
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 隔离)或实现特定功能(如双进程保活)。
注意事项
-
进程生命周期:
- 独立进程的 Activity 会启动一个新的进程实例,与主进程不共享内存。
- 进程内单例、静态变量等数据在独立进程中重新初始化。
-
跨进程通信:
- 若需与主进程交互,需使用 IPC 机制 (如
Intent
、Messenger
、AIDL
、ContentProvider
)。
- 若需与主进程交互,需使用 IPC 机制 (如
-
性能开销:
- 每个独立进程会增加内存和 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);
}
}
});
总结
这样,错误就显示到了,如果你的程序整好死给你看了,工厂的测试人员,就可以把日志发给你,看了。除了显示出来,你想怎么处理就怎么处理,通过接口,传到服务器啦,或者保存到本地的文件了。传到服务器,给你手机发短信。等等。