前言
本篇文章是《全面理解Android内存优化》系列文章的最后一篇。系列的主要目的是希望将Android开发中涉及性能优化的部分做一次系统的归纳、总结和学习。本系列文章包含理论基础、工具使用、项目实践三个部分。
理论基础:「全面理解Android内存优化 1」-Android的内存机制与管理建议,主要讲解Android性能优化时涉及到的各种基础知识
工具使用:「全面理解Android内存优化 2」-内存优化工具的使用,主要讲解Android性能优化时各种常用工具的使用。
项目实践:「全面理解Android内存优化 3」-从理论到实践,以一个实际APP为例,总结在在开发中会被忽视的内存问题。
本文中实战时使用的项目地址:https://github.com/linux-link/Fan,可以先阅读这篇文章了解这个项目一次组件化与Android Jetpack的实践
本篇属于三个部分中的项目实践部分。
目录
- 关闭无用的Service
- 多进程WebView的优化
- 避免一图毁十优
- 总结
正文
一、关闭无用的Service
《饭fan》是一个单Activity多Fragment的APP,在App的入口Activity同时启动了两个Service,TinkerService用于检查热修复补丁,UpdateService用于检查是否有更新。
在操作APP一段时间后,使用Memory Profiler检查内存,得到下图
可以看到内存中依然存在TinkerService和UpdateService。没有特殊指定的service是运行在主线程中的,这些已经无用的Service会拖慢主线程并占据主进程的可用内存。
解决方案
- 调用stopService或stopSelf关闭这些service
关闭service后再次使用Memory Profiler检查内存,可以看到,APP占用的总内存已经减少了
二、多进程WebView的优化
从1.0.3版开始《饭fan》中集成了一个简单的商城系统,商城系统的制作参考了
慕课网的一个课程—《混合开发入门 主流开发方案实战京东移动端APP》。
商城系统集成完毕后,调试过程中,LeakCanary提示,ShoppingActivity发生了内存泄漏,如下图所示
WebView应该是Android中最容易发生内存泄漏的系统组件,往往都是Activity退出时,WebView依然持有activity的引用,导致Activity发生泄漏。 网络上有很多如何防止WebView产生泄漏,但是效果都不好,有的甚至根本没有效果。
解决方案
- 让持有webview的Activity独立运行在一个进程,在activity的onDestroy中关闭这个进程
让Activity独立运行在一个进程中,可以彻底清除掉webview以及Activity
,但是让持有webview的Activity独立在一个进程中,会产生另一个问题——长时间的白屏。
webview本身初始化以及载入Html页面都需要一定的时间,这段时间会产白屏。
如果在启动Activity时需要额外再创建一个进程,那么白屏的时间就会进一步拉长,有时甚至长达4-5秒。
《饭fan》中针对长时间这个问题,又做了进一步优化。
- 1.在app启动时,同时启动一个ShoppingService。ShoppingService运行在与WebViewActivity相同的进程中,退出WebViewActivity后当前进程会被关闭,在适当时候再重启ShoppingService。
- 2.引入腾讯的x5WebView和VasSonic,加快webview初始化速度,同时也提高了WebView在各个系统上兼容性。
- 3.在webview初始化的同时,使用APP内网络框架来请求Html页面中所需的数据。通过并行的方式,节省webview的加载数据的时间。
优化步骤大致就是以上这些,具体实现的代码请参考《饭fan》中Component_shopping组件。
三、Bitmap造成的内存泄漏
在Android内存优化中有“一图毁十优”的说法,一般普通的内存泄漏浪费的内存都在几十KB到几MB之间,但是一个bitmap泄漏就有可能浪费几十MB的内存空间,所以bitmap的优化一直是Android内存优化的重中之重。所以我们接下来的就重点介绍Bitmap的优化方案。
- 1.使用RGB_565解码图片
在开发中大多数的图片加载框架的默认解码方案是ARGB_8888,这种解码方案,每个像素占4个字节,其实还有一种图片解码方案是RGB_565,这种解码方案,每个像素占2个字节,但是在视觉效果上与ARGB_8888差距并不明显。
所以一些页面的缩略图、背景图片以及一些用户感官上认为它就是缩略图的地方可以使用RGB_565来解码,在减小内存占用上,有立竿见影的效果,强烈推荐使用。
- 2.不要乱放图片
在开发中我们往往会要求美工一张图标切3到5套不同尺寸的,然后分别放置在res下不同的资源目录里面
目录 | 对应的dpi |
---|---|
res/drawable | 0 |
res/mipmap-lidp | 120 |
res/mipmap-mdpi | 160 |
res/mipmap-hdpi | 240 |
res/mipmap-xhdpi | 320 |
res/mipmap-xxhdpi | 480 |
res/mipmap-xxxhdpi | 640 |
Android有一套特殊的适配策略,对放在mipmap目录的图标会忽略屏幕密度,会去尽量匹配大一点的,然后系统自动对图片进行缩放,从而优化显示和节省资源。图片的缩放比率=手机的dpi / mipmap目录的dpi。
放在drawable目录下的会根据ROM的不同得到一个默认的dpi,但是这个dpi并一定是手机屏幕的实际dpi。
例如:如果我们将一张500X500的图标仅放在ldpi(120)下,那么在在480dpi的手机上实际的显示尺寸是2000X2000。
当我们分不清图标应该放在哪个目录下时,应该尽量将高品质的图片放在高密度目录下,这样能控制图片的缩放比率小于1,保证画质的前提,内存也是可控的。
- 3.控制那些不可控的图片
这是什么意思呢,举一个我曾经实际遇到的例子,我们的APP有一个课件的功能,允许教师上传课件,服务器会把这些课件转成图片返回给APP显示,有个老师上传了一篇PDF格式的论文,服务器转换后每个图片足足有4000X8000这么大,加载每张图片需要消耗内存4000X8000X4/1024/1204=122MB,直接导致了OOM。
在这个例子中教师上传的课件转换后的图片就属于不可控图片,如果服务器不做过滤,那么APP就需要对这些用户上传的图片特殊处理。
处理步骤如下:
- 从服务器下载的图片获取它的高度和宽度
- 对于高度或宽度大于手机屏幕尺寸的图片计算缩放比率,并做缩放解码
- 要对所有的图片解码API(decodexxxx)做OutOfMemoryError的异常处理
具体的代码请参考BitmapUtils。
四、总结
本篇主要总结了一些开发中常常被忽视的内存问题,其它常见的内存问题,在第一篇文章已经有所提及,不过正是这些被忽视的问题切切实实地占据了手机中大量的内存,这些问题其实才是我们更应该关注的重点。
参考资料
《Android移动性能实战》-腾讯SNG专项测试团队 编著