前言

本宝宝苦啊,辛辛苦苦上线一个版本,上线之后,看到崩溃日志,感觉整个人都不好了.

别人家的崩溃日志是这样子的:

 1 Fatal Exception: java.lang.NullPointerException
 2        at com.*.*.*.*$4.run(*.java:537)
 3        at android.os.Handler.handleCallback(Handler.java:733)
 4        at android.os.Handler.dispatchMessage(Handler.java:95)
 5        at android.os.Looper.loop(Looper.java:136)
 6        at android.app.ActivityThread.main(ActivityThread.java:5314)
 7        at java.lang.reflect.Method.invokeNative(Method.java)
 8        at java.lang.reflect.Method.invoke(Method.java:515)
 9        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:862)
10        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:678)
11        at dalvik.system.NativeStart.main(NativeStart.java)

我们家的崩溃日志是这样子的:

 1 Fatal Exception: java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@5e844ef
 2        at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1282)
 3        at android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:599)
 4        at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:538)
 5        at android.view.View.getDrawableRenderNode(View.java:15766)
 6        at android.view.View.drawBackground(View.java:15712)
 7        at android.view.View.draw(View.java:15479)
 8        at android.widget.FrameLayout.draw(FrameLayout.java:658)
 9        at android.view.View.updateDisplayListIfDirty(View.java:14384)
10        at android.view.View.getDisplayList(View.java:14413)
11        ...
12        at android.os.Handler.dispatchMessage(Handler.java:104)
13        at android.os.Looper.loop(Looper.java:194)
14        at android.app.ActivityThread.main(ActivityThread.java:5631)
15        at java.lang.reflect.Method.invoke(Method.java)
16        at java.lang.reflect.Method.invoke(Method.java:372)
17        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:959)
18        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)

全部是Android Framework里的崩溃啊,表示不服.

再瞅瞅啊,虽然能够看出来是由于某个Bitmap被回收导致的Crash,但是你妹的,竟然崩溃栈完全没有看到自己的代码,让我怎么定位,我不想一个一个界面去 排除啊.崩溃栈你不能直接显示出是哪一个View崩溃了吗?把View类的类名暴露给我看看也好啊.

面对这种情况,让我一个一个排除肯定是不干的,让我想想怎么办呢?

提出方案

1.第一个方案,也就是想想有没有办法拿到真实的调用类名.

想法是好的,但是发现没法进行注入啊,Java的UncaughtExceptionHandler中虽然传入了Thread对象和Throwable对象,但是这两个对象通过查看引用关系,发现没法找到真实类,所以放弃.

2.第二个方案,看看能不能通过method hook的形式去进行注入

上网查了一下,目前网上流行的java方法注入其实基本上都是在Dalvik虚拟机下通过类似Dexposed的形式进行注入,而对于ART虚拟机貌似现在还没有一个很好的方案,所以暂时列为备选吧,如果没有别的更好的方案,就使用版本控制进行使用了.

3.最后实在是没办法了,不如我们来针对崩溃时的内存堆栈进行分析吧

反正我们知道现在的问题是由于Bitmap被recycle掉了,那么如果我们通过堆栈分析找到那个被recycle掉的bitmap,然后在去分析它的引用关系不就好了吗?

选择内存分析库

内存分析啊,得有工具啊.虽然Eclipse和AndroidStudio下都有内存分析工具,但是我不想在把用户的内存信息整个传过来,一个内存堆栈至少20m,太恐怖了,我要在客户端进行分析啊,怎么办,怎么办?

话说最近那啥LeakCanary不是很火吗?他不是也对对象的引用关系做了内存分析吗?

就看看它是怎么分析内存泄露的呗,查了一下,发现他是移植了mat的内存分析工具,然后建了一个叫做haha的 一个开源库.

再定睛一看,咦,原来他还有一个2.0版本,是移植于AndroidStudio的perflib, 恩,就是他了,就用它来分析了.

开始内存分析

确定内存分析点

我们不可能随时随地去做内存dump,毕竟dump内存的时候,会导致当前进程所有的线程冻结,因此我们只能选择在应用Crash的时候做内存dump.

