Android Crash捕获及处理

一、Android Crash说明
程序因未捕获的异常而突然终止,系统会调用UncaughtExceptionHandler接口来处理未被程序正常捕获的异常,该接口含有UncaughtExceptionHandler方法,UncaughtExceptionHandler方法回传了 Thread 和 Throwable 两个参数。

public class CrashHandler implements Thread.UncaughtExceptionHandler{

    @Override
    public void uncaughtException(Thread t, Throwable e) {

    }
}

只需要实现UncaughtExceptionHandler接口,并调用Thread.setDefaultUncaughtExceptionHandler()方法,即可以获取到未捕获的异常,并可以进行相应的crash上报

二、实现思路
首先收集产生崩溃的手机信息,因为Android的样机种类繁多,很可能某些特定机型下会产生莫名的bug,需要将手机的信息和崩溃信息手机并上传,以便于在后续版本中进行修复。同时也可以在Debug模式下将Crash日志写入到本地文件中,便于及时分析和处理。

三、实现方法

public class CrashHandler implements UncaughtExceptionHandler {
    /**
     * crash文件地址,后续可以扫描这个地址,将crash上报至服务器
     */
    public static final String CRASH_PATH = "/crash/log/";

    /**
     * mDefaultHandler
     */
    private UncaughtExceptionHandler mDefaultHandler;

    /**
     * instance
     */
    private static CrashHandler instance = new CrashHandler();

    /**
     * infos
     */
    private Map<String, String> infos = new HashMap<String, String>();

    private CrashHandler() {
    }

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

    /**
     * 初始化
     *
     */
    public void init() {
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        // 设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        if (!handleException(throwable)) {
            mDefaultHandler.uncaughtException(thread, throwable);
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param exception
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable exception) {
        if (exception == null) {
            return false;
        }
        collectDeviceInfo();
        writeCrashInfoToFile(exception);
        return true;
    }

    /**
     * 相关的crash信息
     */
    public void collectDeviceInfo() {
        infos.put(CrashCollectionParameter.VERSION_NAME, BaseConfig.versionName);
        infos.put(CrashCollectionParameter.VERSION_CODE, String.valueOf(BaseConfig.versionCode));
        infos.put(CrashCollectionParameter.CRASH_TIME, new SimpleDateFormat().format(new Date()));
        infos.put(CrashCollectionParameter.DEVICE_ID, BaseConfig.deviceId);
        infos.put(CrashCollectionParameter.NET_TYPE, BaseConfig.getNetwork());
        infos.put(CrashCollectionParameter.APP_CHANNEL, BaseConfig.channel);
        infos.put(CrashCollectionParameter.SYSTEM_VERSION, BaseConfig.os);
        infos.put(CrashCollectionParameter.DEVICE_RESOLUTION, BaseConfig.resolution);
        infos.put(CrashCollectionParameter.APP_MODE, BaseConfig.appMode);

        // 获取Build.class中定义的基本信息
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param ex 将崩溃写入文件系统
     */
    private void writeCrashInfoToFile(Throwable ex) {
        final StringBuffer sb = new StringBuffer();
        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);

        // 将crash信息写入SD卡的Log日志里面
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            String sdcardPath = Environment.getExternalStorageDirectory().getPath();
            final String filePath = sdcardPath + CRASH_PATH;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    writeLog(sb.toString(), filePath);
                }
            }).start();
        }
    }

    /**
     * @param log
     * @param name
     * @return 将crash信息写入文件,返回写入的文件名称,便于后续将crash上传至服务器
     */
    private void writeLog(String log, String name) {
        CharSequence timestamp = new Date().toString().replace(" ", "");
        String filename = name + timestamp + ".log";

        File file = new File(filename);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try {
            file.createNewFile();
            FileWriter fw = new FileWriter(file, true);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(log);
            bw.newLine();
            bw.close();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Crash相关参数,可以根据需要收集更多参数

public final class CrashCollectionParameter {
    /**
     * 版本信息
     */
    public static final String VERSION_NAME = "versionName";

    /**
     * 版本号
     */
    public static final String VERSION_CODE = "versionCode";

    /**
     * Crash发生时间,应尽量使用服务器时间
     */
    public static final String CRASH_TIME = "crashTime";

    /**
     * 设备DeviceID
     */
    public static final String DEVICE_ID = "deviceID";

    /**
     * 网络类型
     */
    public static final String NET_TYPE = "netType";

    /**
     * 渠道类型
     */
    public static final String APP_CHANNEL = "appChannel";

    /**
     * 系统版本
     */
    public static final String SYSTEM_VERSION = "systemVersion";

    /**
     * 分辨率
     */
    public static final String DEVICE_RESOLUTION = "deviceResolution";

    /**
     * App模式,用于区分是Debug、Release等
     */
    public static final String APP_MODE = "appMode";

}

在APP启动时需要获取设备相关信息

public final class BaseConfig {
    public static int width;
    public static int height;
    public static String resolution;
    public static String versionName;
    public static int versionCode;
    public static String channel;
    public static String appMode;
    public static String deviceId = "000000000000000";
    public static String networkType;
    public static String networkSubtype;
    public static String os;

    public static void init(Context context) {
        try {
            PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(),
                    PackageManager.GET_CONFIGURATIONS);
            versionName = pi.versionName;
            versionCode = pi.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
        }

        TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        deviceId = tm.getDeviceId();

        initDisplay(context);
        initNetwork(context);

        try {
            ApplicationInfo applicationInfo = context.getPackageManager()
                    .getApplicationInfo(context.getPackageName(),
                            PackageManager.GET_META_DATA);
            channel = applicationInfo.metaData.getString("channel");
            appMode = applicationInfo.metaData.getString("appMode");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        os = Build.VERSION.RELEASE;
    }

    public static void initNetwork(Context context) {
        NetworkInfo ni = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
        networkType = ni == null ? "" : ni.getTypeName();
        networkSubtype = ni == null ? "" : ni.getSubtypeName();
    }

    public static String getNetwork() {
        String net;
        if ("MOBILE".equals(networkType)) {
            net = TextUtils.isEmpty(networkSubtype) ? networkType : networkSubtype;
        } else {
            net = networkType;
        }
        return net;
    }

    public static void initDisplay(Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        width = metrics.widthPixels;
        height = metrics.heightPixels;
        resolution = new StringBuilder().append(width).append('*').append(height).toString();
    }
}

同时需要在Application启动时初始化BaseConfig和CrashHandler

public class CrashTestApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        BaseConfig.init(this);
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init();
    }
}

四、后续操作
现在crash都已经以日志的形式保存在了SDCrad中,后续可以在用户连接WiFi后,遍历crash日志目录,将crash日志上传至服务器,上传成功后还可以根据需要将手机上的crash日志删除

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值