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