捕获异常信息CrashHandler

2020-04-07  本文已影响0人  勤劳的蚂蚁

注意事项:

  1. 建好的文件有时候看不到,需要重启手机或者是调用 MediaScannerConnection.scanFile 方法。scanFile 方法在测试三星机型上有时候没有找到文件夹,需要重新启动手机,当其有文件夹就可以找到该文件了。
  2. 输出日期时间 需要HH,如果为 hh,则下午时间和早上时间区分不开
  3. 通过下面方法将异常捕获在 public void uncaughtException(Thread thread, Throwable ex) 中处理
    //设置该CrashHandler为程序的默认处理器
    Thread.setDefaultUncaughtExceptionHandler(this);
  4. 考虑文件拼接,printwrite 不直接输入文件内容,其会覆盖之前内容,输出编码格式为utf-8,需要OutputStreamWriter 转码。
  5. addUserInfo 可以自己定义需要传递的用户信息。
  6. 服务器上传 可以考虑与后台配合完成
public class CrashHandler implements Thread.UncaughtExceptionHandler {

    /** 文件包名称*/
    private static final String TAG = "TxxCrash";
    /** 文件包名称*/
    private static final String SP_NAME = "TxxCrash";
    /** 存储位置文件夹*/
    private String logdir;
    /** 文件名称*/
    protected static final String FILE_NAME = "txx_crash_log";
    /** 文件后缀*/
    private static final String FORMAT = ".txt";
    /** 系统默认的UncaughtException处理类*/
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    /** 程序的Context对象*/
    private Context mContext;
    /** 用来存储设备信息和异常信息*/
    private Map<String, String> infos = new HashMap<String, String>();

    /** 根据时间,定期上传日志*/
    public final static int TIME = 1;
    /** 根据日志大小,上传日志*/
    public final static int MEMORY = 2;
    /** 根据,时间,日志大小,上传日志*/
    public final static int TIME_AND_MEMORY = 3;
    //type
    private int TYPE = 0;
    //first init
    private boolean isFirstInit;
    //first init string
    private final static String IS_FIRST_INIT = "is_first_init";
    //first init time
    private final static String FIRST_INIT_TIME = "first_init_time";
    //时间差
    private int TIME_SIZE;
    //设置的时间间隔
    private int DAYS = 7;


    /**
     * 获取CrashHandler实例 ,单例模式
     */
    private CrashHandler() {
    }
    public static CrashHandler getInstance() {
        return Bulid.single;
    }
    private static class Bulid {
        private static final CrashHandler single = new CrashHandler ();
    }
    /**
     * 初始化
     *
     * @param context
     * @param type    上传模式
     */
    public void init(Context context, int type) {
        mContext = context.getApplicationContext ();
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
        //设置文件保存路径
        logdir = Environment.getExternalStorageDirectory().getAbsolutePath()
                + File.separator + TAG;
        //设置样式
        this.TYPE = type;
        //时间存储
        saveOrGetTimeType();
    }

    /**
     * 初始化
     *
     * @param context 默认按照日志大小,控制日志上传
     */
    public void init(Context context) {
      this.init (context,3);
    }

    /**
     * 获取保存时间信息
     */
    private void saveOrGetTimeType() {
        SharedPreferences crash = mContext.getSharedPreferences(SP_NAME,MODE_PRIVATE );
        isFirstInit = crash.getBoolean(IS_FIRST_INIT, true);
        if (isFirstInit) {
            SharedPreferences.Editor edit = crash.edit();
            edit.putBoolean(IS_FIRST_INIT, false);
            edit.putLong(FIRST_INIT_TIME, getTimeToLong());
            edit.commit();
        } else {
            long firstTime = crash.getLong(FIRST_INIT_TIME, 0);
            long currentTime = getTimeToLong();
            TIME_SIZE = (int) ((currentTime - firstTime) / 86400);
        }
    }

