重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
Android内存优化一:java垃圾回收机制
创新互联是一家集网站建设,内蒙古企业网站建设,内蒙古品牌网站建设,网站定制,内蒙古网站建设报价,网络营销,网络优化,内蒙古网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。
Android内存优化二:内存泄漏
Android内存优化三:内存泄漏检测与监控
Android内存优化四:OOM
Android内存优化五:Bitmap优化
压缩比:scale = (flaot) targetDensity / density
targetDensity : 设备屏幕像素密度dpi
density: 图片对应的文件夹的像素密度dpi
1)、同一张图片放在不同的资源目录下,其分辨率会有变化。
2)、Bitmap的分辨率越高,其解析后的宽高越小,甚至小于原有的图片(及缩放),从而内存也响应的减少。
3)、图片不放置任何资源目录时,其使用默认分辨率mdpi:160。
4)、资源目录分辨率和屏幕分辨率一致时,图片尺寸不会缩放。
Bitmap放在资源目录中的计算方式为:
主要通过编码、采样、复用、匿名共享区进行优化
由于ARGB_4444的画质惨不忍睹,一般假如对图片没有透明度要求的话,可以改成RGB_565,相比ARGB_8888将节省一半的内存开销
其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。
ALPHA_8 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度。
ARGB_4444 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节。
ARGB_8888 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节。
RGB_565 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节。
bitmap的占用内存,是以bitmap的宽高和每个像素占用的字节数决定的。
根据BitmapFactory 的采样率进行压缩 设置采样率,不能小于1 假如是2 则宽为之前的1/2,高为之前的1/2,一共缩小1/4 以此类推
图片复用指的是inBitmap这个属性。
不使用这个属性,你加载三张图片,系统会给你分配三份内存空间,用于分别储存这三张图片
如果用了inBitmap这个属性,加载三张图片,这三张图片会指向同一块内存,而不用开辟三块内存空间。
inBitmap的限制:
1、3.0-4.3
复用的图片大小必须相同
编码必须相同
2、4.4以上
复用的空间大于等于即可
编码不必相同
3、不支持WebP
4、图片复用,这个属性必须设置为true;
options.inMutable = true;
Android 系统为了进程间共享数据开辟的一块内存区域,由于这块区域不受应用的Head的大小限制,相当于可以绕开oom,FaceBook的Fresco首次应用到实际中。
限制:5.0以后就限制了匿名共享内存的使用。
在SDK 11 - 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。 新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。 我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。
8.0Bitmap的像素数据存储在Native,为什么又改为Native存储呢?
因为8.0共享了整个系统的内存,测试8.0手机如果一直创建Bitmap,如果手机内存有1G,那么你的应用加载1G也不会oom。
可以利用LRU开管理Bitmap,给他设置内存最大值,及时回收。
BitmapRegionDecoder
2个基本原则
既然需要的内存公式已得到,那优化就显而易见了,无非就是减小的这三个参数的值,具体的策略如下:
这里我们将图片分为2种情况来探讨:
图片占用的内存 大小为:
为什么mipmap不在这种情况的考虑范围之内呢?
因为mipmap是Android系统为了避免Launcher Icon变形而添加的资源目录,也就是说,mipmap中的图片不会被缩放。所以Google也不推荐将除Launcher Icon之外的图片放在mipmap目录中。
本地图片通常都是通过Android提供的BitmapFactory来加载的, 这里看几个常用的API:
图片的优化可通过Options参数来实现(Options的介绍可参考 从fresco 看图片优化 :
inPreferredConfig的取值为Bitmap.Config类型(这里只考虑以下几种情况),它是一个枚举类型,用来设置每个像素需要的字节数:
1.jpeg和gif
2.webp
3.png8, png24, png32
网络图片通常我们都是使用开源库进行加载, 所以不需要拿到Bitmap再进行缩放或裁剪。
这时可让后台实现网络图片的裁剪,即:根据图片的请求参数返回合适的尺寸,最大也只需要控件的大小即可。
再大也没意义,不仅浪费流量,还占用内存。
如果你的APP中有很多图片,那么可对图片的宽高根据设备的内存情况进行适当的缩小:
尽量为所有分辨率创建资源 资源匹配分辨率 = 减少不必要的缩放,从而提高UI绘制效率
对于一个多图片的APP来说,图片所占内存的优化是一项必不可少的工作。
总的来说,其优化也就是通过 缩放 和指定 Bitmap.Config的值 来实现的,只是不同位置,不同格式的图片有所差异而已。
;utm_source=weixinqun
内容整理自网络。
在做内存优化的时候,我们发现除了解决内存泄露问题,剩下的就只有想办法减少真实的内存占用。而在App中,大部分内存可能被我们图片占用了,所以减少图片的内存占用可以带来直接的效果。本文就简单介绍一张图片到底占用多少内存,我们先假设我们有一张图片时** 600 * 800** 的,图片占用空间大小假设是** 100KB**。
图片内存大小跟占用空间大小有什么关系?
占用空间的大小不是图片占用内存的大小,一些初学者可能会误解一下。占用空间是在磁盘上占用的空间,内存大小是加载到内存中占用的内存大小。两个只是单位是一样的,本质不是一个概念。
一张图片到底占用多少内存呢?(ARGB_8888编码)
1. 图片占用内存的计算公式: 图片高度 * 图片宽度 * 一个像素占用的内存大小
2. 所以上面的图片占用内存是:**800 * 600 * 4 byte = 1875KB = 1.83M **
上面的计算公式中,为什么是4byte呢?文章后面有总结哦
图片所在目录对内存的影响?
在Android中,图片的存放目录和手机的屏幕密度影响图片最终的大小,举个例子:
假设我们的图片放到 xhdpi 目录下,那么我们本文中的图片占用的内存大小如下:
屏幕密度为2的设备:800 * 600 * 4byte = 1.83M
屏幕密度为3的设备:800 * 1.5 * 600 * 1.5 * 4byte = 1.83 * 2.25M =** 4.12M**
所以,计算图片占用内存大小的时候,要考虑图片所在的目录跟设备密度,这两个因素其实影响的是图片的高宽,android会对图片进行拉升跟压缩。
总结
1. 图片确实很占用内存,内存优化先考虑图片内存占用;
2. 一定要避免使用大图片,这就是.9图很有用的原因之一;
3. 图片的大小对内存的影响是正比关系;
4. 本文只是简单的告知读者怎么计算图片的内存大小。
大图: 440 * 336 小图: 220 * 168 资源目录: xhdpi
小图的高宽都是大图的1/2--小图是原图的1/4
界面效果:
测试设备: Coolpad 8676-M01 5.1 density=2.0
测试前准备操作: 同一款设备,设置图片前后多次调用gc直到内存短时间内保持稳定不再变化
内存使用情况: 下图依次是 初始内存,大图内存,小图内存
大图占用内存: 11.23 MB - 10.66 MB = 0.57 MB
小图占用内存: 10.81 MB - 10.66 MB = 0.15 MB
大图小图内存关系: 0.15 MB * 4 = 0.60 MB 约等于 0.57 MB (这是统计工具的误差,理论上就是相等的)
同样的方式在另外一台设备小米4c上得到的结果如下:
测试设备: Xiaomi Mi-4c V8.2.1.0.LXKCNDL 5.1.1 density=3.0
大图占用内存: 13.22 MB - 11.95 MB = 1.27 MB
小图占用内存: 12.27 MB - 11.95 MB = 0.32 MB
大图小图内存关系: 0.32 MB * 4 = 1.28 MB 约等于 1.27 MB
结论: 由此可见大图比小图占用更多的内存,图片大小(分辨率)与占用内存成正比关系
备注: 图片在硬盘上占用的磁盘空间大小,与在内存中占用的内存大小完全不一样,不是一个概念,不要混淆
根据上文中图片大小与内存的关系,可以更加深刻的理解Android中.9图片的作用,它不但能减少apk的体积,还能减少图片占用内存。
有些时候我们根本不需要图片,而是自己绘制背景,可以在自定义View的onDraw中绘制背景,当然最方便的还是使用系统的Drawable,绘制部分交给系统去完成。
下面测试图片与Drawable的内存占用对比
原始图片大小: 482 * 482
界面效果:
测试设备: Xiaomi Mi-4c V8.2.1.0.LXKCNDL 5.1.1
测试前准备操作: 同一款设备,设置背景前后多次调用gc直到内存短时间内保持稳定不再变化
内存使用情况: 下图依次是 初始内存,使用图片占用的内存,使用Drawable占用的内存,使用onDraw绘制占用的内存
使用图片占用内存: 13.97 MB - 11.97 MB = 2.00 MB
使用Drawable占用内存: 11.97 MB - 11.97 MB = 0.00 MB (不会是0,有误差,只是很少)
使用onDraw绘制占用内存: 11.98 MB - 11.97 MB = 0.01 MB
结论: 绘制背景,或者使用系统提供Drawable作为背景,会大大减少内存占用
Drawable参考资料:
Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)
Android GradientDrawable(shape标签定义)静态使用和动态使用(圆角,渐变实现)
“让你的图片最小化”一节中描述的方法:使用尽可能小的图,使用.9,自己绘制背景或者使用Drawable来绘制背景
加载大图片时需要对图片进行压缩,使用等比例压缩方法直接在内存中处理图片
这样做要注意的是,图片质量会变差,inSampleSize设置的值越大,图片质量就越差。
有时候我们取得一张图片,也许只是为了获得这个图片的一些信息,比如图片的width、height等信息,不需要显示到界面上,这个时候我们可以不把图片加载到内存中。
由于Android外层是使用java,而底层使用的是C语言为图片对象分配的内存空间。所以我们的外部虽然看起来释放了,但里层却并不一定完全释放了,我们使用完图片后最好再释放掉里层的内存空间。
RGB(ARGB)
RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。在Android中还有包含透明度Alpha的颜色模型,即ARGB。
YUV
YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
YUV的原理是把亮度与色度分离,研究证明,人眼对亮度的敏感超过色度。利用这个原理,可以把色度信息减少一点,人眼也无法查觉这一点。
主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题
YUV的存储中与RGB格式最大不同在于,RGB格式每个点的数据是连继保存在一起的。即R,G,B是前后不间隔的保存在2-4byte空间中。而YUV的数据中为了节约空间,U,V分量空间会减小。每一个点的Y分量独立保存,但连续几个点的U,V分量是保存在一起的,(反正人眼一般也看不出区别).这几个点合起来称为macro-pixel, 这种存储格式称为Packed格式。另外一种存储格式是把一幅图像中Y,U,V分别用三个独立的数组表示。这种模式称为planar模式。
CMYK
CMYK也称作印刷色彩模式,顾名思义就是用来印刷的。印刷四分色模式是彩色印刷时采用的一种套色模式,利用色料的三原色混色原理,加上黑色油墨,共计四种颜色混合叠加,形成所谓“全彩印刷”。四种标准颜色是:
CMYK和RGB相比有一个很大的不同:RGB模式是一种发光的色彩模式,你在一间黑暗的房间内仍然可以看见屏幕上的内容;CMYK是一种依靠反光的色彩模式,我们是怎样阅读报纸的内容呢?是由阳光或灯光照射到报纸上,再反射到我们的眼中,才看到内容。它需要有外界光源,如果你在黑暗房间内是无法阅读报纸的。只要是在印刷品上看到的图像,就是CMYK模式表现的。比如期刊、杂志、报纸、宣传画等,都是印刷出来的,那么就是CMYK模式的了。
CMYK原色与叠加之后的颜色对比
在不考虑透明度的情况下,一个像素点的颜色值在计算机中的表示方法有以下3种:
在Java中,float类型的变量占32位,int类型的变量占32位,short和char类型的变量都在16位,因此可以看出,用浮点数表示法编码一个像素的颜色,内存占用量是96位即12字节;而用24位整数表示法编码,只要一个int类型变量,占用4个字节(高8位空着,低24位用于表示颜色);用16位整数表示法编码,只要一个short类型变量,占2个字节;因此可以看出采用整数表示法编码颜色值,可以大大节省内存,当然,颜色质量也会相对低一些。在Android中获取Bitmap的时候一般也采用整型编码。
回想一下Android的BitmapConfig类中,有ARGB_8888、ARGB_4444、RGB565等常量,现在可以知道它们分别代表了什么含义。同时也可以计算一张图片在内存中可能占用的大小,比如采用ARGB_8888编码载入一张1920 1200的图片,大概就会占用1920 1200*4/1024/1024=8.79MB的内存。
采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存;
1920 1200的图片:*
ARGB_8888:1920 1200 4/1024/1024=8.79MB
ARGB_4444,RGB565:1920 1200 2/1024/1024=4.39MB
在Android中,对图片的使用一定要关注,大多数情况下,占用内存多,OOM发生都是因为图片资源使用不当。不要盲目加一个大图到Android项目中,能使用.9进来使用,而且.9图本身尽可能小,另外能使用绘制实现就不要加一个图片资源。有些时候,在不影响用户体验的情况下,可以降低图片素材质量,比如不需要透明度的就不要了,有些透明度用肉眼看不出来。
Android内存优化实践
1.内存模型与分布
我们知道android应用大多是使用java语言进行开发的,这就需要我们了解java的内存模型,此外在android中的应用都是基于Dalvik 虚拟机或者ART虚拟机,那么对这些虚拟机的内存分布也应该有所了解。
上图是常见的java虚拟机的内存分布图:
方法区:主要存储虚拟机加载的类信息,常量,静态变量,及时编译器编译后的代码等数据。内存优化时这一部分主要考虑是不是加载了很多不必要的第三方库。这部分的内存减少主要是常量池的回收和类的卸载(类卸载条件:无引用,类加载器可卸载)
堆:几乎所有的对象都在这个区域产生,该区域属于线程共享的区域,所以写代码时更要注意多线程安全。这个内存区域的大小变化主要是对象的创建和回收,比如:如果短时间内有大量的对象创建和回收,可能会造成内存抖动,如果对象创建之后一直回收不掉,则会导致内存泄漏,严重的内存泄漏会导致频繁的gc,从而是界面卡顿。
虚拟机栈:这个区域描述的是java方法执行的内存模型,我们常说的方法栈的入栈就是将方法的栈帧存储到虚拟机栈,这个区域是线程私有的,其生命周期就是线程的生命周期。也就是说每个线程都会有,默认一个线程的线程栈大小是1M,这不包括在方法中产生的其他对象的大小。这一块我们能控制的就是线程的数量,特别是程序中没有使用线程池或者使用的多个第三方库都带有线程池的情况。
本地方法栈:同虚拟机栈的作用非常类似,是为虚拟机执行native方法服务的,所以需要注意的地方也和虚拟机栈一样,特别是使用了第三方so的情况
程序计数器:当前线程执行的虚拟机字节码的行号记录器,占用的内存较小,可以不考虑
2.内存限制
android是基于Linux系统的,android中的进程分为两种:
1.native进程:采用C/C++实现,不包含dalvik实例的linux进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的
2.java进程:实例化了dalvik虚拟机实例的linux进程,进程的入口main函数为java函数。dalvik虚拟机实例的宿主进程是fork()系统调用创建的linux进程,所以每一个android上的java进程实际上就是一个linux进程,只是进程中多了一个dalvik虚拟机实例
我们知道,操作系统对进程的内存是有限制的,而且操作系统对dalvik虚拟机自身的堆内存大小也是有限制的。可以通过如下命令查看限制大小:
adb shell getprop | grep dalvik.vm.heapgrowthlimit
可以在Androidmanifest文件中application节点加入android:largeHeap=“true”来增加其dalvik虚拟机中堆的大小
我们常说的堆大小其实是包涵两部分的,一是java的堆,而是native的堆,java堆中主要是一下java对象,由 C/C++申请的内存空间则在native堆中,也有一些对象需要结合native和java堆共同完成,比如bitmap,bitmap分为bitmap对象和其中存储的像素值,对象分配在java堆,而存储的像素值则根据版本不同存储的位置也不同,api 11 - api 25是存储在java堆中的,其他版本是存储在native堆中的;
3.内存泄漏
常见的内存泄漏:
1.静态引用(自身代码和第三方代码)
2.集合内引用
3.Handler消息未清除
4.非静态的内部类中持有外部内的应用
5.匿名内部类/非静态内部类和异步线程
检查的方式:
我这里使用的是leakcanary,一般简单的内存泄漏可以直接在leakcanary中查到引用链路,不能查看的我是使用MAT来分析的当前内存信息;
上图中各项详细的指标的意义可以在这里查到,这里主要占比比较大的几个区域:
allocated:表示app内分配的java的对象数,从当前数值可以看出程序内可能存在过多创建对象的情况,比如string对象
Native:从 C 或 C++ 代码分配的对象内存,频繁进出相关页面发现native堆的大小并没有减小,说明存在c/c++层的内存泄漏
Code:您的应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。这个区域能优化的就是移除不需要的so库,懒加载使用so库,移除无用代码(import,方法和类)
4.优化实践
了解了android中的内存分布和泄漏相关,接下来就是结合自身业务进行内存优化了,如下:
1.先解决程序中内存占用较大的业务模块中的内存泄漏,不熟悉MAT的使用的可以看看这个
2.移除程序中多余的代码和引用,这里使用默认的lint检测再配合shrinkResources来删除无效资源
3.优化图片,保证图片放置在合理的文件夹,根据View大小加载合适的图片大小,根据手机状态配置bitmap和回收策略
4.优化对象创建,比如string,使用对象池等