异常捕获上传服务器

本文介绍了一种用于Android应用程序的全局异常捕获机制。该机制不仅能够在应用程序出现未捕获异常时给予用户友好的提示,还能收集异常信息并发送到后台以便开发者及时修复问题。

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

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import com.mob.tools.utils.SharePrefrenceHelper;
import com.willkong.p2pclient.util.UIUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 作用: 程序中的未捕获的全局异常的捕获(单例)
 * <p>
 * 解决两个问题:
 * 1.当出现未捕获的异常时,能够给用户一个相对友好的提示
 * 2.在出现异常时,能够将异常信息发送给后台,便于在后续的版本中解决bug
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {

    //系统默认的处理未捕获异常的处理器
    private Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    //单例模式:(懒汉式)
    //本身实例化未捕获异常的处理器的操作就是系统在一个单独的线程中完成的,所有不涉及到
    //多线程的问题,所以使用懒汉式更好!
    private CrashHandler() {

    }

    private static CrashHandler crashHandler = null;

    public static CrashHandler getInstance() {
        if (crashHandler == null) {
            //用来解决多并发的问题
            synchronized (CrashHandler.class) {
                if (crashHandler == null) {
                    crashHandler = new CrashHandler();
                }
            }
        }
        return crashHandler;
    }

    private Context mContext;

    public void init(Context mContext) {
        this.mContext = mContext;
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        //将当前类设置为出现未捕获异常的处理器
        Thread.currentThread().setDefaultUncaughtExceptionHandler(this);
    }

    //一旦系统出现未捕获的异常,就会调用如下的回调方法
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

//        Log.e("TAG", "亲,出现了未捕获的异常了!" + ex.getMessage());
        new Thread() {
            public void run() {
                //prepare()和loop()之间的操作就是在主线程中执行的!
                //在android系统中,默认情况下,一个线程中是不可以调用Looper进行消息的处理的。除非是主线程
                Looper.prepare();
                Toast.makeText(UIUtils.getContext(), "亲,出现了未捕获的异常了!", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        //收集异常信息
        collectionException(ex);
        //异常处理--新的处理方法
        handleException(ex);
        //让系统默认处理
        defaultUncaughtExceptionHandler.uncaughtException(thread, ex);

        try {
            Thread.sleep(2000);

            //移除当前activity
            ActivityManager.getInstance().removeCurrent();
            //结束当前的进程
            android.os.Process.killProcess(android.os.Process.myPid());
            //结束虚拟机
            System.exit(0);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 异常处理
     *
     * @param ex
     */
    private void handleException(Throwable ex) {
        //1.获取信息
        //1.1 崩溃信息
        //1.2 手机信息
        //1.3 版本信息
        //2.  写入文件
        String crashFileName = saveInfoToSD(ex);
        //缓存崩溃日志文件名
        cacheCrashFile(crashFileName);
        //上传服务器
        sendToSever();
    }

    /**
     * 上传到服务器
     */
    private void sendToSever() {
        File crashFile = getCrashFile();
        if (crashFile.exists()){
            //上传到服务器


            //打印出异常信息
            try {
                InputStreamReader fileReader = new InputStreamReader(new FileInputStream(crashFile));
                char[] buffer = new char[1024];
                int len = 0;
                while ((len = fileReader.read(buffer))!=-1){
                    String str = new String(buffer,0,len);
                    Log.d("TAG",str);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 缓存崩溃日志文件
     *
     * @param fileName
     */
    private void cacheCrashFile(String fileName) {
        SharedPreferences sp = mContext.getSharedPreferences("crash",Context.MODE_PRIVATE);
        sp.edit().putString("CRASH_FILE_NAME",fileName).commit();
    }

    /**
     * 获取崩溃文件
     * @return
     */
    public File getCrashFile(){
        String crashFileName = mContext.getSharedPreferences("crash",Context.MODE_PRIVATE).getString("CRASH_FILE_NAME","");
        return new File(crashFileName);
    }

    /**
     * 保存到SD卡
     *
     * @param ex
     * @return
     */
    private String saveInfoToSD(Throwable ex) {
        String fileName = null;
        StringBuffer sb = new StringBuffer();
        //1.手机信息 + 应用信息 -->obtainSimpleInfo(mContext)
        for (Map.Entry<String, String> entry : obtainSimpleInfo(mContext).entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key).append(" = ").append(value).append("\n");
        }
        //2.崩溃的详细信息
        sb.append(obtainExceptionInfo(ex));

        //保存文件 手机应用的目录,并没有拿手机sdCard目录,6.0以上需要动态申请权限
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            File dir = new File(mContext.getFilesDir()+File.separator+"crash"+File.separator);

            //先删除之前的异常信息
            if (dir.exists()){
                //删除该目录下的所有子文件
                deleteDir(dir);
            }

            //再从新创建文件夹
            if (!dir.exists()){
                dir.mkdir();
            }

            try {
                fileName = dir.toString()+File.separator+getAssignTime("yyyy_MM_dd_HH_mm")+".txt";
                FileOutputStream fos = new FileOutputStream(fileName);
                fos.write(sb.toString().getBytes());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return fileName;
    }

    private String getAssignTime(String dateFormatStr) {
        DateFormat dateFormat = new SimpleDateFormat(dateFormatStr);
        String datestr = dateFormat.format(new Date());
        return datestr;
    }

    /**
     * 删除文件
     * @param dir
     */
    private boolean deleteDir(File dir) {
        if (dir.isDirectory()){
            File[] children = dir.listFiles();
            //递归删除目录中的子目录下
            for (File child : children) {
                child.delete();
            }
        }
        //目录此时为空,可以删除
        return true;
    }

    /**
     * 获取系统未捕捉的错误信息即崩溃的详细信息
     * @param throwable
     * @return
     */
    private String obtainExceptionInfo(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        throwable.printStackTrace(printWriter);
        printWriter.close();
        return stringWriter.toString();
    }

    /**
     * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中
     *
     * @param context
     * @return
     */
    private HashMap<String, String> obtainSimpleInfo(Context context) {
        HashMap<String, String> map = new HashMap<>();
        PackageManager mPackageManager = context.getPackageManager();
        PackageInfo mPackageInfo = null;
        try {
            mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        map.put("versionName", mPackageInfo.versionName);
        map.put("versionCode", "" + mPackageInfo.versionCode);
        map.put("MODEL", "" + Build.MODEL);
        map.put("SDK_INT", "" + Build.VERSION.SDK_INT);
        map.put("PRODUCT", "" + Build.PRODUCT);
        map.put("MOBLE_INFO", getMobileInfo());
        return map;
    }

    /**
     * 获取手机信息
     *
     * @return
     */
    public static String getMobileInfo() {
        StringBuffer sb = new StringBuffer();
        try {
            //利用反射获取Build的所有属性
            Field[] fields = Build.class.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String name = field.getName();
                String value = null;

                value = field.get(null).toString();

                sb.append(name + " = " + value);
                sb.append("\n");
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    /**
     * 收集异常信息
     * @param ex
     */
    private void collectionException(Throwable ex) {
        final String exMessage = ex.getMessage();
        //收集具体的客户的手机、系统的信息
        final String message = Build.DEVICE + ":" + Build.MODEL + ":" + Build.PRODUCT + ":" + Build.VERSION.SDK_INT;

        //发送给后台此异常信息
        new Thread() {
            public void run() {
                //需要按照指定的url,访问后台的sevlet,将异常信息发送过去
                Log.e("TAG", "exception = " + exMessage);
                Log.e("TAG", "message = " + message);
            }
        }.start();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值