Android自定义全局捕获异常并上传,实现实时收集APP崩溃crash信息

在APP上线后,可能会出现一些BUG导致了APP的闪退,用户体验就非常致命,我们一定要第一时间找到问题的所在

1、我们需要自定义一个异常收集类(创建一个Thread.UncaughtExceptionHandler的继承类);
2、替换掉APP本身的异常处理(在Thread.UncaughtExceptionHandler实现类中使用Thread.setDefaultUncaughtExceptionHandler(this)方法替换);
3、在类中收集信息,这个信息最好包括手机的一些信息,比如:厂商、型号、cup型号、内存大小等...,因为安卓手机的多样性导致我们在适配的时候非常麻烦,也是因为有些问题的出现是因为个别的硬件差异造成的,所以这些信息最好收集;

整体思路就是,自定义一个异常收集类替换到本来的异常处理类,在这个类中去收集一些必要的信息回传到我们的后台,我们可以在崩溃信息发生的第一时间去处理

以下是异常收集类代码,可以用作参考

/**
 * Created by zsk on 2019/6/11.
 */

public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "CrashHandlerUtil";
    //创建CrashHandlerUtil实列
    private static CrashHandlerUtil crashHandlerUtil = new CrashHandlerUtil();
    //系統默认UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //程序Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String, String> infos = new HashMap<>();
    //用于格式化日期,作为日志名的一部分
    private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", 
    Locale.CHINA);
    private static String error = "程序崩溃!!!";
    // 外置存储卡默认预警临界值
    private static final long THRESHOLD_WARNING_SPACE = 100 * M;
    // 保存文件时所需的最小空间的默认值
    public static final long THRESHOLD_MIN_SPCAE = 60 * M;
    public static CrashHandlerUtil getInstance() {
        return crashHandlerUtil;
    }
    public void init(Context context){
        mContext=context;
        //获取系统默认uncaughtException处理器
        mDefaultHandler=Thread.getDefaultUncaughtExceptionHandler();
        //设置CrashHandler为程序默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);

    }
    /**
     * 程序异常调用该方法
     *@param thread 线程
    * @param  throwable 异常
    */
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        StringBuilder sb = new StringBuilder();
        // 1.获取当前应用程序的版本号.
        PackageManager pm = mContext.getPackageManager();
        try {
            PackageInfo packinfo = pm.getPackageInfo(mContext.getPackageName(),
                    0);
            sb.append("程序的版本号为" + packinfo.versionName);
            sb.append("\n");

            // 2.获取手机的硬件信息.
            Field[] fields = Build.class.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                // 暴力反射,获取私有的字段信息
                fields[i].setAccessible(true);
                String name = fields[i].getName();
                sb.append(name + " = ");
                String value = fields[i].get(null).toString();
                sb.append(value);
                sb.append("\n");
            }
            // 3.获取程序错误的堆栈信息 .
            StringWriter writer = new StringWriter();
            PrintWriter printWriter = new PrintWriter(writer);
            throwable.printStackTrace(printWriter);

            String result = writer.toString();
            sb.append(result);
            // 4将错误信息转成文件上传到服务器
            uploadFile(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * url       完整的接口地址
     * file       需要上传的文件
     *  fileUploadObserver 上传回调
     */
    private void uploadFile(String s) {
        try {
            File file = string2file(s);
            RetrofitHelp.getIns().upLoadFile("", file, new 
                        FileUploadObserver<ResponseBody>() {
                @Override
                public void onUpLoadSuccess(ResponseBody responseBody) {
                        //上传成功
                }

                @Override
                public void onUpLoadFail(Throwable e) {
                        //上传失败 
                }

                @Override
                public void onProgress(int progress) {
                      //上传进度
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回 false
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        // 收集设备参数信息
        collectDeviceInfo(mContext);
        // 保存日志文件
        saveCrashInfo2File(ex);
        // 使用 Toast 来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, error, Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
        return true;
    }

    /**
     * 收集设备参数信息
     *
     * @param context 上下文
     */
    public void collectDeviceInfo(Context context){
        try {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            if(packageInfo!=null){
                String versionName= packageInfo.versionName == null ? "null" : packageInfo.versionName;
                String versionCode = packageInfo.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 (IllegalAccessException e) {
                 Log.e(TAG, "an error occured when collect crash info", e);
             }
         }
    }
    /**
     * 保存错误信息到文件中 *
     *
     * @param ex
     * @return 返回文件名称,便于将文件传送到服务器
     */
    private String saveCrashInfo2File(Throwable ex) {
        StringBuffer sb = getTraceInfo(ex);
        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 = dateFormat.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".log";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = Environment.getExternalStorageDirectory() + "/crash/";
                Log.i("TAG","路径======="+path);
                File dir = new File(path);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                else if(!hasEnoughSpaceForWrite(path))
                {
                    deleteDirectory(path);
                }
                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;
    }
    /**
     * 整理异常信息
     * @param e
     * @return
     */
    public static StringBuffer getTraceInfo(Throwable e) {
        StringBuffer sb = new StringBuffer();
        Throwable ex = e.getCause() == null ? e : e.getCause();
        StackTraceElement[] stacks = ex.getStackTrace();
        for (int i = 0; i < stacks.length; i++) {
            sb.append("class: ").append(stacks[i].getClassName())
                    .append("; method: ").append(stacks[i].getMethodName())
                    .append("; line: ").append(stacks[i].getLineNumber())
                    .append(";  Exception: ").append(ex.toString() + "\n");
        }
        return sb;
    }
    /**
     * 删除文件夹下的文件
     * @param   filePath 被删除目录下文件的路径
     * @return  目录删除成功返回true,否则返回false
     */
    public boolean deleteDirectory(String filePath) {
        boolean flag = false;
        //如果filePath不以文件分隔符结尾,自动添加文件分隔符
        if (!filePath.endsWith(File.separator)) {
            filePath = filePath + File.separator;
        }
        File dirFile = new File(filePath);
        if (!dirFile.exists() || !dirFile.isDirectory()) {
            return false;
        }
        flag = true;
        File[] files = dirFile.listFiles();

        //遍历删除文件夹下的所有文件(包括子目录)
        for (int i = 0; i < files.length; i++) {
            if (files[i].isFile()) {
                //删除子文件
                flag = deleteFile(files[i].getAbsolutePath());
                if (!flag) break;
            }
        }
        if (!flag)
            return false;
        return true;
    }
    /**
     * 删除单个文件
     * @param   filePath    被删除文件的文件名
     * @return 文件删除成功返回true,否则返回false
     */
    public boolean deleteFile(String filePath) {
        File file = new File(filePath);
        if (file.isFile() && file.exists()) {
            return file.delete();
        }
        return false;
    }
    /**
     * 判断外部存储是否存在,以及是否有足够空间保存指定类型的文件
     *
     * @param directoryPath
     * @return false: 无存储卡或无空间可写, true: 表示ok
     */
    public boolean hasEnoughSpaceForWrite(String directoryPath) {
        long residual = getAvailableExternalSize(directoryPath);
        if (residual < THRESHOLD_MIN_SPCAE) {
            return false;
        } else if (residual < THRESHOLD_WARNING_SPACE) {

        }
        return true;
    }
    /**
     * 获取外置存储卡剩余空间
     * @return
     */
    public long getAvailableExternalSize(String directoryPath) {
        try {
            StatFs sf = new StatFs(directoryPath);
            long blockSize = sf.getBlockSize();
            long availCount = sf.getAvailableBlocks();
            long availCountByte = availCount * blockSize;
            return availCountByte;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
    /**
     * 将String转成file
     * @param s
     * @return
     */
    private File string2file (String s) throws Exception {
        File fileDir = new File(Environment.getExternalStorageDirectory() .getAbsolutePath() +"/log");
        if(!fileDir.exists()){
            fileDir.mkdirs();
        }
        Date date = new Date();
        File file = new File(fileDir, SPHelper.getInstence(mContext).getUid() + "-" + date.getYear()+date.getMonth()+date.getDay()
                +date.getHours()+date.getMinutes()+ ".txt");
        if(!file.exists()){
            file.createNewFile();
        }
        ByteArrayInputStream ins = new ByteArrayInputStream(s.getBytes());
        OutputStream os = new FileOutputStream(file);
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        os.close();
        ins.close();
        return file;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值