客服群大喊:这个用户的图片不显示,那个用户的图片也不显示。把测试机全拿在手上,搞什么鬼……默默的开bug了。园中泉水闭不上,满地都是回忆!是的,又出事了!内存问题一直是一个既陌生又熟悉的话题,而且大多数都发生在用户家里的一部手机上。Android系统本身也在不断优化,三方框架也在逐渐成熟。再加上手机厂商对大内存的支持,内存问题似乎已经很少见了,但依然不容忽视。有了这次修复内存问题的记录,分享一些“自创”的解决方案,仅供参考。好了,走吧!修复问题的三部曲,先重现,再定位,最后修复。估计有人会说,异常现象都有了,没什么可重现的,就冲进代码,直接动手吧。修复bug总是一件惊心动魄的事情,一不小心,世界就可能崩塌。要么修复不彻底,要么引入新问题。知其所以然,从根源入手,不仅能加深对问题的认识,还能保证最终验证问题是否修复。内存问题经常出现在一些特殊的环境中,也有很多情况不一定会出现,在一些低端机型上往往会有所体现。所以从模型入手或许是一个不错的选择。最终通过bugly找到了对应的问题机型和系统版本,在各个云测试平台上找到了台云测试机。按照进入问题页面的几个固定流程,反复执行,最后锁定复现流程。定位知道如何重现问题,下一步就是定位问题。通常,内存问题有两种情况:内存堆积:由于特殊情况关闭了页面,但内存中仍然缺少资源。内存占用过高:由于业务需要或使用不当导致内存占用过高。我们先看看这个问题属于什么情况。在AndroidStudio2.3及更早版本自带的AndroidMonitor中,可以直观的反映出当前应用整体的内存使用情况。【估计大家分享工具使用方法都腻了,这次就不赘述了。142MB!!!!这么多内存在进入事故现场之前就被占用了。难怪之后会出现内存异常。看来这次要先解决内存占用高的问题了。我们必须先详细了解内存的具体情况,才能知道从何下手解决,是避免无意义的使用,还是优化必要的使用。先强制gc,再dumpjavaheap,查看整体内存情况,按shallowsize排序。首当其冲的字节数组映入眼帘。大家都知道位图一直是大客户。然后我们分析byte[]中的每个对象。从数据上看,同样大小的内存使用有很多,理论上应该有很多张同样大小的图片。但是为什么有这么多?是否重复了相同的图片?或其他?所谓耳听为虚,眼见为实。如果你能看到这些图片长什么样子,是不是很容易做出相应的判断呢?来吧,让我们开始行动:来自Gracker3的Android内存优化:在MAT|Performance中打开Bitmap的原始图像。感谢Gracker的分享,获得新技能。有关详细信息,请参阅门户。主要思路是通过MAT将相应的字节数组保存为原始图像文件,然后使用相应的工具打开预览。但是我记得之前AndroidStudio是可以直接查看的,现在不知道去哪里了。第一步:由于AndroidStudiodump出来的文件mat不能直接打开,所以需要进行转换。在Captures中找到刚刚转储的prof文件。右键单击->导出为标准.hprof。第二步:通过MATEclipseMemoryAnalyzerOpenSourceProject打开。第三步:右键单击要查看的对象->复制->将值保存到文件。将其保存为xxx.data。他建议使用Gracker共享的gimp。Photoshop不确定是不是我的使用方式有问题,验证时一直无法正常显示。第四步:查看对应图片的相关属性。主体是宽度和高度。因为上一步保存的是图片的原始格式文件,没有包含相应的参数信息,所以在导入gimp的时候需要指定相应的参数。第5步:打开gimpGIMP-下载。然后打开刚刚导出的问题。图像类型一般为8888或565根据实际情况选择RGBAlpha或RGB565。然后填写刚才查询的宽高参数。***点击打开查看实物图片。这样就可以直观的查看内存中图片的实际情况。那么我们就可以进一步分析问题的实际原因了。通过以上方法,确定了三个问题:图片资源占用较多,首页图片确实很多。有未使用的图像资源被占用(消失状态)。因为设计师要求的效果,所以占用了很多mask图片。解决方案——图片大量占用对于图片大量占用的问题,其实可以从以下几个方向来思考问题。从效果设计的角度避免,尽量少用全屏图片来应对需求。但在这方面,我个人主张尊重设计师,专业的事交给专业的人。图片资源本身,在满足效果的前提下,尽量选择RGB565。可能少量的图片不明显,但是大量的情况下,节省的内存资源还是很客观的。图片资源不用时及时释放。结合上面的方向来看我们遇到的问题。设计角度目前无法调整,原因是眼泪,这里就不多说了。资源本身已经是RGB565。图片的发布应该是fresco的强项,但好像并不是这样。看来问题可能出在这里。回到ui页面一看,就明白了。viewpager+fragment+recyclerview,相当于大量的图片都在使用,所以fresco不会释放相应的资源。临时解决方案:为了保证核心逻辑的流畅性,通过RxBus在进入和退出核心页面时发送Event事件,然后在使用图片较多的页面注册接收这一系列事件,遍历所有SimpleDraweeViews,并调用其Controller的onDetach或onAttach实现图片资源引用的临时释放和加载回收。为什么说是治标不治本呢,因为我一直觉得这是一个取巧的办法,理论上。不应直接调用方法干预fresco的管理进程。所以在这里打个洞,等重新理解壁画的原理后再补上。也希望大家能提点建议或者意见。解决方案——未使用的图片资源占用每个页面,有提示处理网络异常和相关数据加载异常。原来的处理方式是通过include统一导入后隐藏,遇到异常时显示。问题就出在这里。这些异常提示本身是有小概率触发的,但是如果通过include标签引入,会直接实例化,占用内存资源。临时解决方案:改用ViewStub标签实现按需加载。为什么是临时解决方案?因为有的机型黑屏的时候wifi就断了。重新进入应用时,会经过一个联网过程,所以会先触发联网异常。ViewStub只能加载一次。加载后占用内存。解决方案——在对图片进行遮罩之前,为了显示图片上的文字又不想被图案影响,所以在上面加一层阴影遮罩,保证字体的显示效果。习惯上使用fresco:overlayImage的方法来实现。但是这种实现方式会导致掩码本身成为一个独立的内存资源。解决方法:尽量通过Processor将mask和要显示的图片预先组合起来,这样内存中只保留一个资源。结果,通过上面的优化方法,再次测试同一个模型,内存占用降低了……综上所述,这次我们从内存占用高入手,解决了内存占用过多导致的内存溢出问题。后面遇到记忆遗留的问题,回来补上下面的。记忆问题的调查和解决是一个常见的话题,因为适应和其他情况往往是一个更困难的问题。开发时很难发现,所以建议在一个需求完成后,例行检查一下内存状态,看看有没有问题,需要调整的地方。
