Android/Java 异常捕获
1. 不捕获异常可能导致的后果 Android/Java 异常捕获详解及 Exception、Throwable 区别
在 Android/Java 开发中,如果不捕获异常,会导致以下几种严重后果:
1. 应用崩溃(最直接的后果)
- Android 中:未捕获的异常会导致应用抛出
RuntimeException
,触发默认的异常处理机制,最终导致应用崩溃(ANR 或直接退出) - 会显示:“应用已停止运行” 或 “App has stopped”
2. 用户体验差
- 突然的应用退出会让用户感到困惑和不满
- 可能造成用户数据丢失(未保存的操作结果)
- 降低用户对应用的信任度
3. 线程终止
// 示例:工作线程中的未捕获异常会导致线程终止
new Thread(() -> {// 如果这里抛出异常且未被捕获throw new RuntimeException("线程中的未处理异常");
}).start();
// 线程会静默终止,不会执行后续代码
4. 资源泄漏
- 异常导致代码执行路径中断,可能无法正确释放资源
- 如:数据库连接、文件句柄、网络连接等可能无法正常关闭
5. Android 中的特殊处理
// 主线程(UI线程)中的未捕获异常
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 如果这里发生异常且未被捕获// 会导致应用立即崩溃
}
因此,最佳实践建议是:
- 始终处理已知的受检异常(Checked Exceptions)
- 对关键业务代码添加异常处理
- 使用适当的日志记录异常信息
- 向用户提供友好的错误信息,而不是原始的异常堆栈
- 在适当的地方重新抛出异常,而不是盲目捕获所有异常
- 使用 finally 块确保资源释放
2. 需要和不需要捕获异常的情况
1.需要捕获异常的情况
1. 受检异常 (Checked Exceptions)
所有继承自 Exception
但不继承自 RuntimeException
的异常都必须被捕获或声明抛出:
try {FileInputStream file = new FileInputStream("file.txt");
} catch (FileNotFoundException e) {// 必须处理,因为这是受检异常Log.e("TAG", "文件未找到", e);
}
2. 可能失败的操作
- 网络请求:
try {HttpResponse response = httpClient.execute(request);
} catch (IOException e) {// 处理网络异常showNetworkError();
}
- 文件操作:
try {writeToFile(data);
} catch (IOException e) {// 处理文件写入失败Log.e("TAG", "写入文件失败", e);
}
3. 外部资源访问
- 数据库操作:
try {database.insert(data);
} catch (SQLException e) {// 处理数据库异常showErrorMessage("数据库操作失败");
}
4. 用户输入验证
try {int number = Integer.parseInt(userInput);
} catch (NumberFormatException e) {// 处理格式错误showValidationError("请输入有效数字");
}
5. 第三方库或API调用
try {thirdPartyLibrary.doSomething();
} catch (ThirdPartyException e) {// 处理第三方库异常handleThirdPartyError(e);
}
6. 需要给用户友好提示的异常
try {processUserRequest();
} catch (BusinessException e) {// 将技术异常转换为用户友好的消息showToast("操作失败,请重试");
}
2. 不需要捕获异常的情况
1. 运行时异常 (RuntimeExceptions)
通常不需要捕获,因为它们通常表示编程错误:
// 不需要捕获 NullPointerException
String length = text.length(); // 如果text为null,让应用崩溃以便发现bug// 不需要捕获 ArrayIndexOutOfBoundsException
int value = array[index]; // 应该确保index在范围内
2. 错误 (Errors)
继承自 Error
的异常通常不应该被捕获:
// 不要捕获这些错误
// OutOfMemoryError, StackOverflowError, VirtualMachineError等
3. 无法有效处理的异常
如果不知道如何正确处理异常,最好不要捕获:
// 不好的做法:
try {criticalOperation();
} catch (Exception e) {// 空捕获或只是打印日志 - 这隐藏了问题Log.e("TAG", "错误", e);
}// 更好的做法:让异常传播到合适的地方处理
4. 在框架层面统一处理的异常
// 在Android中,可以在Application级别设置默认异常处理器
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {// 全局异常处理Log.e("Global", "未捕获异常", throwable);// 上报崩溃日志
});
3. 最佳实践
1. 具体的异常捕获
try {// 可能抛出多种异常的操作
} catch (FileNotFoundException e) {// 处理特定异常
} catch (IOException e) {// 处理IO异常
} catch (Exception e) {// 最后捕获通用异常
}
2. 适当的异常转换
try {readConfigFile();
} catch (IOException e) {// 将底层异常转换为更有意义的业务异常throw new ConfigurationException("无法读取配置文件", e);
}
3. 资源清理使用try-with-resources
// 自动关闭资源,无需显式捕获关闭异常
try (FileInputStream fis = new FileInputStream("file.txt");BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {// 使用资源
} catch (IOException e) {// 处理异常
}
4. 在合适的层级处理异常
// 在底层方法中抛出异常
public User loadUser(String id) throws UserNotFoundException {// ...
}// 在UI层处理异常
try {User user = loadUser(userId);updateUI(user);
} catch (UserNotFoundException e) {showUserNotFoundError();
}
总结
情况 | 是否需要捕获 | 理由 |
---|---|---|
受检异常 | 必须 | 编译器要求 |
运行时异常 | 通常不需要 | 通常是编程错误 |
错误 | 不需要 | 严重系统问题 |
可恢复的错误 | 需要 | 给用户第二次机会 |
无法处理的异常 | 不需要 | 应该让上层处理 |
记住的关键原则:只在你知道如何正确处理异常时才捕获它。否则,让异常传播到能够适当处理它的层级。
3. Java异常捕获详解
1. Java 异常体系结构
1.1 异常类层次结构
Throwable (可抛出对象)
├── Error (错误) - 严重系统问题,通常不应捕获
│ ├── VirtualMachineError
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception (异常) - 可处理的异常├── RuntimeException (运行时异常) - 非受检异常│ ├── NullPointerException│ ├── IndexOutOfBoundsException│ ├── IllegalArgumentException│ └── ArithmeticException└── 其他Exception (非运行时异常) - 受检异常├── IOException├── SQLException├── FileNotFoundException└── InterruptedException
2. Throwable、Exception、Error 的区别
2.1 Throwable
// Throwable 是所有异常和错误的根类
public class ExceptionDemo {public void testThrowable() {try {// 可能抛出异常的操作int[] arr = new int[5];arr[10] = 1; // 这里会抛出 ArrayIndexOutOfBoundsException} catch (Throwable t) {// Throwable 可以捕获所有异常和错误Log.e("Exception", "捕获到 Throwable: " + t.getMessage());Log.e("Exception", "异常类型: " + t.getClass().getName());// 获取堆栈跟踪t.printStackTrace();// Throwable 的常用方法Log.d("Exception", "消息: " + t.getMessage());Log.d("Exception", "本地化消息: " + t.getLocalizedMessage());Log.d("Exception", "原因: " + t.getCause());}}
}
2.2 Exception vs Error
public class ExceptionVsError {// Exception - 可恢复的异常public void handleException() {try {// 可能抛出异常的代码String str = null;int length = str.length(); // NullPointerException} catch (Exception e) {// Exception 可以捕获所有异常,但不包括 ErrorLog.w("Exception", "捕获到异常: " + e.getMessage());// 可以进行恢复操作showErrorMessage("数据异常,请重试");}}// Error - 严重的系统错误public void handleError() {try {// 可能引发错误的操作int[] hugeArray = new int[Integer.MAX_VALUE]; // 可能 OutOfMemoryError} catch (Error err) {// Error 通常不应该被捕获,因为程序可能处于不稳定状态Log.e("Error", "系统错误: " + err.getMessage());// 通常做法是记录错误并优雅退出saveErrorLog(err);System.exit(1);}}private void showErrorMessage(String message) {Toast.makeText(context, message, Toast.LENGTH_SHORT).show();}private void saveErrorLog(Error error) {// 保存错误日志}
}
3. Try-Catch-Finally 详解
3.1 基本语法
public class TryCatchDemo {public void basicTryCatch() {try {// 可能抛出异常的代码块FileInputStream fis = new FileInputStream("file.txt");// 读取文件操作...} catch (FileNotFoundException e) {// 捕获特定异常Log.e("File", "文件未找到: " + e.getMessage());} catch (IOException e) {// 捕获其他IO异常Log.e("File", "IO错误: " + e.getMessage());} catch (Exception e) {// 捕获所有其他异常Log.e("General", "未知异常: " + e.getMessage());} finally {// 无论是否发生异常都会执行的代码Log.d("Finally", "清理资源");// 通常用于关闭资源}}
}
3.2 多重 Catch 块的顺序
public class MultipleCatchDemo {public void multipleCatchOrder() {try {// 可能抛出多种异常的代码processData();} catch (FileNotFoundException e) {// 具体的异常应该放在前面Log.e("Specific", "文件未找到");} catch (IOException e) {// 更一般的异常放在后面Log.e("General", "IO异常");} catch (Exception e) {// 最一般的异常放在最后Log.e("Universal", "通用异常");}// 错误示例:如果把 Exception 放在前面,其他的 catch 块永远不会执行/*try {// ...} catch (Exception e) { // 错误:这个会捕获所有异常// ...} catch (IOException e) { // 这个永远不会执行// ...}*/}
}
4. 受检异常 vs 非受检异常
4.1 受检异常 (Checked Exception)
public class CheckedExceptionDemo {// 必须声明或处理受检异常public void readFile() throws IOException {File file = new File("test.txt");FileReader reader = new FileReader(file); // 必须处理 FileNotFoundException// 或者使用 try-catch}public void alternativeApproach() {try {File file = new File("test.txt");FileReader reader = new FileReader(file);} catch (FileNotFoundException e) {Log.e("Checked", "文件未找到", e);}}
}
4.2 非受检异常 (Unchecked Exception)
public class UncheckedExceptionDemo {// 运行时异常不需要声明public void processData() {String str = null;// 这里可能抛出 NullPointerException,但不需要声明int length = str.length();}public void handleUnchecked() {try {int[] arr = {1, 2, 3};int value = arr[5]; // ArrayIndexOutOfBoundsException} catch (ArrayIndexOutOfBoundsException e) {Log.e("Unchecked", "数组越界", e);}}
}
5. 自定义异常
5.1 创建自定义异常
// 自定义受检异常
public class NetworkException extends Exception {private int errorCode;public NetworkException(String message) {super(message);}public NetworkException(String message, int errorCode) {super(message);this.errorCode = errorCode;}public NetworkException(String message, Throwable cause) {super(message, cause);}public int getErrorCode() {return errorCode;}
}// 自定义非受检异常
public class BusinessException extends RuntimeException {public BusinessException(String message) {super(message);}public BusinessException(String message, Throwable cause) {super(message, cause);}
}
5.2 使用自定义异常
public class CustomExceptionDemo {public void makeNetworkRequest() throws NetworkException {try {// 模拟网络请求if (isNetworkAvailable()) {// 正常请求} else {throw new NetworkException("网络不可用", 1001);}} catch (IOException e) {throw new NetworkException("网络请求失败", e);}}public void validateInput(String input) {if (input == null || input.trim().isEmpty()) {throw new BusinessException("输入不能为空");}}
}
6. Try-with-Resources (Java 7+)
6.1 自动资源管理
public class TryWithResourcesDemo {public void readFileAutoClose() {// 自动关闭资源,不需要 finally 块try (FileInputStream fis = new FileInputStream("file.txt");BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {String line;while ((line = reader.readLine()) != null) {Log.d("File", line);}} catch (IOException e) {Log.e("File", "读取文件失败", e);}// 资源会自动关闭,即使在发生异常的情况下}
}
7. 异常处理最佳实践
7.1 Android 中的异常处理
public class AndroidExceptionHandler {private Context context;// 全局异常处理器public void setupGlobalExceptionHandler() {Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {Log.e("Global", "未捕获异常: " + thread.getName(), throwable);saveCrashReport(throwable);showUserFriendlyMessage();System.exit(1);});}// 异步任务中的异常处理public void asyncOperation() {new Thread(() -> {try {// 后台操作performBackgroundTask();} catch (Exception e) {Log.e("Async", "后台任务异常", e);// 在主线程中处理UI更新new Handler(Looper.getMainLooper()).post(() -> {Toast.makeText(context, "操作失败", Toast.LENGTH_SHORT).show();});}}).start();}// 不要吞掉异常public void badPractice() {try {riskyOperation();} catch (Exception e) {// 错误:吞掉异常,没有日志也没有处理// 应该至少记录日志}}public void goodPractice() {try {riskyOperation();} catch (SpecificException e) {Log.w("Good", "特定异常,可以继续", e);fallbackOperation();} catch (Exception e) {Log.e("Good", "严重异常", e);handleSevereError(e);}}
}
8. 常见异常类型及处理
8.1 Android 常见异常
public class CommonAndroidExceptions {// NullPointerExceptionpublic void handleNullPointer() {try {String text = getTextFromView();int length = text.length(); // 可能为null} catch (NullPointerException e) {Log.e("Null", "空指针异常", e);// 防御性编程:使用空检查}}// IndexOutOfBoundsExceptionpublic void handleIndexOutOfBounds() {try {List<String> list = getDataList();String item = list.get(10); // 可能越界} catch (IndexOutOfBoundsException e) {Log.e("Index", "索引越界", e);// 检查大小 before accessing}}// NetworkOnMainThreadException (Android特有)public void handleNetworkOnMainThread() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {try {// 在主线程执行网络操作URL url = new URL("http://example.com");HttpURLConnection conn = (HttpURLConnection) url.openConnection();} catch (NetworkOnMainThreadException e) {Log.e("Network", "主线程网络操作", e);// 移动到后台线程} catch (IOException e) {Log.e("Network", "网络IO异常", e);}}}
}
9. 异常链和包装异常
public class ExceptionChaining {public void processWithChaining() {try {performComplexOperation();} catch (BusinessException e) {// 保留原始异常信息Log.e("Business", "业务操作失败", e);// 可以获取根本原因Throwable rootCause = e;while (rootCause.getCause() != null) {rootCause = rootCause.getCause();}Log.d("Root", "根本原因: " + rootCause.getClass().getSimpleName());}}private void performComplexOperation() throws BusinessException {try {// 底层操作可能抛出IOExceptionreadConfigurationFile();} catch (IOException e) {// 包装原始异常,提供更多上下文throw new BusinessException("读取配置文件失败", e);}}
}
总结
特性 | Throwable | Exception | Error |
---|---|---|---|
可捕获性 | 所有 | 大部分 | 不建议 |
恢复可能性 | 可变 | 通常可恢复 | 通常不可恢复 |
检查要求 | - | 受检异常需要处理 | 不需要 |
典型用途 | 根类 | 业务异常 | 系统错误 |
最佳实践:
- 使用具体的异常类型进行捕获
- 不要吞掉异常(空的 catch 块)
- 使用异常链保留原始异常信息
- 在 finally 块或 try-with-resources 中清理资源
- 为自定义异常提供有意义的错误信息
- 在 Android 中注意主线程限制