收集android崩溃信息

这篇博客详细介绍了如何在Android应用中收集崩溃信息,包括设备信息、内存信息和Activity栈的详细数据。通过使用PackageManager获取PackageInfo,利用ActivityManager获取MemoryInfo以了解内存使用情况,以及分析Activity栈来跟踪应用状态。此外,还讨论了如何通过代码生成内存日志,以辅助排查内存溢出问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近测试人员偶尔碰到应用崩溃的情况,但是在开发过程中却没有,所以增加了个记录崩溃日志的功能。
主要记录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)
......
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值