最近测试人员偶尔碰到应用崩溃的情况,但是在开发过程中却没有,所以增加了个记录崩溃日志的功能。
主要记录3类信息,包括设备信息、内存信息、Activity栈信息。
设备信息
使用PackageManager获取PackageInfo,记录版本名称和版本号。
使用android.os.Build.class.getDeclaredFields()获取设备信息。
内存信息
使用ActivityManager获取ActivityManager.MemoryInfo。
- ActivityManager.getMemoryClass():在当前设备中,应用可使用最大内存,单位MB。
- ActivityManager.getLargeMemoryClass():在manifest中设置android:largeHeap=”true”时,应用可使用最大内存,单位MB。
- MemoryInfo.availMem:系统剩余内存
- MemoryInfo.lowMemory:系统是否处于低内存运行
- MemoryInfo.threshold:当系统剩余内存低于该值时处于低内存运行
状态 - MemoryInfo.totalMem:系统总内存
使用android.os.Debug.MemoryInfo获取进程的内存统计数据,以KB为单位。
- android.os.Debug.dumpHprofData():转储dump文件到手机,可使用MAT分析崩溃时应用内存状况。
- Debug.getNativeHeapAllocatedSize():native堆已分配内存,字节为单位。
- Debug.getNativeHeapFreeSize():native堆空闲内存,字节为单位。
- Debug.getNativeHeapSize():native堆总内存大小,字节为单位。
- Debug.MemoryInfo.dalvikPrivateDirty:The private dirty pages used by dalvik.
- Debug.MemoryInfo.dalvikPss:The proportional set size for dalvik.
- Debug.MemoryInfo.dalvikSharedDirty:The shared dirty pages used by dalvik.
- Debug.MemoryInfo.nativePrivateDirty:The private dirty pages used by the native heap.
- Debug.MemoryInfo.nativePss:The proportional set size for the native heap.
- Debug.MemoryInfo.nativeSharedDirty:The shared dirty pages used by the native heap.
- Debug.MemoryInfo.otherPrivateDirty:The private dirty pages used by everything else.
- Debug.MemoryInfo.otherPss:The proportional set size for everything else.
- Debug.MemoryInfo.otherSharedDirty:The shared dirty pages used by everything else.
使用Runtime.getRuntime()获取虚拟机内存信息,字节为单位。
- Runtime.getRuntime().maxMemory():虚拟机可以使用最大内存
- Runtime.getRuntime().freeMemory():运行程序可使用空闲内存
- Runtime.getRuntime().totalMemory():运行程序可使用总内存大小,当这个值接近maxMemory()时可以发生内存溢出。
Activity栈
ActivityManager.getRunningTasks(1)可获得当前运行任务。
RunningTaskInfo对应运行任务。
- RunningTaskInfo.id:任务id
- RunningTaskInfo.description:任务描述
- RunningTaskInfo.numActivities:Activity数量
- RunningTaskInfo.baseActivity:栈底Activity
- RunningTaskInfo.topActivity:栈顶Activity
没有找到获取所有Activity的方法。
代码
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Debug;
import android.os.Environment;
import android.util.Log;
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static CrashHandler mCrashHandler;
private Context mContext;
private UncaughtExceptionHandler mDefaultHandler;
private Map<String, String > infos = new LinkedHashMap<String, String>();
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
public CrashHandler(Context context) {
mContext = context;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
public static CrashHandler getInstance(Context context){
if(mCrashHandler == null){
mCrashHandler = new CrashHandler(context);
}
return mCrashHandler;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
handleException(ex);
if(mDefaultHandler != null){
mDefaultHandler.uncaughtException(thread, ex);
}
}
private boolean handleException(Throwable ex){
if(ex == null){
return false;
}
collectDeviceInfo(mContext);
collectMemoryInfo(mContext);
collectTaskInfo(mContext);
saveCrashInfo2File(ex);
return true;
}
private void collectDeviceInfo(Context ctx){
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
if(pi != null){
String versionName = pi.versionName == null? "null" : pi.versionName;
String versionCode = pi.versionCode + "";
infos.put("versionName", versionName);
infos.put("versionCode", versionCode);
}
} catch (NameNotFoundException e) {
Log.e(TAG, "an error occured when collect package info", e);
}
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
Log.d(TAG, field.getName() + ":" + field.get(null));
} catch (Exception e) {
Log.e(TAG, "an error occured when collect crash info", e);
}
}
}
private String saveCrashInfo2File(Throwable ex) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : infos.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue() + "\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);
try {
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = "crash-" + time + "-" + timestamp + ".log";
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
String path = Environment.getExternalStorageDirectory().getPath() + "/crash_log/";
File dir = new File(path);
if(!dir.exists()){
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path + fileName);
fos.write(sb.toString().getBytes());
fos.close();
}
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing file...", e);
}
return null;
}
@SuppressLint("NewApi")
private void collectMemoryInfo(Context ctx){
try {
final ActivityManager activityManager = (ActivityManager) ctx.getSystemService(Activity.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(info);
infos.put("应用可使用最大内存", activityManager.getMemoryClass() + "MB");
infos.put("系统剩余内存", (info.availMem >> 10)/1024.0+"MB");
infos.put("系统是否处于低内存运行", info.lowMemory + "");
infos.put("低内存运行,当系统剩余内存<", info.threshold/1024.0/1024.0 + "MB");
infos.put("系统总内存", info.totalMem/1024.0/1024.0 + "MB");
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = "crash-" + time + "-" + timestamp + ".hprof";
android.os.Debug.dumpHprofData(Environment.getExternalStorageDirectory().getPath() + "/crash_log/" + fileName);
Debug.MemoryInfo debugInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(debugInfo);
infos.put("应用Native已使用内存",(Debug.getNativeHeapAllocatedSize()/1024.0/1024.0) + "MB");
infos.put("应用Native空闲内存",(Debug.getNativeHeapFreeSize()/1024.0/1024.0) + "MB");
infos.put("应用Native占用总内存",(Debug.getNativeHeapSize()/1024.0/1024.0) + "MB");
infos.put("dalvikPrivateDirty", debugInfo.dalvikPrivateDirty/1024.0 + "MB");
infos.put("dalvikPss", debugInfo.dalvikPss + "");
infos.put("dalvikSharedDirty", debugInfo.dalvikSharedDirty + "");
infos.put("nativePrivateDirty", debugInfo.nativePrivateDirty + "");
infos.put("nativePss", debugInfo.nativePss + "");
infos.put("nativeSharedDirty", debugInfo.nativeSharedDirty + "");
infos.put("otherPrivateDirty", debugInfo.otherPrivateDirty + "");
infos.put("otherPss", debugInfo.otherPss + "");
infos.put("otherSharedDirty", debugInfo.otherSharedDirty + "");
infos.put("应用dalvik可使用最大内存",(Runtime.getRuntime().maxMemory()/1024.0/1024.0) + "MB");
infos.put("应用dalvik空闲内存",(Runtime.getRuntime().freeMemory()/1024.0/1024.0) + "MB");
infos.put("应用dalvik占用总内存",(Runtime.getRuntime().totalMemory()/1024.0/1024.0) + "MB");
} catch (Exception e) {
Log.e(TAG, "an error occured when collect memory info", e);
}
}
private void collectTaskInfo(Context ctx){
try {
final ActivityManager activityManager = (ActivityManager) ctx.getSystemService(Activity.ACTIVITY_SERVICE);
List<RunningTaskInfo> runningTaskInfos = activityManager.getRunningTasks(1);
if(runningTaskInfos != null && runningTaskInfos.size() > 0){
RunningTaskInfo runningTaskInfo = runningTaskInfos.get(0);
infos.put("任务id", runningTaskInfo.id + "");
infos.put("任务description", runningTaskInfo.description + "");
infos.put("任务numActivities", runningTaskInfo.numActivities + "");
infos.put("任务baseActivity", runningTaskInfo.baseActivity.getClassName());
infos.put("任务topActivity", runningTaskInfo.topActivity.getClassName());
}
} catch (Exception e) {
Log.e(TAG, "an error occured when collect memory info", e);
}
}
}
生成的日志
versionName=4.1.1
versionCode=411
BOARD=msm8939
BOOTLOADER=3.19.0.0000
BRAND=htc
CPU_ABI=armeabi-v7a
CPU_ABI2=armeabi
DEVICE=htc_a51dtul
DISPLAY=KTU84P release-keys
FINGERPRINT=htc/htccn_chs_cmcc/htc_a51dtul:4.4.4/KTU84P/422469.10:user/release-keys
HARDWARE=qcom
HOST=ABM105
ID=KTU84P
IS_DEBUGGABLE=false
MANUFACTURER=HTC
MODEL=HTC D820t
PRODUCT=htccn_chs_cmcc
RADIO=unknown
SERIAL=HC4BPYC01956
STAGE=2
TAGS=release-keys
TIME=1439269516000
TYPE=user
UNKNOWN=unknown
USER=buildteam
应用可使用最大内存=96MB
系统剩余内存=532.96484375MB
系统是否处于低内存运行=false
低内存运行,当系统剩余内存<=96.0MB
系统总内存=1858.0234375MB
应用已使用内存=19.8946533203125MB
应用空闲内存=1.6053009033203125MB
应用占用总内存=141.765625MB
dalvikPrivateDirty=74912
dalvikPss=74945
dalvikSharedDirty=2432
nativePrivateDirty=0
nativePss=0
nativeSharedDirty=0
otherPrivateDirty=50228
otherPss=55060
otherSharedDirty=6604
应用dalvik可使用最大内存=96.0MB
应用dalvik空闲内存=4.2220611572265625MB
应用dalvik占用总内存=25.0859375MB
任务id=793
任务description=null
任务numActivities=6
任务baseActivity=...
任务topActivity=...
java.lang.OutOfMemoryError: (Heap Size=98112KB, Allocated=81399KB)
......