崩溃日志的捕获有很多种方式,最直接的就是接入三方的捕获,但是由于某些原因或者说某些原因导致不能准确的定位到崩溃的位置,也为了使应用程序测试时更好的定位崩溃位置(测试机多的时候不可能每个都去打LOG和断点)
其实原理很简单,应用出现异常后,会由默认的异常处理器来处理异常,我们要做的就是把这个任务接管过来,自己处理异常,包括收集日志,保存到本地,然后上传到服务器。
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Date;
/**
* <p>类描述:异常捕获处理器
* @author ma
*/
public class CrashHandler implements UncaughtExceptionHandler {
//每个小时的日志都是记录在同一个文件
private final static String LOG_FILE_CREATE_TIME_FORMAT = "yyyy-MM-dd_HH";
private final static String LOG_FILE_SUFFIX = "_crash.log";
//日志记入的时间
private final static String LOG_RECORD_TIME_FORMAT ="yyyy-MM-dd HH:mm:ss";
private UncaughtExceptionHanlderListener mHanlderListener;
private Context mContext;
private static CrashHandler sInstance;
//设置日志所在文件夹路径
private String mLogDir;
private StringBuffer sb;
public static CrashHandler getInstance(Context context){
if (sInstance == null) {
sInstance = new CrashHandler(context);
}
return sInstance;
}
private CrashHandler(Context context){
mContext = context;
Thread.setDefaultUncaughtExceptionHandler(this);
}
/* (non-Javadoc)
* @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable)
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Log.e("ssssssssss","cuowu1");
hanldeException(ex);
if (mHanlderListener != null) {
mHanlderListener.handlerUncaughtException(sb);
}
}
/**
* 设置外部要处理异常发生时操作监听器
* @param hanlderListener : {@link UncaughtExceptionHanlderListener}
*/
public void setHanlderListener(UncaughtExceptionHanlderListener hanlderListener) {
this.mHanlderListener = hanlderListener;
}
/**
* 设置日志所在文件夹路径
* @param logDirPath
*/
public void setCrashLogDir(String logDirPath){
mLogDir = logDirPath;
}
//崩溃日志的保存操作
private void hanldeException(Throwable ex){
Log.e("ssssssssss","cuowu2");
if (ex == null) {
return ;
}
if (CrashUtils.isSDCardAvaiable(mContext) && !TextUtils.isEmpty(mLogDir)) {
saveCrashInfoToFile(ex);
}
}
//保存错误信息到文件中
private void saveCrashInfoToFile(Throwable ex) {
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
String content = info.toString();
printWriter.close();
StringBuffer sb = new StringBuffer();
long time = System.currentTimeMillis();
sb.append(">>>>>>>>>>>>>>时间 ");
sb.append(CrashUtils.formatDate(new Date(time), LOG_RECORD_TIME_FORMAT));
sb.append(">>>>>>>>>>>>>> ");
sb.append("\r\n");
sb.append(">>>>>>>>>>>>>>手机型号 ");
sb.append(CrashUtils.getPhoneModel(mContext));
sb.append(">>>>>>>>>>>>>> ");
sb.append("\r\n");
sb.append(">>>>>>>>>>>>>>IMEI号 ");
sb.append(CrashUtils.getPhoneIMEI(mContext));
sb.append(">>>>>>>>>>>>>> ");
sb.append("\r\n");
sb.append(">>>>>>>>>>>>>>应用版本号 ");
sb.append(CrashUtils.getAppVersionCode(mContext));
sb.append(">>>>>>>>>>>>>> ");
sb.append("\r\n");
sb.append(">>>>>>>>>>>>>>可用/总内存 ");
sb.append(CrashUtils.getAvailMemory(mContext)+"/"+ CrashUtils.getTotalMemory(mContext));
sb.append(">>>>>>>>>>>>>> ");
sb.append("\r\n");
sb.append(">>>>>>>>>>>>>>IP ");
sb.append(CrashUtils.getLocalIpAddress());
sb.append(">>>>>>>>>>>>>> ");
sb.append("\r\n");
sb.append(content);
sb.append("\r\n");
sb.append("\r\n");
this.sb =sb;
CrashUtils.writeToFile(mLogDir,generateLogFileName("error",time), sb.toString(), "utf-8");
return ;
}
//生成日志文件名
private String generateLogFileName(String prefix, long time){
StringBuilder sb = new StringBuilder();
sb.append(prefix);
sb.append("_");
sb.append(CrashUtils.formatDate(new Date(time),LOG_FILE_CREATE_TIME_FORMAT));
sb.append(LOG_FILE_SUFFIX);
return sb.toString();
}
/**
* 未捕获异常的处理监听器
*/
public static interface UncaughtExceptionHanlderListener{
/**
* 当获取未捕获异常时的处理
* 一般用于关闭界面和数据库、网络连接等等
*/
public void handlerUncaughtException(StringBuffer sb);
}
}
CrashUtils.java
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.support.v4.app.ActivityCompat;
import android.telephony.TelephonyManager;
import android.text.format.Formatter;
import android.util.Log;
import com.blankj.utilcode.util.Utils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
/**
* 版本 帮助类
*
* @author ma
*/
public class CrashUtils {
// 默认为北京时间对应的东八区
private static final TimeZone GMT = TimeZone.getTimeZone("GMT+8");
// SD卡的最小剩余容量大小1MB
private final static long DEFAULT_LIMIT_SIZE = 1;
private static String imei;
/获取手机信息/
/**
* 获取应用版本号
*
* @param mContext
* @return
*/
public static int getAppVersionCode(Context mContext) {
try {
PackageManager manager = mContext.getPackageManager();
PackageInfo info = manager.getPackageInfo(mContext.getPackageName(), 0);
int version = info.versionCode;
return version;
} catch (Exception e) {
return -1;
}
}
/**
* 获取手机IMEI号
*
* @param mContext
* @return
*/
public static String getPhoneIMEI(Context mContext) {
if (ActivityCompat.checkSelfPermission(Utils.getApp(), Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
imei = telephonyManager.getDeviceId();
}
return imei;
}
/**
* 获取手机型号
*
* @param mContext
* @return
*/
public static String getPhoneModel(Context mContext) {
// 取得 android 版本
String manufacturer = "";
String model = "";
try {
Class<Build> build_class = android.os.Build.class;
// 取得牌子
Field manu_field = build_class.getField("MANUFACTURER");
manufacturer = (String) manu_field.get(new android.os.Build());
// 取得型號
Field field2 = build_class.getField("MODEL");
model = (String) field2.get(new android.os.Build());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return manufacturer + " " + model;
}
/**
* 获取本地IP
*
* @return
*/
public static String getLocalIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
}
return null;
}
/**
* 获取android当前可用内存大小
*/
public static String getAvailMemory(Context mContext) {// 获取android当前可用内存大小
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
am.getMemoryInfo(mi);
//mi.availMem; 当前系统的可用内存
return Formatter.formatFileSize(mContext, mi.availMem);// 将获取的内存大小规格化
}
/**
* 获得系统总内存
*/
public static String getTotalMemory(Context mContext) {
String str1 = "/proc/meminfo";// 系统内存信息文件
String str2;
String[] arrayOfString;
long initial_memory = 0;
try {
FileReader localFileReader = new FileReader(str1);
BufferedReader localBufferedReader = new BufferedReader(
localFileReader, 8192);
str2 = localBufferedReader.readLine();// 读取meminfo第一行,系统总内存大小
arrayOfString = str2.split("\\s+");
for (String num : arrayOfString) {
Log.i(str2, num + "\t");
}
initial_memory = Integer.valueOf(arrayOfString[1]).intValue() * 1024;// 获得系统总内存,单位是KB,乘以1024转换为Byte
localBufferedReader.close();
} catch (IOException e) {
}
return Formatter.formatFileSize(mContext, initial_memory);// Byte转换为KB或者MB,内存大小规格化
}
/时间格式化/
public static String formatDate(Date date, String pattern) {
if (date == null) {
throw new IllegalArgumentException("date is null");
}
if (pattern == null) {
throw new IllegalArgumentException("pattern is null");
}
SimpleDateFormat formatter = formatFor(pattern);
return formatter.format(date);
}
private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() {
@Override
protected SoftReference<Map<String, SimpleDateFormat>> initialValue() {
return new SoftReference<Map<String, SimpleDateFormat>>(
new HashMap<String, SimpleDateFormat>());
}
};
private static SimpleDateFormat formatFor(String pattern) {
SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
Map<String, SimpleDateFormat> formats = ref.get();
if (formats == null) {
formats = new HashMap<String, SimpleDateFormat>();
THREADLOCAL_FORMATS.set(new SoftReference<Map<String, SimpleDateFormat>>(formats));
}
SimpleDateFormat format = formats.get(pattern);
if (format == null) {
format = new SimpleDateFormat(pattern, Locale.CHINA);
format.setTimeZone(GMT);
formats.put(pattern, format);
}
return format;
}
/将文本信息写入文件/
public static void writeToFile(String dir, String fileName, String content, String encoder) {
File file = new File(dir, fileName);
File parentFile = file.getParentFile();
Log.e("ssssssssss","xieru");
OutputStreamWriter osw = null;
BufferedWriter bw = null;
try {
if (!parentFile.exists()) {
parentFile.mkdirs();
}
if (!file.exists()) {
file.createNewFile();
}
osw = new OutputStreamWriter(new FileOutputStream(file, true), encoder);
bw = new BufferedWriter(osw);
bw.append(content);
bw.append("\r\n");
bw.flush();
} catch (IOException e) {
} finally {
closeSilently(bw);
closeSilently(osw);
}
}
/**
* 关闭流操作
*/
public static void closeSilently(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
}
}
}
/判断SD卡是否可用/
public static boolean isSDCardAvaiable(Context context) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
if (getSDFreeSize() > DEFAULT_LIMIT_SIZE) {
return true;
} else {
// ToastUtil.showToast(context, "SD卡容量不足,请检查");
return false;
}
} else {
// ToastUtil.showToast(context, "SD卡不存在或者不可用");
return false;
}
}
/**
* 获取SDCard的剩余大小
*
* @return 多少MB
*/
@SuppressWarnings("deprecation")
public static long getSDFreeSize() {
// 取得SD卡文件路径
File path = Environment.getExternalStorageDirectory();
StatFs sf = new StatFs(path.getPath());
// 获取单个数据块的大小(Byte)
long blockSize = sf.getBlockSize();
// 空闲的数据块的数量
long freeBlocks = sf.getAvailableBlocks();
// 返回SD卡空闲大小
return (freeBlocks * blockSize) / 1024 / 1024; // 单位MB
}
}
在Application类里进行初始化
CrashHandler.getInstance(getApplicationContext()).setCrashLogDir(getCrashLogDir());
getCrashLogDir()方法存储位置(PS:一般存储位置为文件下:Android/Data/包名/files/Download/log 下)
protected String getCrashLogDir() {
// TODO Auto-generated method stub
String sb = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + "/log";
return sb;
}
在MainActivity.java类里人为制造一个崩溃
Integer.parseInt("sss");
对了别忘记申请权限哦
<!-- 访问网络状态 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 外置存储存取权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />