一、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日志删除
