常见异常:
- 空指针异常(NullPointerException)
- 非法状态异常(IllegalStateException)
- 索引越界异常(IndexOutOfBoundsException)
- 不合法的参数异常(IllegalArgumentException)
- 内存溢出错误(OutOfMemoryError)
自定义异常处理器:
//设置全局捕获的异常处理器
Thread.setDefaultUncaughtExceptionHandler(new MyCrashHandler())
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
setupGlobalExceptionHandler();
}
private void setupGlobalExceptionHandler() {
// 1. 获取系统默认的处理器(备用)
final Thread.UncaughtExceptionHandler defaultHandler =
Thread.getDefaultUncaughtExceptionHandler();
// 2. 设置自定义的全局处理器
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
// 3. 收集崩溃信息
String crashInfo = collectCrashInfo(thread, ex);
// 4. 执行紧急操作(在子线程中进行耗时操作)
new Thread(() -> {
// 4.1 保存崩溃信息到本地文件
saveCrashToLocal(crashInfo);
// 4.2 可选:尝试上传到服务器(网络操作需谨慎)
// uploadCrashToServer(crashInfo);
// 5. 所有操作完成后,交给系统默认处理(结束进程)
// 也可以在此处直接杀死进程:android.os.Process.killProcess(android.os.Process.myPid());
defaultHandler.uncaughtException(thread, ex);
}).start();
// 6. 为文件保存等操作争取一点时间(不推荐在主线程等待太久)
try {
Thread.sleep(2000); // 等待2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
private String collectCrashInfo(Thread thread, Throwable ex) {
StringBuilder sb = new StringBuilder();
// 收集时间
sb.append("Crash Time: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date())).append("\n\n");
// 收集设备信息
sb.append("Device Info:\n");
sb.append("Model: ").append(Build.MODEL).append("\n");
sb.append("Android SDK: ").append(Build.VERSION.SDK_INT).append("\n");
sb.append("App Version: ").append(BuildConfig.VERSION_NAME).append("\n\n");
// 收集异常堆栈
sb.append("Stack Trace:\n");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
sb.append(sw.toString());
return sb.toString();
}
private void saveCrashToLocal(String crashInfo) {
// 将崩溃信息保存到应用私有目录
String fileName = "crash_" + System.currentTimeMillis() + ".log";
File crashFile = new File(getExternalFilesDir("crashes"), fileName);
try (FileOutputStream fos = new FileOutputStream(crashFile)) {
fos.write(crashInfo.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
设计一个异常兜底方案,需要在应用崩溃前执行清理操作并引导用户。这个方案的核心思路是:通过全局异常处理器捕获崩溃 → 在新进程中执行兜底操作 → 提供用户友好的恢复界面。
下图展示了完整的兜底方案架构和工作流程:
flowchart TD
A[应用发生未捕获异常] --> B[全局异常处理器被触发]
B --> C{决策: 执行兜底策略}
C --> D[启动独立进程的<br>兜底Activity]
C --> E[在子线程中执行<br>紧急清理操作]
D --> F[显示用户友好的崩溃恢复界面]
E --> G[选择性清理<br>缓存/临时文件]
F --> H{用户选择操作}
G --> I[记录清理日志]
H --> J[重启应用]
H --> K[检查更新]
H --> L[提交反馈]
H --> M[退出应用]
J --> N[结束原进程<br>启动新进程]
K --> O[跳转应用商店]
subgraph P [独立进程环境]
D
F
H
J
K
L
M
end
🛠️ 核心实现方案
1. 增强全局异常处理器
在Application中设置异常处理器,执行紧急清理并启动兜底Activity:
public class SafetyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
setupSafetyNet();
}
private void setupSafetyNet() {
final Thread.UncaughtExceptionHandler defaultHandler =
Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
// 1. 收集崩溃信息
String crashInfo = collectCrashInfo(ex);
saveCrashLog(crashInfo);
// 2. 根据异常类型执行不同的清理策略
executeSafetyCleaning(ex);
// 3. 在新进程中启动兜底Activity
Intent intent = new Intent(getApplicationContext(), SafetyNetActivity.class);
intent.putExtra("crash_info", crashInfo);
intent.putExtra("exception_type", ex.getClass().getSimpleName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// 4. 设置新进程启动标记
intent.putExtra("from_safety_net", true);
// 5. 尝试启动,如果失败则交给系统处理
try {
startActivity(intent);
} catch (Exception e) {
if (defaultHandler != null) {
defaultHandler.uncaughtException(thread, ex);
return;
}
}
// 6. 给新Activity启动时间后结束进程
try {
Thread.sleep(1500);
} catch (InterruptedException ignored) {}
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
});
}
private void executeSafetyCleaning(Throwable ex) {
new Thread(() -> {
try {
// A. 内存相关异常:清理缓存
if (ex instanceof OutOfMemoryError ||
ex instanceof MemoryError) {
clearAppCache();
}
// B. 存储相关异常:清理临时文件
if (ex instanceof IOException ||
ex.getMessage() != null &&
ex.getMessage().contains("storage")) {
clearTempFiles();
}
// C. 网络相关异常:清理网络缓存
if (ex instanceof SocketTimeoutException ||
ex instanceof IOException &&
ex.getMessage() != null &&
(ex.getMessage().contains("Network") ||
ex.getMessage().contains("socket"))) {
clearNetworkCache();
}
// D. 总是执行的轻量级清理
clearPartialCache();
} catch (Exception e) {
// 清理过程中的异常要静默处理
}
}).start();
}
}
2. 实现兜底恢复Activity
在独立进程中运行的恢复界面,提供用户操作选项:
<!-- AndroidManifest.xml 配置 -->
<activity
android:name=".SafetyNetActivity"
android:process=":safety_net"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity=".safety_net"
android:exported="false" />
public class SafetyNetActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_safety_net);
// 获取崩溃信息
String crashType = getIntent().getStringExtra("exception_type");
String crashInfo = getIntent().getStringExtra("crash_info");
setupUI(crashType);
analyzeAndSuggest(crashType, crashInfo);
}
private void setupUI(String crashType) {
TextView title = findViewById(R.id.tv_title);
TextView suggestion = findViewById(R.id.tv_suggestion);
Button btnRestart = findViewById(R.id.btn_restart);
Button btnUpgrade = findViewById(R.id.btn_upgrade);
Button btnFeedback = findViewById(R.id.btn_feedback);
Button btnExit = findViewById(R.id.btn_exit);
// 根据崩溃类型显示不同提示
String[] suggestions = getSuggestionsForCrash(crashType);
title.setText(suggestions[0]);
suggestion.setText(suggestions[1]);
btnRestart.setOnClickListener(v -> restartApp());
btnUpgrade.setOnClickListener(v -> checkAndUpgrade());
btnFeedback.setOnClickListener(v -> submitFeedback());
btnExit.setOnClickListener(v -> exitApp());
}
private String[] getSuggestionsForCrash(String crashType) {
Map<String, String[]> suggestionMap = new HashMap<>();
suggestionMap.put("OutOfMemoryError", new String[]{
"内存不足",
"已自动清理缓存,建议重启应用。如果问题持续,请检查设备存储空间。"
});
suggestionMap.put("NullPointerException", new String[]{
"程序异常",
"应用遇到了意外错误,重启可能解决问题。"
});
// ... 其他异常类型的提示
return suggestionMap.getOrDefault(crashType, new String[]{
"应用程序遇到问题",
"抱歉给您带来不便,请尝试重启应用。"
});
}
private void restartApp() {
// 重启应用
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
// 结束当前进程
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private void checkAndUpgrade() {
// 检查更新逻辑
if (shouldForceUpgrade()) {
showForceUpgradeDialog();
} else {
// 引导到应用商店
try {
Uri uri = Uri.parse("market://details?id=" + getPackageName());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
} catch (Exception e) {
// 跳转失败,显示网页版应用商店
Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=" + getPackageName());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
}
}
}
3. 实现智能清理策略
public class SafetyCleaner {
public static void clearAppCache(Context context) {
try {
// 1. 清理内部缓存
File cacheDir = context.getCacheDir();
if (cacheDir != null && cacheDir.isDirectory()) {
deleteDir(cacheDir);
}
// 2. 清理外部缓存
File externalCacheDir = context.getExternalCacheDir();
if (externalCacheDir != null && externalCacheDir.isDirectory()) {
deleteDir(externalCacheDir);
}
// 3. 清理WebView缓存
clearWebViewCache(context);
// 4. 清理图片加载框架缓存
clearImageLoaderCache(context);
} catch (Exception e) {
// 静默处理清理异常
}
}
public static void clearPartialCache() {
// 只清理过期或过大的缓存文件
File cacheDir = context.getCacheDir();
if (cacheDir.exists() && cacheDir.isDirectory()) {
File[] files = cacheDir.listFiles();
if (files != null) {
long now = System.currentTimeMillis();
for (File file : files) {
// 删除7天前的缓存文件
if (now - file.lastModified() > 7 * 24 * 60 * 60 * 1000L) {
file.delete();
}
// 删除超过10MB的大文件
if (file.length() > 10 * 1024 * 1024) {
file.delete();
}
}
}
}
}
private static void clearWebViewCache(Context context) {
// 清理WebView缓存
try {
context.deleteDatabase("webview.db");
context.deleteDatabase("webviewCache.db");
} catch (Exception e) {
// 忽略异常
}
}
}
📊 兜底策略配置表
| 异常类型 | 触发条件 | 清理操作 | 用户提示 | 自动操作 |
|---|---|---|---|---|
| 内存不足 | OutOfMemoryError | 清除所有缓存、临时文件 | "内存不足,已清理缓存" | 自动重启 |
| 存储异常 | IOException (含storage关键字) | 清除临时文件、日志文件 | "存储空间可能不足" | 引导清理存储 |
| 网络超时 | SocketTimeoutException | 清除网络缓存 | "网络连接不稳定" | 重试或离线模式 |
| 空指针 | NullPointerException | 轻量级清理 | "程序异常,建议重启" | 无 |
| 版本不兼容 | NoSuchMethodError 等 | 无 | "检测到版本兼容问题,请更新" | 强制升级提示 |
⚠️ 重要注意事项
-
进程隔离:兜底Activity必须在独立进程(
android:process=":safety_net")中运行,确保即使主进程崩溃也能正常显示。 -
权限处理:Android 11+ 对文件访问有限制,清理缓存时应使用
Context.getCacheDir()等API,而非直接访问绝对路径。 -
数据安全:
// 避免清理用户重要数据 public static void safeClean(Context context) { // 不清理以下目录: // - getFilesDir() 用户文件 // - getDatabasePath() 数据库 // - SharedPreferences // - 外部私有目录 } -
性能影响:清理操作应在子线程执行,且设置超时时间:
ExecutorService executor = Executors.newSingleThreadExecutor(); Future<?> future = executor.submit(() -> executeSafetyCleaning(ex)); try { future.get(3, TimeUnit.SECONDS); // 3秒超时 } catch (TimeoutException e) { future.cancel(true); // 超时取消清理 } -
避免循环崩溃:记录崩溃频率,防止频繁重启:
public boolean shouldRestartApp() { SharedPreferences sp = getSharedPreferences("crash_stats", MODE_PRIVATE); long lastCrashTime = sp.getLong("last_crash", 0); int crashCount = sp.getInt("crash_count", 0); long currentTime = System.currentTimeMillis(); if (currentTime - lastCrashTime < 60000) { // 1分钟内 crashCount++; } else { crashCount = 1; } // 1分钟内崩溃3次,不再自动重启 if (crashCount >= 3) { return false; } sp.edit().putLong("last_crash", currentTime) .putInt("crash_count", crashCount) .apply(); return true; }
🎯 最佳实践建议
-
分级策略:
- 一级崩溃(UI相关):仅提示,不清理
- 二级崩溃(资源相关):轻度清理
- 三级崩溃(系统相关):重度清理+引导升级
-
用户控制:提供设置选项让用户决定是否启用自动清理:
if (Settings.isAutoCleanEnabled()) { executeSafetyCleaning(ex); } -
反馈机制:收集用户选择的处理方式,优化兜底策略。