Android自定义全局捕获异常并上传,实现实时收集APP崩溃crash信息

一、异常收集

目的:在APP上线后,可能会出现一些BUG导致了APP的闪退,用户体验就非常致命,我们一定要第一时间找到问题的所在,去处理掉问题,处理有方法有两种,一是发一个修改后的新版本,另一个是用热修复发布一个更新补丁,具体选择哪一种符合自己需求就行。
我们主要说的异常的收集和处理,热修复不在范畴内。

1、我们需要自定义一个异常收集类(创建一个Thread.UncaughtExceptionHandler的继承类);
2、替换掉APP本身的异常处理(在Thread.UncaughtExceptionHandler实现类中使用Thread.setDefaultUncaughtExceptionHandler(this)方法替换);
3、在类中收集信息,这个信息最好包括手机的一些信息,比如:厂商、型号、cup型号、内存大小等...,因为安卓手机的多样性导致我们在适配的时候非常麻烦,也是因为有些问题的出现是因为个别的硬件差异造成的,所以这些信息最好收集;

整体思路就是,自定义一个异常收集类替换到本来的异常处理类,在这个类中去收集一些必要的信息回传到我们的后台,我们可以在崩溃信息发生的第一时间去处理

以下是异常收集类代码,可以用作参考,也可以直接用(这个类是参考的别人的,自己做了一些修改)
public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler {
 
    public static final String TAG = "CrashHandlerUtil";
 
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler实例
    private static CrashHandlerUtil INSTANCE = new CrashHandlerUtil();
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String, String> infos = new HashMap<>();
 
    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINA);
    private String crashTip = "应用开小差了,稍后重启下,亲!";
 
    public String getCrashTip() {
        return crashTip;
    }
 
    public void setCrashTip(String crashTip) {
        this.crashTip = crashTip;
    }
 
 
    private CrashHandlerUtil() {
    }
 
 
    public static CrashHandlerUtil getInstance() {
        return INSTANCE;
    }
 
 
    public void init(Context context) {
        mContext = context;
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
 
    /**
     * 当UncaughtException发生时会转入该函数来处理
     *
     * @param thread 线程
     * @param ex     异常
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Logger.e("error : ", e);
                e.printStackTrace();
            }
            //退出程序
            //退出JVM(java虚拟机),释放所占内存资源,0表示正常退出(非0的都为异常退出)
            System.exit(0);
            //从操作系统中结束掉当前程序的进程
            android.os.Process.killProcess(android.os.Process.myPid());
        }
    }
 
    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param throwable 异常
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(final Throwable throwable) {
        if (throwable == null) {
            return false;
        }
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                throwable.printStackTrace();
                StringUtils.showMsgAsCenter(mContext,getCrashTip());
                Looper.loop();
            }
        }.start();
        //收集设备参数信息
        collectDeviceInfo(mContext);
        //保存日志文件
        saveCrashInfo2File(throwable);
        return true;
    }
 
    /**
     * 收集设备参数信息
     *
     * @param ctx 上下文
     */
    public 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 (PackageManager.NameNotFoundException e) {
            Logger.e("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());
//                Logger.e(field.getName() + " : " + field.get(null));
            } catch (Exception e) {
                Logger.e("an error occured when collect crash info", e);
            }
        }
    }
 
    /**
     * 保存错误信息到文件中
     *
     * @param ex 异常
     * @return 返回文件名称, 便于将文件传送到服务器
     */
    private String saveCrashInfo2File(Throwable ex) {
 
        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);
        Logger.e(sb.toString());
        if(BuildConfig.DEBUG) {
            return null;
        }
        
        /*
        这个 crashInfo 就是我们收集到的所有信息,可以做一个异常上报的接口用来提交用户的crash信息
         */
        String crashInfo = sb.toString();
        
        return null;
    }
}
Application的onCreate中调用init()方法

