在Android中自定义捕获Application全局异常,可以替换掉系统的强制退出对话框(很有参考价值与实用价值)
大家都知道,现在安装 Android 系统的手机和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,这对于下一个版本的 BUG 修复帮助极大,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序。
我们先建立一个 crash 项目,项目结构如图:
在 MainActivity.java 代码中,代码是这样写的:
- package com.scott.crash;
- import android.app.Activity;
- import android.os.Bundle;
- public class MainActivity extends Activity {
- private String s;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- System.out.println(s.equals("any string"));
- }
- }
我们在这里故意制造了一个潜在的运行期异常,当我们运行程序时就会出现以下界面:
遇到软件没有捕获的异常之后,系统会弹出这个默认的强制关闭对话框。
我们当然不希望用户看到这种现象,简直是对用户心灵上的打击,而且对我们的 BUG 的修复也是毫无帮助的。我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。
接下来我们就来实现这一机制,不过首先我们还是来了解以下两个类:android.app.Application 和java.lang.Thread.UncaughtExceptionHandler。
1、Application:用来管理应用程序的全局状态。在应用程序启动时 Application 会首先创建,然后才会根据情况(Intent)来启动相应的Activity 和 Service。本示例中将在自定义加强版的 Application 中注册未捕获异常处理器。
2、Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
大家在刚才的项目结构图中看到的 CrashHandler.java 实现了 Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:
- package com.scott.crash;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- import java.io.Writer;
- import java.lang.Thread.UncaughtExceptionHandler;
- import java.lang.reflect.Field;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- import android.content.Context;
- import android.content.pm.PackageInfo;
- import android.content.pm.PackageManager;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.os.Build;
- import android.os.Environment;
- import android.os.Looper;
- import android.util.Log;
- import android.widget.Toast;
- /**
- * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
- *
- * @author user
- *
- */
- public class CrashHandler implements UncaughtExceptionHandler {
- public static final String TAG = "CrashHandler";
- // CrashHandler 实例
- private static CrashHandler INSTANCE = new CrashHandler();
- // 程序的 Context 对象
- private Context mContext;
- // 系统默认的 UncaughtException 处理类
- private Thread.UncaughtExceptionHandler mDefaultHandler;
- // 用来存储设备信息和异常信息
- private Map<String, String> infos = new HashMap<String, String>();
- // 用于格式化日期,作为日志文件名的一部分
- private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
- /** 保证只有一个 CrashHandler 实例 */
- private CrashHandler() {
- }
- /** 获取 CrashHandler 实例 ,单例模式 */
- public static CrashHandler getInstance() {
- return INSTANCE;
- }
- /**
- * 初始化
- *
- * @param context
- */
- public void init(Context context) {
- mContext = context;
- // 获取系统默认的 UncaughtException 处理器
- mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
- // 设置该 CrashHandler 为程序的默认处理器
- Thread.setDefaultUncaughtExceptionHandler(this);
- }
- /**
- * 当 UncaughtException 发生时会转入该函数来处理
- */
- @Override
- public void uncaughtException(Thread thread, Throwable ex) {
- if (!handleException(ex) && mDefaultHandler != null) {
- // 如果用户没有处理则让系统默认的异常处理器来处理
- mDefaultHandler.uncaughtException(thread, ex);
- } else {
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- Log.e(TAG, "error : ", e);
- }
- // 退出程序,注释下面的重启启动程序代码
- android.os.Process.killProcess(android.os.Process.myPid());
- System.exit(1);
- // 重新启动程序,注释上面的退出程序
- Intent intent = new Intent();
- intent.setClass(mContext,MainActivity.class);
- intent.addFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
- android.os.Process.killProcess(android.os.Process.myPid());
- }
- }
- /**
- * 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
- *
- * @param ex
- * @return true:如果处理了该异常信息;否则返回 false
- */
- private boolean handleException(Throwable ex) {
- if (ex == null) {
- return false;
- }
- // 使用 Toast 来显示异常信息
- new Thread() {
- @Override
- public void run() {
- Looper.prepare();
- Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出。", Toast.LENGTH_LONG).show();
- Looper.loop();
- }
- }.start();
- // 收集设备参数信息
- collectDeviceInfo(mContext);
- // 保存日志文件
- saveCrashInfo2File(ex);
- return true;
- }
- /**
- * 收集设备参数信息
- * @param ctx
- */
- public void collectDeviceInfo(Context ctx) {
- try {
- PackageManager pm = ctx.getPackageManager();
- PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
- if (pi != null) {
- String versionName = pi.versionName == null ? "null" : pi.versionName;
- String versionCode = pi.versionCode + "";
- infos.put("versionName", versionName);
- infos.put("versionCode", versionCode);
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "an error occured when collect package info", e);
- }
- Field[] fields = Build.class.getDeclaredFields();
- for (Field field : fields) {
- try {
- field.setAccessible(true);
- infos.put(field.getName(), field.get(null).toString());
- Log.d(TAG, field.getName() + " : " + field.get(null));
- } catch (Exception e) {
- Log.e(TAG, "an error occured when collect crash info", e);
- }
- }
- }
- /**
- * 保存错误信息到文件中
- *
- * @param ex
- * @return 返回文件名称,便于将文件传送到服务器
- */
- private String saveCrashInfo2File(Throwable ex) {
- StringBuffer sb = new StringBuffer();
- for (Map.Entry<String, String> entry : infos.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue();
- sb.append(key + "=" + value + "\n");
- }
- Writer writer = new StringWriter();
- PrintWriter printWriter = new PrintWriter(writer);
- ex.printStackTrace(printWriter);
- Throwable cause = ex.getCause();
- while (cause != null) {
- cause.printStackTrace(printWriter);
- cause = cause.getCause();
- }
- printWriter.close();
- String result = writer.toString();
- sb.append(result);
- try {
- long timestamp = System.currentTimeMillis();
- String time = formatter.format(new Date());
- String fileName = "crash-" + time + "-" + timestamp + ".log";
- if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- String path = "/sdcard/crash/";
- File dir = new File(path);
- if (!dir.exists()) {
- dir.mkdirs();
- }
- FileOutputStream fos = new FileOutputStream(path + fileName);
- fos.write(sb.toString().getBytes());
- fos.close();
- }
- return fileName;
- } catch (Exception e) {
- Log.e(TAG, "an error occured while writing file...", e);
- }
- return null;
- }
- }
在收集异常信息时,朋友们也可以使用 Properties,因为 Properties 有一个很便捷的方法 properties.store(OutputStream out, String comments),用来将Properties 实例中的键值对外输到输出流中,但是在使用的过程中发现生成的文件中异常信息打印在同一行,看起来极为费劲,所以换成 Map 来存放这些信息,然后生成文件时稍加了些操作。
完成这个 CrashHandler 后,我们需要在一个 Application 环境中让其运行,为此,我们继承 android.app.Application,添加自己的代码,CrashApplication.java代码如下:
- package com.scott.crash;
- import android.app.Application;
- public class CrashApplication extends Application {
- @Override
- public void onCreate() {
- super.onCreate();
- CrashHandler crashHandler = CrashHandler.getInstance();
- crashHandler.init(getApplicationContext());
- }
- }
最后,为了让我们的 CrashApplication 取代 android.app.Application 的地位,在我们的代码中生效,我们需要修改 AndroidManifest.xml:
- <application android:name=".CrashApplication" ...>
- </application>
因为我们上面的 CrashHandler 中,遇到异常后要保存设备参数和具体异常信息到 SDCARD,所以我们需要在 AndroidManifest.xml 中加入读写 SDCARD 权限:
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
搞定了上边的步骤之后,我们来运行一下这个项目:
可以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息,然后看一下 SDCARD 生成的文件:
用文本编辑器打开日志文件,看一段日志信息:
- CPU_ABI=armeabi
- CPU_ABI2=unknown
- ID=FRF91
- MANUFACTURER=unknown
- BRAND=generic
- TYPE=eng
- ......
- Caused by: java.lang.NullPointerException
- at com.scott.crash.MainActivity.onCreate(MainActivity.java:13)
- at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
- at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
- ... 11 more
这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器,有关文件上传的技术,请参照 Android 中使用 HTTP 服务相关介绍。
不过在使用 HTTP 服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:
- /**
- * 网络是否可用
- *
- * @param context
- * @return
- */
- public static boolean isNetworkAvailable(Context context) {
- ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo[] info = mgr.getAllNetworkInfo();
- if (info != null) {
- for (int i = 0; i < info.length; i++) {
- if (info[i].getState() == NetworkInfo.State.CONNECTED) {
- return true;
- }
- }
- }
- return false;
- }
相关推荐
在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,这对于下一个版本的bug修复帮助极大,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息...
本文将深入探讨如何在Android中实现全局异常捕获,以及如何使用系统级对话框显示这些异常信息。 首先,我们需要创建一个自定义的应用程序(Application)类,它是整个应用程序的入口点,可以在此监听并处理全局异常。...
在Android中,我们通常通过创建一个自定义的`Application`类来实现全局异常捕获。自定义`Application`类需要继承自Android的`Application`基类,并在`AndroidManifest.xml`中指定我们的自定义类。在自定义的`...
总之,通过以上步骤,你可以充分利用 Dubbo 的异常处理机制,确保在分布式环境中,服务消费者能有效地捕获和处理来自服务提供者的自定义异常。这不仅提高了代码的可读性和可维护性,还能提高整个系统的稳定性和用户...
在Android开发中,全局异常捕获是一个至关重要的实践,它能确保应用在遇到未预期的错误时仍能保持稳定,并提供良好的用户体验。标题"android全局异常捕获 exception_global"和描述都指向了这个主题,表明我们将探讨...
在Android中,我们可以通过创建一个自定义的`Thread.UncaughtExceptionHandler`并将其设置为应用的默认异常处理器来实现全局异常捕获。`Thread.UncaughtExceptionHandler`是Java线程处理未捕获异常的接口,当线程抛...
自己编写异常类型 ,自定义错误异常进行全局捕捉。实现项目全局的拦截定义输出。。。
Android捕获全局异常并写日志保存到sd卡,捕获异常消息后输出这个消息,可避免APP强制退出,抛出NullPointException玩玩哈,捕获全局异常是因为有的异常我们捕获不到,捕获时我们采用同步方法,以免单例多线程环境下...
在Android中,当出现未捕获的异常时,系统会默认调用`Thread.getDefaultUncaughtExceptionHandler()`,并传递异常信息。默认的异常处理器通常会终止应用,显示错误报告。全局异常捕获就是通过替换这个默认处理器,...
普通的程序如果出现问题了都会强制退出,像360助手之类的程序崩溃的时候还会给出一个是否上传错误到服务器的提示,本项目就是一个简单的全局异常捕捉例子,捕捉到异常以后可以把异常信息写入文件以供后来分析或者用...
全局异常捕获是指在整个应用程序运行过程中,无论在哪个线程、哪个模块抛出未被捕获的异常时,都能进行有效的处理,避免应用突然崩溃。这通常通过实现自定义的异常处理器来实现。本主题将深入探讨Android全局异常...
总结,Android全局异常捕获是保证应用健壮性的重要手段,它涉及到对各种类型异常的捕获和处理,包括系统级异常、第三方库异常、Webview加载异常等。通过自定义异常处理器,开发者可以更全面地监控应用状态,及时发现...
Android 全局异常错误或崩溃捕捉。当出现崩溃,软件不会闪退,会出现弹出一个对话框,异常错误信息保存在sd卡一个文件夹下。后续需要还可以发送到服务器的。我博客网址: http://blog.csdn.net/qq_16064871。我写了...
本篇将详细介绍如何在Android中实现全局异常捕获,并提供一个名为"CrashHandler"的完整工程源码。 首先,我们需要了解Android中的异常处理机制。在Java中,每个线程都有一个默认的`UncaughtExceptionHandler`,当...
总的来说,VB.NET全局错误和异常处理涉及对`AppDomain.UnhandledException`和`Application.ThreadException`事件的订阅,以及如何在这些事件处理器中执行适当的错误恢复策略。通过正确实施全局错误处理,开发者可以...
1. **全局异常捕获**: 在Android应用中,全局异常捕获通常通过自定义的`UncaughtExceptionHandler`实现。这个处理器会捕获那些没有被应用程序内部捕获的运行时异常,防止应用因为未处理的异常而突然退出。 2. **...
android在产品上线以后为了能够实时捕获异常,需要写一个异常捕获的类,这个类在你遇到异常的时候会执行uncaughtException这个方法,然后你就可以对异常进行一些操作。
总的来说,全局异常捕获是通过自定义`UncaughtExceptionHandler`并设置为系统默认处理器来实现的,这使得开发者可以在一个集中的位置处理所有未被捕获的异常,提供了更健壮的错误处理策略。在实际项目中,我们还应该...
在Spring MVC框架中,处理应用程序可能出现的异常是至关重要的,以确保系统稳定性和用户体验。"利用SpringMVC切面捕获全局异常入库源码"这个项目正是为了实现这样的功能,即通过面向切面编程(AOP)的方式捕获并记录...