android使用UncaughtExceptionHandler捕获全局异常

本文介绍如何在Java中实现自定义的未捕获异常处理机制,包括通过覆盖ThreadGroup的uncaughtException方法和使用Thread的setUncaughtExceptionHandler方法。提供了一个具体的CrashHandler类实现,并展示了如何在应用程序中进行配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

好久之前记录到word中的,也忘记参考的哪篇博客……真的不记得了

工程目录

这里写图片描述

概述

      Thread的run方法是不抛出任何检查型异常(checked exception)的,但是它自身却可能因为一个异常而被终止,导致这个线程的终结
      JDK5.0之前,不能为单独的Thread设置UncaughtExceptionHandler,也不能指定一个默认的UncaughtExceptionHandler。为了可以设置一个UncaughtExceptionHandler,需要去继承ThreadGroup并覆写uncaughtException方法
     JDK5.0及之后,我们通过Thread的实例方法setUncaughtExceptionHandler,可以为任何一个Thread设置一个UncaughtExceptionHandler。当然你也可以为所有Thread设置一个默认的UncaughtExceptionHandler,通过调用Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)方法,这是Thread的一个static方法

主要文件

CrashHandler.java

public class CrashHandler implements Thread.UncaughtExceptionHandler {
    public static final String TAG = "CrashHandler";

    private static CrashHandler sInstance = new CrashHandler();// CrashHandler 实例

    private Context mContext;// 程序的 Context 对象
    private Thread.UncaughtExceptionHandler mDefaultHandler;// 系统默认的 UncaughtException 处理类
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");// 用于格式化日期,作为日志文件名的一部分

    private String pathLog = "/sdcard/crash/";//log存储的路径
    private Map<String, String> infos = new HashMap<String, String>();// 用来存储设备信息和异常信息

    /**
     * 保证只有一个 CrashHandler 实例
     */
    private CrashHandler() {

    }

    /**
     * 获取 CrashHandler 实例 ,单例模式
     */
    public static CrashHandler getInstance() {
        return sInstance;
    }

    /**
     * 初始化
     * @param context
     */
    public void init(Context context) {
        mContext = context;
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 获取系统默认的 UncaughtException 处理器
        Thread.setDefaultUncaughtExceptionHandler(this);// 设置该 CrashHandler 为程序的默认处理器
    }

    /**
     * 当 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.addFlags(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 (PackageManager.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);
            }
        }
        System.out.println(infos.toString());
    }

    /**
     * 保存错误信息到文件中
     * @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();
        System.out.println("CrashHandler.saveCrashInfo2File"+result);
        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)) {
                File dir = new File(pathLog);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(pathLog + 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;
    }
}

CrashApplication .java

public class CrashApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(getApplicationContext());
    }
}

AndroidManifest.xml

<application
            android:label="@string/app_name"
            android:name=".CrashApplication"
            android:icon="@drawable/ic_launcher">
        <activity
                android:name="MainActivity"
                android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".ExceptionActivity"/>
    </application>

运行效果

这里写图片描述

输出的设备及异常信息:

SUPPORTED_64_BIT_ABIS=[Ljava.lang.String;@b416fd0
versionCode=1
BOARD=BalongV8R1SFT
BOOTLOADER=unknown
TYPE=user
matchers=[Ljava.lang.String;@309612ce
ID=HuaweiALE-UL00
TIME=1450304496000
BRAND=Huawei
TAG=Build
SERIAL=N2F4C15905016654
HARDWARE=hi6210sft
SUPPORTED_ABIS=[Ljava.lang.String;@5ce2bc9
NO_HOTA=false
CPU_ABI=arm64-v8a
IS_DEBUGGABLE=false
RADIO=unknown
replacements=[Ljava.lang.String;@2f4f5ef
MANUFACTURER=HUAWEI
SUPPORTED_32_BIT_ABIS=[Ljava.lang.String;@187d7082
TAGS=release-keys
CPU_ABI2=
UNKNOWN=unknown
USER=android
FINGERPRINT=Huawei/ALE-UL00/hwALE-H:5.0.2/HuaweiALE-UL00/C00B230:user/release-keys
HOST=localhost#1
PRODUCT=ALE-UL00
versionName=1.0
DISPLAY=ALE-UL00C00B230
HIDE_PRODUCT_INFO=false
MODEL=ALE-UL00
DEVICE=hwALE-H
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.luomo.gexception/com.luomo.gexception.ExceptionActivity}: java.lang.ArithmeticException: divide by zero
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2432)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2492)
    at android.app.ActivityThread.access$1200(ActivityThread.java:158)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1349)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5564)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:960)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
Caused by: java.lang.ArithmeticException: divide by zero
    at com.luomo.gexception.ExceptionActivity.onCreate(ExceptionActivity.java:10)
    at android.app.Activity.performCreate(Activity.java:6013)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1108)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2385)
    ... 10 more
java.lang.ArithmeticException: divide by zero
    at com.luomo.gexception.ExceptionActivity.onCreate(ExceptionActivity.java:10)
    at android.app.Activity.performCreate(Activity.java:6013)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1108)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2385)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2492)
    at android.app.ActivityThread.access$1200(ActivityThread.java:158)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1349)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5564)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:960)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

项目下载路径

http://download.youkuaiyun.com/download/u010137760/9420900

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值