为了尽可能降低内存dump的几率,我们只能够在自定义的UncaughtExceptionHandler对传入的Throwable对象进行判断,对指定的异常进行内存分析.

例如这里:我会判断Throwable的message中,是否以Canvas: trying to use a recycled bitmap android.graphics.Bitmap开头,以确保这是我想要进行内存分析的崩溃点.

新开进程进行分析

由于分析内存耗时挺久的,在我的机器上大概耗时30s,那么在用户的机器上只可能耗时更久,因此我在这里单开一个内存分析进程进行内存分析.

首先获取内存快照:

1 HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
2 HprofParser parser = new HprofParser(buffer);
3 Snapshot snapshot = parser.parse();

然后对内存快照中的对象建立他们的链接关系,是的你没有看错,通过上面的代码获得的快照对象中还没有对对象间的引用关系做关联,需要我们手动调一个方法:

1 snapshot.computeDominators();

计算之后,我们来找一下Bitmap类对象

1 Collection<ClassObj> classes = snapshot.findClasses(Bitmap.class.getName());
2 Collection<Heap> heaps = snapshot.getHeaps();
3 for (Heap heap : heaps) {4     for (ClassObj clazz : classes) {5         List<Instance> instances = clazz.getHeapInstances(heap.getId());
6     }
7 }

这里有一个坑,其实snapshot也是从每个heap上获取他的ClassObj列表的,但是可能出现这个heap上的ClassObj对象出现在了另一个heap中的情况,因此我们不能直接获取heap的ClassObj列表,需要直接从snapshot总获取ClassObj列表.