在类的最后一个方法中红crashInfo是收集到的信息需要回传到我们的后台或着其他途径
收集的信息结构如下
SUPPORTED_64_BIT_ABIS=[Ljava.lang.String;@f4d714
versionCode=54
BOARD=unknown
BOOTLOADER=unknown
TYPE=user
ID=MRA58K
TIME=1506044459000
BRAND=Xiaomi
TAG=Build
SERIAL=7D6TPFT4QCS8S8FQ
HARDWARE=mt6797
SUPPORTED_ABIS=[Ljava.lang.String;@f3fdbbd
CPU_ABI=armeabi-v7a
RADIO=unknown
IS_DEBUGGABLE=false
DISPLAY_TYPE=unknown
MANUFACTURER=Xiaomi
SUPPORTED_32_BIT_ABIS=[Ljava.lang.String;@3f3dd67
TAGS=release-keys
CPU_ABI2=armeabi
UNKNOWN=unknown
USER=builder
FINGERPRINT=Xiaomi/nikel/nikel:6.0/MRA58K/V8.5.7.0.MBFCNED:user/release-keys
HOST=c3-miui-ota-bd06.bj
PRODUCT=nikel
versionName=1.4.7
DISPLAY=MRA58K
MODEL=Redmi Note 4
DEVICE=nikel
java.lang.OutOfMemoryError: Failed to allocate a 843012 byte allocation with 381736 free bytes and 368KB until OOM
    at com.bumptech.glide.b.a.a(SourceFile:379)
    at com.bumptech.glide.load.resource.c.b.<init>(SourceFile:92)
    at com.bumptech.glide.load.resource.c.b$a.newDrawable(SourceFile:368)
    at com.bumptech.glide.load.resource.a.a.a(SourceFile:32)
    at com.bumptech.glide.load.resource.a.a.b(SourceFile:16)
    at com.bumptech.glide.load.engine.g.b(SourceFile:44)
    at com.bumptech.glide.request.GenericRequest.a(SourceFile:487)
    at com.bumptech.glide.load.engine.c.b(SourceFile:158)
    at com.bumptech.glide.load.engine.c.a(SourceFile:22)
    at com.bumptech.glide.load.engine.c$b.handleMessage(SourceFile:202)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:207)
    at android.app.ActivityThread.main(ActivityThread.java:5791)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:762)
收集到了一个OOM,这个可能也比较常见了,我们在处理一些大图片的时候,如果稍不注意可能就会造成OOM了,特别是现在的一些低配手机上。
当你看到这个异常信息后是不是对怎么处理就心有成竹了呢。
那么问题又来了,这个收集到的信息怎么都是a.b.c这样的?
大家在发布APP的时候肯定都对代码进行过混淆,混淆后的代码就是这样,那么收集的时候也只能收集到这个程度了。
二、如何处理混淆后的异常信息

我们可以利用SDK中tools下的proguardgui.bat工具和混淆对应文档进行反混淆处理
D:\Android\sdk\tools\proguard\bin\proguardgui.bat   工具在SDK中的位置,有的SDK版本不同这个工具的具体位置可能有改变,也可以在tools中直接搜索proguardgui.bat,双击运行即可
1、点击左侧栏中的Retrace
2、mapping file处选择APP的mapping文件的位置

3、Obfuscated stack trace输入你收集到的异常信息,注意是异常信息,并不是我们刚才收集的那些所有的信息,刚才收集的信息中还包含了手机的一些信息,这些不需要,只复制这些到输入框
java.lang.OutOfMemoryError: Failed to allocate a 843012 byte allocation with 381736 free bytes and 368KB until OOM
    at com.bumptech.glide.b.a.a(SourceFile:379)
    at com.bumptech.glide.load.resource.c.b.<init>(SourceFile:92)
    at com.bumptech.glide.load.resource.c.b$a.newDrawable(SourceFile:368)
    at com.bumptech.glide.load.resource.a.a.a(SourceFile:32)
    at com.bumptech.glide.load.resource.a.a.b(SourceFile:16)
    at com.bumptech.glide.load.engine.g.b(SourceFile:44)
    at com.bumptech.glide.request.GenericRequest.a(SourceFile:487)
    at com.bumptech.glide.load.engine.c.b(SourceFile:158)
    at com.bumptech.glide.load.engine.c.a(SourceFile:22)
    at com.bumptech.glide.load.engine.c$b.handleMessage(SourceFile:202)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:207)
    at android.app.ActivityThread.main(ActivityThread.java:5791)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:762)
4、最后点击ReTrace

详细的异常信息就看到了。
大家看到了是加载gif图片时发生了OOM,接下来就可以根据异常信息去改写代码了。然后修复APP吧


最后把这几个文件描述下,作为参考,赶紧点开你的工程的找个文件看看到底是什么东东

dump.txt
APK文件中所有类的内部结构

mapping.txt
混淆前后类、方法、类成员等的对照

resources.txt
工程中用到的所有资源信息(描述可能不完全)

seeds.txt
没有被混淆的类和成员

usage.txt
被移除的代码

猜你喜欢

转载自blog.csdn.net/u014644594/article/details/83748420