全局异常处理

在项目中,如果有异常未捕获,运行时触发异常,则会将异常抛出,给用户一个不好的体验。

虽然现在系统厂商都对未捕获异常进行了处理,基本不会发生弹出异常,崩溃退出应用的场景,但是我们在项目中一般还是会进行一个全局异常处理,可以自己定义或者使用第三方统计,如有盟统计,腾讯bugly等。

异常定义及分类

异常是指在程序运行过程中所出现的错误,这些错误会干扰到指令的正常执行,从而导致程序异常退出。

对java语言来说,所有异常都继承自Throwable

这里写图片描述

Error异常是我们无法处理的异常,属于系统异常。

Exception异常是由于我们代码编写失误造成的,可以通过修改代码进行控制。我们所处理的异常都属于此类异常。

异常根据触发时机不同又可分为两种:

  1. 编译时异常

    如ClassNotFoundException/CanNotFoundId layout未找到id

  2. 运行时异常

    如数组越界/类型转换异常

对异常的全局处理

/**
 * Interface for handlers invoked when a <tt>Thread</tt> abruptly
 * terminates due to an uncaught exception.
 * 当一个线程因为未捕获异常突然终止时调用
*/
public interface UncaughtExceptionHandler {
    /**
    *未捕获异常处理
    */
    void uncaughtException(Thread t, Throwable e);
}

所以我们只需要实现UncaughtExceptionHandler中uncaughtException方法即可。

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private static CrashHandler instance;
    private Context mcontext;
    private Thread.UncaughtExceptionHandler mDefualtHandller;
    private Map<String, String> eMaps = new ArrayMap<>();
    private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private boolean isPrintLog;

    private CrashHandler() {
    }

    public static CrashHandler getInstance() {
        if (instance == null) {
            synchronized (CrashHandler.class) {
                if (instance == null) {
                    instance = new CrashHandler();
                }
            }
        }
        return instance;
    }

    /**
     * 初始化
     * @param context
     * @param isPrintLog 是否输出错误信息
     */
    public void init(Context context, boolean isPrintLog) {
        mcontext = context;
        this.isPrintLog = isPrintLog;
        mDefualtHandller = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 当触发未捕获异常时,系统将调用此方法
     * @param t
     * @param e
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {

        if (!handleException(e)) {
            if (mDefualtHandller != null)
                mDefualtHandller.uncaughtException(t, e);
        } else {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            Process.killProcess(Process.myPid());
            System.exit(1);
        }
    }

    /**
     * 是否手动处理未捕获异常
     *
     * @param e
     * @return
     */
    private boolean handleException(Throwable e) {
        if (e == null)
            return false;
        showExceptionMsg(e);
        collectExceptionInfo();
        saveExceptionInfo(e);
        return true;
    }

    /**
     * 收集异常信息
     */
    private void collectExceptionInfo() {
        PackageManager pm = mcontext.getPackageManager();
        try {
            PackageInfo packageInfo = pm.getPackageInfo(mcontext.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (packageInfo != null) {
                eMaps.put("versionName", packageInfo.versionName);
                eMaps.put("versionCode", String.valueOf(packageInfo.versionCode));
            }

            saveDeviceInfo();

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 保存设备信息
     */
    private void saveDeviceInfo() {
        Field[] fields = Build.class.getFields();
        if (fields != null && fields.length > 0)
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    eMaps.put(field.getName(), field.get(null).toString());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
    }

    /**
     * 保存异常信息到sd卡
     */
    private void saveExceptionInfo(Throwable e) {
        StringBuffer stringBuffer = new StringBuffer();
        for (Map.Entry<String, String> entry : eMaps.entrySet()) {
            stringBuffer.append(entry.getKey() + "===" + entry.getValue() + "\n\r");
        }
        Writer write = new StringWriter();
        PrintWriter printer = new PrintWriter(write);
        e.printStackTrace(printer);
        Throwable cause = e.getCause();
        while (cause != null) {
            cause.printStackTrace(printer);
            e.getCause();
        }
        printer.close();
        stringBuffer.append(write.toString());

        long time = System.currentTimeMillis();
        String date = dateFormat.format(new Date());
        String fileName = "crash-" + date + "-" + time + ".log";
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            String filePath = "/sdcard/" + mcontext.getPackageName() + "-crash/";
            File file = new File(filePath);
            if (!file.exists())
                file.mkdirs();
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file + fileName);
                fos.write(stringBuffer.toString().getBytes());
            } catch (FileNotFoundException e1) {
                e1.printStackTrace();
            } catch (IOException e1) {
                e1.printStackTrace();
            } finally {
                try {
                    fos.close();
                } catch (IOException e1) {
                }
            }
        }
        //TODO 将错误信息上次到服务器

    }

    /**
     * 提示异常信息
     */
    private void showExceptionMsg(Throwable e) {
        if (!isPrintLog) return;
        if (Looper.getMainLooper() == Looper.myLooper())
            showToast(e, true);
        else
            showToast(e, false);
    }


    private void showToast(Throwable e, boolean isMainThread) {
        if (isMainThread)
            Toast.makeText(mcontext, "未捕获异常:" + e.getMessage(), Toast.LENGTH_LONG).show();
        else {
            Looper.prepare();
            Toast.makeText(mcontext, "未捕获异常:" + e.getMessage(), Toast.LENGTH_LONG).show();
            Looper.loop();
        }
    }
}

然后在Application中初始化即可。

public class App extends Application {

    CrashHandler crashHandler;
    @Override
    public void onCreate() {
        super.onCreate();
        crashHandler=CrashHandler.getInstance();
        crashHandler.init(this,BuildConfig.ISDEBUG);
    }
}

其中BuildConfig.ISDEBUG可以通过在build.gradle中配置

debug{
        buildConfigField('boolean' , 'ISDEBUG', 'true')
    }
release {
        buildConfigField('boolean' , 'ISDEBUG', 'false')
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }

如果是字符串则需要 buildConfigField(‘String’ , ‘aaaa’, ‘\”fadfasfa\”’)

使用第三方统计

使用有盟统计或者腾讯bugly,按照文档设置完成之后,可以在后台看到异常报表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值