参考:
关于Android中图片大小、内存占用与drawable文件夹关系的研究与分析
Android drawable微技巧,你所不知道的drawable的那些细节
Android为了更好地优化应用在不同屏幕密度下的用户体验,在项目的res目录下可以创建drawab-[density](density为6种通用密度名)目录,开发者在进行APP开发时,针对不同的屏幕密度,将图片放置于对应的drawable-[density]目录,Android系统会依据特定的原则来查找各drawable目录下的图片。查找流程为:
- 先查找和屏幕密度最匹配的文件夹。如当前设备屏幕密度dpi为160,则会优先查找drawable-mdpi目录;如果设备屏幕密度dpi为420,则会优先查找drawable-xxhdpi目录。
- 如果在最匹配的目录没有找到对应图片,就会向更高密度的目录查找,直到没有更高密度的目录。例如,在最匹配的目录drawable-mdpi中没有查找到,就会查找drawable-hdpi目录,如果还没有查找到,就会查找drawable-xhdpi目录,直到没有更高密度的drawable-[density]目录。
- 如果一直往高密度目录均没有查找,Android就会查找drawable-nodpi目录。drawable-nodpi目录中的资源适用于所有密度的设备,不管当前屏幕的密度如何,系统都不会缩放此目录中的资源。因此,对于永远不希望系统缩放的资源,最简单的方法就是放在此目录中;同时,放在该目录中的资源最好不要再放到其他drawable目录下了,避免得到非预期的效果。
- 如果在drawable-nodpi目录也没有查找到,系统就会向比最匹配目录密度低的目录依次查找,直到没有更低密度的目录。例如,最匹配目录是xxhdpi,更高密度的目录和nodpi目录查找不到后,就会依次查找drawable-xhdp、drawable-hdpi、drawable-mdpi、drawable-ldpi。
举个例子,假如当前设备的dpi是320,系统会优先去drawable-xhdpi目录查找,如果找不到,会依次查找xxhdpi → xxxhdpi → hdpi → mdpi → ldpi。对于不存在的drawable-[density]目录直接跳过,中间任一目录查找到资源,则停止本次查找。
总结一下图片查找过程:优先匹配最适合的图片→查找密度高的目录(升序)→查找密度低的目录(降序)。
比如,设备的dpi为320,这匹配目录为drawable-xhdpi;设备的dpi为150,则匹配目录为drawable-mdpi。图片的放大和缩小遵循以下规律:
如果图片所在目录为匹配目录,则图片会根据设备dpi做适当的缩放调整。 如果图片所在目录dpi低于匹配目录,那么该图片被认为是为低密度设备需要的,现在要显示在高密度设备上,图片会被放大。 如果图片所在目录dpi高于匹配目录,那么该图片被认为是为高密度设备需要的,现在要显示在低密度设备上,图片会被缩小。 如果图片所在目录为drawable-nodpi,则无论设备dpi为多少,保留原图片大小,不进行缩放。 那么六种通用密度下的缩放倍数是多少呢?以mdpi为基线,各密度目录下的放大倍数(即缩放因子density)如下
密度 放大倍数 ldpi 0.75 mdpi 1.0 hdpi 1.5 xhdpi 2.0 xxhdpi 3.0 xxxhdpi 4.0
关于切图的选取,Android官方给的建议,各种密度都给出一套图,分别放置在对应的drawable目录下,这种适配是最好的。但也存在问题,一是这种方式会增大安装包的大小;二是很多公司UI在出图时只会出一套。
在这种情况下,怎么使用好这一套切图呢?由于目前的Android智能手机的屏幕基本都在1080p了,屏幕的dpi多数都处于320~480,为了更好地适配,同时为了节省内存成本,建议将切图放置在drawable-xxhdpi目录,同时建议UI针对该密度的设备设计切图。如果UI的切图基于不同尺寸设计,Sketch导出切图时须调整相应的倍数。
例如,假设切图基于376×667的一倍屏幕设计,而要适配1080×1920的屏幕,导出三倍图存放于drawable-xxhdpi目录是适配最好的。
//图片分辨率:790*1115
//图片大小:65.48K
//手机:华为Nova2,1080*1788 xxhdpi
//放在不同文件夹中的形成的bitmap内存如下:
drawable- 默认(效果等同drawable- mdpi)
bitmapSize========================31710600bit
bitmapSize========================30967kb
bitmapSize========================30Mb
drawable- ldpi
bitmapSize========================56374400bit
bitmapSize========================55053kb
bitmapSize========================53Mb
drawable- mdpi
bitmapSize========================31710600bit
bitmapSize========================30967kb
bitmapSize========================30Mb
drawable- hdpi
bitmapSize========================14093600bit
bitmapSize========================13763kb
bitmapSize========================13Mb
drawable- xhdpi
bitmapSize========================7930020bit
bitmapSize========================7744kb
bitmapSize========================7Mb
drawable- xxhdpi
bitmapSize========================3523400bit
bitmapSize========================3440kb
bitmapSize========================3Mb
drawable- xxxhdpi
bitmapSize========================1982992bit
bitmapSize========================1936kb
bitmapSize========================1Mb
drawable- nodpi:不会被放大
bitmapSize========================3523400bit
bitmapSize========================3440kb
bitmapSize========================3Mb
获取屏幕dpi:
private void getDensity() {
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
System.out.println("=====Density is " + displayMetrics.density);
System.out.println("=====densityDpi is " + displayMetrics.densityDpi);
System.out.println("=====height " + displayMetrics.heightPixels);
System.out.println("=====width " + displayMetrics.widthPixels);
}
DisplayMetrics类源码:
//DPI
public static final int DENSITY_LOW = 120;
public static final int DENSITY_MEDIUM = 160;//默认值
public static final int DENSITY_TV = 213; //TV专用
public static final int DENSITY_HIGH = 240;
public static final int DENSITY_260 = 260;
public static final int DENSITY_280 = 280;
public static final int DENSITY_300 = 300;
public static final int DENSITY_XHIGH = 320;
public static final int DENSITY_340 = 340;
public static final int DENSITY_360 = 360;
public static final int DENSITY_400 = 400;
public static final int DENSITY_420 = 420;
public static final int DENSITY_XXHIGH = 480;
public static final int DENSITY_560 = 560;
public static final int DENSITY_XXXHIGH = 640;
//默认DENSITY_MEDIUM
public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
同一张正方形图片放在 mdpi hdpi xhdpi xxhdpi 宽高度会依次变化 3倍 2倍 1.5倍 1倍 bitmap所占内存依次变化 9倍 4倍 2.25倍 1倍
以一张大小为 1024*731 = 748544B, 大小为 485.11K 的图片为例,下面是测试手机占用的内存情况。
从上表可以看出不同屏幕密度的手机加载图片,如果图片放在与自己屏幕密度相同的文件夹下,占用的内存都是 2994176B,
与图片本身大小 748544B 存在一个4倍关系,因为图片采用的ARGB-888色彩格式,每个像素点占用4个字节。
手机在加载图片时,会先查找自己本密度的文夹下是否存在资源,不存在则会向上查找,再向下查找,并对图片进行相应倍数的缩放:
如果在与自己屏幕密度相同的文件夹下存在此资源,会原样显示出来,占用内存正好是: 图片的分辨率*色彩格式占用字节数;
若自己屏幕密度相同的文件夹下不存在此文件,而在大于自己屏幕密度的文件夹下存在此资源,会进行缩小相应的倍数的平方;
若在大于自己屏幕密度的文件夹下没找到此资源,则会向小于自己屏幕密度的文件夹下查找,如果存在,则会进行放大相应的倍数的平方,
这两种情况图片占用内存为:占用内存=图片宽度 X 图片高度/((资源文件夹密度/手机屏幕密度)^2) * 色彩格式每一个像素占用字节数
结论:
- 在对手机进行屏幕适时,可以只切一套图适配所有的手机。
但是如果只切一套小图,那在高屏幕密度手机上,会对图片进行放大,这样图片占用的内存往往比切相应图片放在高密度文件夹下,占用的内存还要大。
那如果只切一套大图放在高幕文件夹下,在小屏幕密度手机上,会缩小显示,按道理是行得通的。但系统在对图片进行缩放时,会进行大量计算,会对手机的性能有一定的影响。同时如果图片缩放比较狠,可能导致图片出现抖动或是毛边。
所以最好切出不同比便的图片放在不同幕度的文件夹下,对于性能要求不大高的图片,可以只切一套大图;
-
一张图片占用内存=图片长 * 图片宽 / (资源图片文件密度/手机屏幕密度)^2 * 每一象素占用字节数,所以图片占用内存跟图片本身大小、手机屏幕密度、图片所在的文件夹密度,图片编码的色彩格式有关;
-
对于网络图片,在不同屏幕密度的手机上加载出来,占用内存是一样的。
-
对于网络或是assets/手机本地图片加载,如果想通过设置 Options 里的 inDensity 或是 inTargetDensity 参数来调整图片的缩放比,必须两个参数均设置才能起作用,只设置一个,不会起作用。
-
drawable 和 mipmap 文件夹存放图片的区别,首先图片放在 drawable-xhdpi 和 mipmap-xhdpi 下,两者占用的内存是一样的, Mipmaps 早在Android2.2+就可以用了,但是直到4.3 google才强烈建议使用。把图片放到 mipmaps 可以提高系统渲染图片的速度,提高图片质量,减少GPU压力。其他并没有什么区别。
华为Nova2 密度:480dp/xxhdpi/3.0x 分辨率:10801920px(可用:10801788) 屏幕尺寸:2.4*4.3/5.0英寸
华为p8 ram 2g rom 16g 密度:320dp/xhdpi/2.0x 分辨率:7201280px(可用:7201208) 屏幕:2.4*4.3/5.0英寸
经测试一:
未设置 android:largeHeap="true" maxMemory======================192 Mb
设置 android:largeHeap="true" maxMemory======================512 Mb
经测试二:
Nova2 加载xxhdpi中图片7m p8 加载xxhdpi中图片3m
经测试三:华为p8中:
flash_bg1 图片放在xxhdpi: 图片所占内存:宽px高px1像素所占内存 (1242/1.5)*(2208/1.5)*4/1024=4761kb defalut:4761kb good: 1190kb
flash_bg1 图片放在xhdpi: 图片所占内存:宽px高px1像素所占内存 124222084/1024=10712kb defalut:10712kb good: 2678kb
1、Android 2.3.3(API 10)及以下的系统
在2.3以下的系统中,Bitmap的像素数据是存储在native中,Bitmap对象是存储在java堆中的,所以在回收Bitmap时,
需要回收两个部分的空间:native和java堆。即先调用recycle()释放native中Bitmap的像素数据,再对Bitmap对象置null,保证GC对Bitmap对象的回收
2、Android 3.0(API 11)及以上的系统
在3.0以上的系统中,Bitmap的像素数据和对象本身都是存储在java堆中的,无需主动调用recycle(),只需将对象置null,由GC自动管理
密度 建议尺寸
布局 | icon尺寸 |
---|---|
mipmap-mdpi | 48 x 48 |
mipmap-hdpi | 72 x 72 |
mipmap-xhdpi | 96 x 96 |
mipmap-xxhdpi | 144 x 144 |
mipmap-xxxhdpi | 192 x 192 |
dpi范围 | 密度 | density分辨率 | 图片icon尺寸 |
---|---|---|---|
0dpi ~ 120dpi | ldpi | 0.75 | 240x320 |
120dpi ~ 160dpi | mdpi(默认) | 1 | 320x480 |
160dpi ~ 240dpi | hdpi | 1.5 | 480x800 |
240dpi ~ 320dpi | xhdpi | 2 | 720x1080 |
320dpi ~ 480dpi | xxhdpi | 3 | 1080x1920 |
480dpi ~ 640dpi | xxxhdpi | 4 | 1440x2560 |
ppi就是pixel per inch,像素每英寸,用于连续调图像, dpi则是dots per inch,一般对应打印机的分辨率。 即:ppi=dpi
iPhone6 主屏尺寸 4.7英寸 主屏分辨率 1334x750像素 屏幕像素密度 326ppi 屏幕像素密度=(13341334+750750)的开方/4.7=326 所以iPhone6的尺寸图可放在xhdpi中
参考:
public static Bitmap readBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
SoftReference<Bitmap> bitmap = new SoftReference(pNetBitmap);
if (bitmap != null) {
if (bitmap.get() != null && !bitmap.get().isRecycled()) {
bitmap.get().recycle();
bitmap = null;
}
}
参考:
AndroidStudio3.0 Android Profiler分析器(cpu memory network 分析器)
一个BitMap位图占用的内存=图片长度图片宽度单位像素占用的字节数。
使用BitmapFactory来decode一张bitmap时,
其单位像素占用的字节数由其参数BitmapFactory.Options的inPreferredConfig变量决定。
(注:drawable目录下有的png图使用Bitmap.Config.RGB_565和ARGB_8888decode出来的大小一样,未解)
ALPHA_8:只有alpha值,没有RGB值,占一个字节。计算:size=w*h
ARGB_4444:一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites,共16bites, 这种格式的图片,看起来质量太差,已经不推荐使用。计算:size=wh2
ARGB_8888:一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites, 即4个字节。这是一种高质量的图片格式,电脑上普通采用的格式。android2.3开始的默认格式。计算:size=wh4
RGB_565:一个像素占用2个字节,没有alpha(A)值,即不支持透明和半透明, Red(R)值占5个bites ,Green(G)值占6个bites ,Blue(B)值占5个bites,共16bites,即2个字节。 对于没有透明和半透明颜色的图片并且不需要颜色鲜艳的来说,该格式的图片能够达到比较的呈现效果, 相对于ARGB_8888来说也能减少一半的内存开销,因此它是一个不错的选择。计算:size=wh2
实际开发中通过代码获取bitmap大小
@TargetApi(Build.VERSION_CODES.KITKAT)
public static int getBitmapSize(BitmapDrawable value) {
Bitmap bitmap = value.getBitmap();
if (VersionUtils.hasKitKat()) {
return bitmap.getAllocationByteCount();
} if (VersionUtils.hasHoneycombMR1()) {
return bitmap.getByteCount();
}
return bitmap.getRowBytes() * bitmap.getHeight();
}
减少Bitmap对象的内存占用:
-
inBitmap:如果设置了这个字段,Bitmap在加载数据时可以复用这个字段所指向的bitmap的内存空间。但是,内存能够复用也是有条件的。在Android4.4(API Level 19)之前,只有新旧两个Bitmap的尺寸一样才能复用内存空间。Android4.4开始只要旧Bitmap的尺寸大于等于新的Bitmap就可以复用了。
-
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
-
Decode format:解码格式,选择ARGB_8888 RBG_565 ARGB_444ALPHA_8,存在很大差异。
-
使用更小的图片:在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InfaltionException。假设有一张很大的 图片被XML文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生inflationException,这个问题的根本原因是发生了OOM。
Android面试系列2018总结(全方面覆盖Android知识结构)
图片有以下存在形式: 1.文件形式(即以二进制形式存在于硬盘上) 2.流的形式(即以二进制形式存在于内存中) 3.Bitmap形式:图片大小=宽px高px色彩模式单位像素所占内存
质量压缩: 特点: File形式的图片确实被压缩了, 但是当你重新读取压缩后的file为 Bitmap是,它占用的内存并没有改变
二次采样压缩: 特点:通过设置采样率, 减少图片的像素, 达到对内存中的Bitmap进行压缩
色彩模式压缩: 特点:由ARGB8888调整为GRB565内存占用减少一半
注意: png不能色彩模式压缩 质量压缩改变的是file形式的大小 二次采样改变的是bitmap形式的大小
PNG格式有8位、24位、32位三种形式, 其中 8位PNG支持两种不同的透明形式(索引透明和alpha透明), 24位PNG不支持透明, 32位PNG在24位基础上增加了8位透明通道,因此可展现256级透明程度。 PNG8和PNG24后面的数字则是代表这种PNG格式最多可以索引和存储的颜色值。 8代表2的8次方也就是256色,而24则代表2的24次方大概有1600多万色。
当使用像 imageView.setBackgroundResource,imageView.setImageResource, 或者 BitmapFactory.decodeResource 这样的方法来设置一张大图片的时候,
这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。
因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap, 再将其设为ImageView的 source, decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode, 无需再使用java层的createBitmap,从而节省了java层的空间。 如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out of Memory异常。
另外,需要特别注意:
decodeStream是直接读取图片资料的字节码了, 不会根据机器的各种分辨率来自动适应,
使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源,
否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。
setImageResource/setBackgroundResource/setImageBitmap/直接xml设置src/直接xml设置background:
设置图片所占内存与设置的大小无关,且强制回收回收不掉,xml直接设置比set方法所占内存少
setImageResource:占用内存如下: 20dp20dp 占用55.34MB 200dp200dp 占用55.39MB match_parent 占用56.1MB 所以设置图片src占用内存与设置尺寸无关
setBackgroundResource:占用内存如下: 20dp20dp 占用56.33MB 200dp200dp 占用55.37MB match_parent 占用55.36MB setImageBitmap:占用内存如下: 20dp20dp 占用55.35MB 200dp200dp 占用55.34MB match_parent 占用55.43MB
直接设置src: 20dp20dp 占用49.91MB 200dp200dp 占用49.95MB match_parent 占用49.89MB
直接设置background: 20dp20dp 占用49.79MB 200dp200dp 占用49.91MB match_parent 占用49.83MB
glide设置图片: 20dp20dp 占用49.89MB 200dp200dp 占用49.98MB match_parent 占用55.88MB