    /**
     * 获取时间
     */
    private long getTimeToLong() {
        try {
            long time = Calendar.getInstance().getTimeInMillis();
            return time;
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 当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.i(TAG, "uncaughtException: " + e.toString());
//            }
//            //退出程序
//            android.os.Process.killProcess(android.os.Process.myPid());
//            System.exit(1);
        }
    }


    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable ex) {
        Log.e (TAG,"-----------------------------------app 出错了  start----------------------------------------");
        //如果没有SD卡,直接返回
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
           Log.e (TAG,"没有外置存储挂载");
            return false;
        }

        if (ex == null) {
            Log.e (TAG,"ex + null");
            return false;
        }else {
            Log.e (TAG,"ex "+ex.toString ());
        }
       //收集设备参数信息
        collectDeviceInfo(mContext);
        //保存日志文件
        saveCrashInfoToFile(ex);
        Log.e (TAG,"-----------------------------------app 出错了  end----------------------------------------");
        return true;
    }

    /**
     * 并不应该每次崩溃都进行日志上传
     *
     * @param file
     */
    private void svaeCrashInfoToServer(File file,StringBuffer stringBuilder) {
        Log.e (TAG,"日志已经很大了,应该上传服务器");

     //上传服务器后 清空数据
        WriteContentTypt(file,stringBuilder.toString (),true);


//        new Thread() {
//            @Override
//            public void run() {
//                Looper.prepare();
//                Toast.makeText(mContext, "日志已经很大了,应该上传服务器", Toast.LENGTH_LONG).show();
//                Looper.loop();
//            }
//        }.start();
    }

    /**
     * 收集设备参数信息
     *
     * @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("App Version", versionName);
                infos.put("App versionCode", versionCode);
            }

            //android版本号
            infos.put("OS Version: ",Build.VERSION.RELEASE+"___"+Build.VERSION.SDK_INT);
            //手机制造商
            infos.put(" 产品品牌--Vendor:: ",Build.MANUFACTURER+"_____"+Build.BRAND);
            //手机型号
            infos.put(" Model:: ",Build.MODEL);
            infos.put(" 设备序列号: ",Build.SERIAL);
            //cpu架构
            infos.put(" CPU ABI: ",Build.CPU_ABI);
            infos.put("产品型号 : ",Build.PRODUCT);
        } catch (NameNotFoundException e) {
            Log.i(TAG, "collectDeviceInfo error NameNotFoundException: " + e.toString());
        }
        Log.e (TAG,"设备信息 :"+infos.toString ());
    }

    /**
     * 保存错误信息到文件中,应该清楚过长时间的错误信息
     *
     * @param ex
     * @return 返回文件名称, 便于将文件传送到服务器
     */
    private String saveCrashInfoToFile(Throwable ex) {
        // 先获取文件
        File file = getFilePath(logdir, FILE_NAME + FORMAT);
        if(file==null){
            Log.e (TAG,"File 文件 创建失败");
           return "";
        }

        final StringBuffer sb = new StringBuffer();
        Date date = Calendar.getInstance().getTime();
        //获取24 小时制,如果写为hh 获取的下午3点 也是 3点
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String times = sdf.format(date);
        sb.append("---------------------txx  start--------------------------"+"\n");
        //可以追加用户信息
        sb.append(addUserInfo());
        sb.append("crash at time: " + times+"\n");
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }

        try {
            Log.e("错误日志文件路径",""+file.getAbsolutePath());
//            Writer writer = new BufferedWriter (new FileWriter (file));
            Writer writer = new StringWriter ();
            PrintWriter printWriter = new PrintWriter(writer,true);
//            printWriter.println (sb.toString ());
            ex.printStackTrace(printWriter);
            Throwable cause = ex.getCause();
            /** 原因不存在或者未知*/
            while (cause != null) {
                cause.printStackTrace(printWriter);
                cause = cause.getCause();
            }
            printWriter.println ("--------------------end---------------------------");
            String result = writer.toString();
            sb.append(result);
            printWriter.close();
            switch (TYPE) {
                case 1:
                    //写入本地,清空文本
                    WriteContentTypt(file,sb.toString (),true);
                case 2:
                    if (checkIsTimeToPush()) {
                        //保存日志到服务器
                        svaeCrashInfoToServer(file,sb);
                    }
                case 3:
                    //检查日志是否过大
                    if (checkFileIsToBig(file)) {
                        //写入本地,清空文本
//                        WriteContentTypt(file,sb.toString (),false);
                        //保存日志到服务器
                        svaeCrashInfoToServer(file,sb);

                    } else {
                        //写入本地,追加文本
                        WriteContentTypt(file,sb.toString (),false);
                    }
                    break;
                default:
                    break;
            }
        }catch (Exception e){
            Log.e (TAG,e.getMessage ().toString ());
        }finally {

            //放开以上注释,请删除此行
            MediaScannerConnection.scanFile(mContext.getApplicationContext (), new String[]{ logdir }, null, new MediaScannerConnection.OnScanCompletedListener() {
                @Override
                public void onScanCompleted(final String path, final Uri uri) {
                    Log.i(TAG, "MediaScannerConnection----- onScanCompleted ");
                }
            });
        }


        return null;
    }

    /**
     * 按照时间周期上传日志
     */
    private boolean checkIsTimeToPush() {
        if (TIME_SIZE >= DAYS) {
            return true;
        } else {
            return false;
        }
    }

    private String addUserInfo() {
     
          return "UserInfo: 无用户信息"+"\n";
       
    }

    private boolean checkFileIsToBig(File file) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            int fileSize = fis.available();
            if (fileSize > 1024 * 1024 * 5) {
                return true;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 创建文件
     *
     * @param filePath 文件路径
     * @param fileName 文件名称
     * @return
     */
    public  File getFilePath(String filePath,
                                   String fileName) {
        File file = new File(filePath, fileName);
        if (file.exists()) {
            return file;
        } else {
            file = null;
            makeRootDirectory(filePath);//创建文件夹
            try {
                file = new File(filePath + File.separator + fileName);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e (TAG,"getFilePathy Exception :"+e.toString ());
            }
            return file;
        }
    }

    /**
     * 创建根目录
     *
     * @param filePath
     */
    public  void makeRootDirectory(String filePath) {
        File file = null;
        try {
            file = new File(filePath);
            if (!file.exists()) {
                file.mkdir();
            }
        } catch (Exception e) {
            Log.e (TAG,"makeRootDirectory Exception :"+e.toString ());
        }
    }

    /**
     * 写入本地文件
     *
     * @param file    文件
     * @param sb      内容
     * @param isClean 是否清空,true:清空,false:保留
     */
    public void WriteContentTypt(File file, String sb, boolean isClean) {
        try {
//            FileOutputStream fos = new FileOutputStream(file, !isClean);
//            fos.flush();
//            for (char c : sb.toString().toCharArray()) {
//                fos.write(c);
//            }
//            fos.flush ();
//            fos.close();
            OutputStreamWriter oStreamWriter = new OutputStreamWriter(new FileOutputStream(file,!isClean), "utf-8");
            oStreamWriter.append(sb);
            oStreamWriter.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Log.i(TAG, "WriteContentTypt  FileNotFoundException" );
        } catch (IOException e) {
            e.printStackTrace();
            Log.i(TAG, "WriteContentTypt  IOException" );
        }
        Log.i(TAG, "WriteContentTypt: " + sb.toString());
    }



    private void zip(String src, String dest) throws IOException { //压缩文件夹,为上传做准备。节省流量。
        ZipOutputStream out = null;
        File outFile = new File(dest);
        File fileOrDirectory = new File(src);
        out = new ZipOutputStream(new FileOutputStream(outFile));
        if (fileOrDirectory.isFile()) {
            zipFileOrDirectory(out, fileOrDirectory, "");
        }else {
            File[] entries = fileOrDirectory.listFiles();
            for (int i = 0; i < entries.length; i++) {
                zipFileOrDirectory(out, entries[i], "");
            }
        }
        if(null != out){
            out.close();
        }
    }

    private  void zipFileOrDirectory(ZipOutputStream out, File fileOrDirectory, String curPath) throws IOException {
        FileInputStream in = null;
        if (!fileOrDirectory.isDirectory()){
            byte[] buffer = new byte[4096];
            int bytes_read;
            in = new FileInputStream(fileOrDirectory);
            ZipEntry entry = new ZipEntry (curPath + fileOrDirectory.getName());
            out.putNextEntry(entry);
            while ((bytes_read = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytes_read);
            }
            out.closeEntry();
        }else{
            File[] entries = fileOrDirectory.listFiles();
            for (int i = 0; i < entries.length; i++) {
                zipFileOrDirectory(out, entries[i], curPath + fileOrDirectory.getName() + "/");
            }
        }
        if (null != in){
            in.close();
        }
    }
    public  void unZip(File srcFile,File desFile) throws IOException {
        GZIPInputStream zis= null;
        FileOutputStream fos = null;
        try {
            //创建压缩输入流,传入源文件
            zis = new GZIPInputStream(new FileInputStream(srcFile));
            //创建文件输出流,传入目标文件
            fos = new FileOutputStream(desFile);
            byte[] buffer= new byte[1024];
            int len= -1;
            //利用IO流写入写出的形式将压缩源文件解压到目标文件中
            while ((len= (zis.read(buffer)))!= -1) {
                fos.write(buffer,0, len);
            }
        }finally{
            close(zis);
            close(fos);
        }
    }

    public  void close(Closeable... closeIO) {
        for(Closeable clo:closeIO) {
            if(clo!=null) {
                try {
                    clo.close();
                } catch (IOException e) {
                    e.printStackTrace ();
                    Log.e (TAG,"关闭发生异常"+e);
                }
            }
        }
    }
}

上一篇下一篇

猜你喜欢

热点阅读