通过以上的代码我们能够获取到当前快照中所有的Bitmap对象,之后,我们通过对对象中的字段进行过滤,可以判断它是否被Recycle掉.

 1 instance.accept(new Visitor(){ 2     ...
 3     public void visitClassInstance(ClassInstance instance) { 4         List<FieldValue> values = instance.getFields();
 5         for (FieldValue value : values) { 6             if ("mRecycled".equals(value.getField().getName()) { 7                 if (value.getValue() == true) { 8                     ...
 9                 }
10                 break;
11             }
12         }
13     }
14     ...
15 })

注意 value.getValue()只对基本类型会生成他的对象实例,对于其他类型则会生成一个ClassInstance对象.

通过上面的代码我们就能够找到我们需要找到的那个被recycle掉的bitmap,然后再去找他的引用关系.

刚才我们说过了,需要调用snapshot.computeDominators();去计算引用关系,计算完成之后,我们可以使用instance.getHardReferences()来获取关于他的强引用对象.

之后再通过一系列的类路径匹配,我们终于能够找到究竟是哪个类出问题了.这个路径匹配问题就不再这里说了,大家自己解决吧

总结

通过上面的做法,我们现在已经能够在用户Crash的时候获取到他的内存信息,并让用户的机器自己分析内存问题.

但这样有个局限,我们只能够针对特定的Crash进行验证分析,也就是说我们发现这种问题后,需要先迭代一个版本去获取用户崩溃的原因,然后才能够发一个fix版本.

所以我这边在考虑如何通过特定语法或配置文件去指明他的crash特征,通过服务器下发特征数据,减少用于验证问题的版本迭代.

原文地址: http://blog.kifile.com/android/2016/01/27/android_client_memory_analysis.html

Android客户端内置内存工具进行崩溃定位的实践经验相关推荐

  1. python对工作效率的提升_使用了这个几个Python内置小工具,可以让你的工作效率提升一倍...

    使用了这个几个Python内置小工具,可以让你的工作效率提升一倍 我们将会详情4个Python解释器自身提供的小工具. 这些小工具在笔者的日常工作中经常使用到, 减少了各种时间的白费, 然而,却很容易 ...

  2. 配置Android Studio内置jre的环境变量

    下载Android Studio时,包括jre.将这个jre配置过环境变量后,就可以在全局使用了.但是和官网安装jre的环境配置略有不同. 图一  jre官网下载,安装后的目录 图二  Android ...

  3. Android获取内置sdcard跟外置sdcard路径

    Android获取内置sdcard跟外置sdcard路径.(测试过两个手机,亲测可用) 1.先得到外置sdcard路径,这个接口是系统提供的标准接口. 2.得到上一级文件夹目录 3.得到该目录的所有文 ...

  4. android 获取sdcard 禁用sdcard,Android获取内置sdcard跟外置sdcard路径

    Android获取内置sdcard跟外置sdcard路径 Android获取内置sdcard跟外置sdcard路径.(测试过两个手机,亲测可用) 1.先得到外置sdcard路径,这个接口是系统提供的标 ...

  5. android 人脸识别demo,Android Camera 内置人脸识别的Demo

    CameraFace Android Camera 内置人脸识别的Demo 通过Android源生API支持的人脸识别FaceDetection,获取到脸部矩形坐标,左右眼坐标,嘴坐标通过View动态 ...

  6. 实现base64格式的amr音频文件在IOS、android微信内置浏览器的播放

    参考文档: 1.https://github.com/yxl/opencore-amr-js   (将amr文件转为wav格式的编解码项目) 因为项目需要,要将amr的base46格式的音频文件在IO ...

  7. Android 12 内置原生壁纸下载

    关注下方公众号,回复 壁纸 领取更多高清壁纸 下载方式 长按二维码关注 code小生 回复[壁纸]立刻获取高清壁纸 Surface 内置原生壁纸下载 Android 11 内置原生壁纸!速度来取

  8. 卸载工具Android,教你轻松卸载/删除Android手机内置游戏/软件

    每年3.15晚会似乎都会引起一场轰动万分的揭秘大行动,今年Android手机应用似乎成为了揭秘行动的焦点内容. 对于那些内置的流氓软件,用户们做了一个艰难的决定--想方设法卸载掉. 但是很多内置的东西 ...

  9. android虚拟内置sd卡,Android: 浅论虚拟SD卡的实现

    项目中使用分布式并发部署定时任务,多台跨jvm,按照常理逻辑每个jvm的定时任务会各自运行,这样就会存在问题,多台分布式jvm机器的应用服务同时干活,一个是加重服务负担虚拟sd卡是什么,另外一个是存在 ...

最新文章

  1. Xshell利用Xftp传输文件,使用pure-ftpd搭建ftp服务
  2. EF Code First Migrations数据库迁移
  3. 最常用计算机机箱,电脑机箱的常用材质是什么?
  4. spark 数据框 删除列_pandas 常用的数据处理函数
  5. 大数据_MapperReduce_从CSV文件中读取数据到Hbase_自己动手实现Mapper和Reducer---Hbase工作笔记0021
  6. C/C++ Memory Layout
  7. manjaro linux下查看硬件温度
  8. 琼斯是计算体心立方弹性模量_本科阶段固体物理期末重点计算题.doc
  9. Python爬取并分析IMDB电影
  10. 一加6手机图片中的文字如何识别?
  11. 老照片瞬间修复神器!快帮你家的长辈恢复照片去吧
  12. Mac安装ffmpeg时 Failed to download resource quot;texi2htmlquot; 的解决办法
  13. python将图片表情包转化成字符
  14. apmserv php升级方法,APMServ5.2.6 升级php5.2 到 5.3版本,及Memcache升级
  15. python中print横向打印
  16. 采药问题 c语言程序,采药-动态规划题解(C语言代码)
  17. HomeAssistant和Node-Red
  18. 两个List数据集合合并成一个List
  19. 「斜」italics() //使用斜体显示字符串 20140817 ①文本处理
  20. 免裁券也不灵?360被曝开始“裁员”,比例达15%到20%

热门文章

  1. Linux下gcc入门
  2. MATLAB 添加自定义的模块到simulink库浏览器
  3. 工预-SQLite(weiwan)
  4. 【算法】SVM分类精度为0,结果很烂怎么办?
  5. 【编程】二叉搜索树的定义
  6. Python7:from module import * 和 import module 的区别
  7. 编写代码的若干个基本规则(以Java为例)
  8. Delphi 与C系列区别之已见(一)
  9. Delphi中预想不到的代码楼主zswang(伴水清清)(专家门诊清洁工)2002-05-16 14:20:38 在 Delphi / VCL组件开发及应用 提问
  10. strhcr函数的使用简单示例