Android 工具类 异常处理类CrashHandler

1.整体分析

1.1.源代码如下,可以直接Copy。  

public class CrashHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "异常处理";
    private static final boolean DEBUG = true;

    private static final String PATH = Environment.getExternalStorageDirectory().getPath() +
            "/test/log/";
    private static final String FILE_NAME = "crash";

    //log文件的后缀名
    private static final String FILE_NAME_SUFFIX = ".txt";

    private static CrashHandler sInstance = new CrashHandler();

    //系统默认的异常处理(默认情况下,系统会终止当前的异常程序)
    private Thread.UncaughtExceptionHandler mDefaultCrashHandler;

    private Context mContext;

    //构造方法私有,防止外部构造多个实例,即采用单例模式
    private CrashHandler() {
    }

    public static CrashHandler getInstance() {
        return sInstance;
    }

    //这里主要完成初始化工作
    public void init(Context context) {
        //获取系统默认的异常处理器
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
        //将当前实例设为系统默认的异常处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
        //获取Context,方便内部使用
        mContext = context.getApplicationContext();

    }

    /**
     * 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用#uncaughtException方法
     * thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息。
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        try {
            //导出异常信息到SD卡中
            dumpExceptionToSDCard(ex);
            //这里可以通过网络上传异常信息到服务器,便于开发人员分析日志从而解决bug
            uploadExceptionToServer();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //打印出当前调用栈信息
        ex.printStackTrace();

        //如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己
        if (mDefaultCrashHandler != null) {
            mDefaultCrashHandler.uncaughtException(thread, ex);
        } else {
            Process.killProcess(Process.myPid());
        }

    }

    private void dumpExceptionToSDCard(Throwable ex) throws IOException {
        //如果SD卡不存在或无法使用,则无法把异常信息写入SD卡
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            if (DEBUG) {
                Log.w(TAG, "sdcard unmounted,skip dump exception");
                return;
            }
        }

        File dir = new File(PATH);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        long current = System.currentTimeMillis();
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
        //以当前时间创建log文件
        File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);
        if (!file.exists()) {
            Log.i(TAG, "文件名称为:"+file.getName());
        }else{
            Log.i(TAG, "文件名称为:"+file.getName());
        }

        try {
            if(file.createNewFile()){
                Log.i(TAG, "文件创建成功:名称为:"+file.getName());
            }
            if(file.exists()){
                Log.i(TAG, "文件exists");
            }
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
            //导出发生异常的时间
            pw.println(time);

            //导出手机信息
            dumpPhoneInfo(pw);

            pw.println();
            //导出异常的调用栈信息
            ex.printStackTrace(pw);

            pw.close();
        } catch (Exception e) {
            Log.e(TAG, "dump crash info failed");
        }
    }

    private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
        //应用的版本名称和版本号
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager
                .GET_ACTIVITIES);
        pw.print("App Version: ");
        pw.print(pi.versionName);
        pw.print('_');
        pw.println(pi.versionCode);

        //android版本号
        pw.print("OS Version: ");
        pw.print(Build.VERSION.RELEASE);
        pw.print("_");
        pw.println(Build.VERSION.SDK_INT);

        //手机制造商
        pw.print("Vendor: ");
        pw.println(Build.MANUFACTURER);

        //手机型号
        pw.print("Model: ");
        pw.println(Build.MODEL);

        //cpu架构
        pw.print("CPU ABI: ");
        pw.println(Build.CPU_ABI);
    }

    private void uploadExceptionToServer() {
        //TODO Upload Exception Message To Your Web Server
    }

}
View Code

1.2.实现的方法如下

  • 继承了Thread.UncaughtExceptionHandler,所以实现override方法uncaughtException
  • 将异常信息写入SD卡,实现dumpExceptionToSDCard
  • 打印应用的版本名称和版本号和手机制造商、手机型号及cpu架构
  • 上传到服务器,这个函数要看情况需不需要 

1.3.需要动态申请写文件的权限。

  参考文章:http://blog.csdn.net/wuqingyou_w/article/details/60138807

private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.WRITE_EXTERNAL_STORAGE" };


    public static void verifyStoragePermissions(Activity activity) {

        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

  将这个方法放在onCreate或者其他必然执行的地方即可。


2.局部分析

2.1.首先是成员变量

  

  TAG用来调试时输出的。

  DEBUG是为了判断当前项目处于开发模式还是调试模式。

  PATH是日志文件存储路径,前面不能变,后面随意。

  注意点:我的手机一开始我找不到这个文件。

  因为我在Logcat上输出了这个Environment.getExternalStorageDirectory().getPath为

    /storage/emulated/0(不同手机可能不同)

  然后我在手机上真的找到了这个路径,但是这个目录下始终没有日志文件

  最后我才知道这个Environment.getExternalStorageDirectory().getPath就是手机的主目录

  在主目录下就有新建的文件夹以及目录了。

  FILE_NAME是日志文件的前缀

  FILE_NAME_SUFFIX是日志文件的后缀

  sInstance是新建的CrashHandler的一个实例

  mDefaultCrashHandler是系统默认的异常处理,系统会终止当前的异常程序

  mContext是上下文

2.2.构造方法

  

2.3.获取单例

  

2.4.完成初始化工作

  

  还是要获取系统默认的异常处理器

  然后将当前实例设为系统默认的异常处理器,这样每次都会先执行这里了

  获取一下上下文,方便内部使用

2.5.这里是实现的接口方法uncaughtException

  

  可以看到,这里先将信息导出到SD卡中,然后上传异常信息到服务器(这里需要看情况写了)

  然后交给系统默认的异常处理器去处理后事。没有默认的,就自己结束自己吧。

2.6.如何写到SD卡中呢?

  

  

  首先获取SD卡默认路径

  先利用file.mkdirs()方法来创建一系列的路径,mkdir()只能创建当前目录的文件夹,mkdirs()可以无限了。

  然后利用系统当前时间可以创建唯一的名称

  然后利用createNewFile()方法,可以创建一个文件了

  之后利用PrintWriter来向文件中写入数据了。

  PrintWriter pw=new PrintWriter(new BufferedWriter(new FileWriter(file)))这种方法来创建流

  然后可以用pw.println()向文件中写入数据了。

  最后要将pw关闭。

2.7.如何向文件中写入手机信息?

  

  这里利用上下文获取包管理器,然后有包管理器获取包信息,然后就可以获取版本号及名称。

  手机制造商和手机型号和cup架构需要Build类获取。

2.8.上传到服务器需要自己看情况写

  


3.使用方法及效果

3.1.首先在项目的Application中初始化

  在onCreate函数中添加==>

  CrashHandler.getInstance().init(this);

3.2.然后只要发生异常,都会将异常信息发送到日志文件

      

        


既然选择了,便不顾风雨兼程。Just follow yourself.
原文地址:https://www.cnblogs.com/Jason-Jan/p/7904551.html