4

关于Android中图片大小、内存占用与drawable文件夹关系的研究与分析

 3 years ago
source link: http://www.androidchina.net/3922.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
关于Android中图片大小、内存占用与drawable文件夹关系的研究与分析 – Android开发中文站
你的位置:Android开发中文站 > Android开发 > 新手入门 > 关于Android中图片大小、内存占用与drawable文件夹关系的研究与分析

从上一篇文章《Android屏幕适配全攻略》写完之后,经常会有朋友问我这个问题:“能不能一个App只提供一套切图适应所有的分辨率呢?”我觉得有必要写一篇文章来研究一下这个问题,所以就有了这篇文章。

本篇内容主要探讨以下场景:同一张图片,放置在不同的drawable文件夹,在同一设备上运行,对图片大小及内存占用有什么影响。

  • 控制变量法

采用锤子T1手机(1080*1960,xxhdpi)进行测试

对于内存的查看,使用AS自带的内存查看工具。

图片大小使用如下代码获取

private void printBitmapSize(ImageView imageView) {
Drawable drawable = imageView.getDrawable();
if (drawable != null) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
Log.d(TAG, " width = " + bitmap.getWidth() + " height = " + bitmap.getHeight());
} else {
Log.d(TAG, "Drawable is null !");
}
}

下面将给出测试的过程的截图,然后进行分析和总结

下面的测试使用的是一张720*1280分辨率的png图片,32位色,占用硬盘大小为77.11k

下面给出测试工程代码,非常简单

public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ImageView img;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img = (ImageView) findViewById(R.id.img);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
printBitmapSize(img);
}
private void printBitmapSize(ImageView imageView) {
Drawable drawable = imageView.getDrawable();
if (drawable != null) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
Log.d(TAG, " width = " + bitmap.getWidth() + " height = " + bitmap.getHeight());
} else {
Log.d(TAG, "Drawable is null !");
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

在不设置图片的情况下,App占用内存8.31M

以下为测试部分

把图片放置在drawable文件夹下,图片大小为2160 * 3840,内存占用39.88M

把图片放置在drawable-mdpi文件夹下,图片大小为2160 * 3840,内存占用39.84M

把图片放置在drawable-hdpi文件夹下,图片大小为1440 * 2560,内存占用22.26M

把图片放置在drawable-xhdpi文件夹下,图片大小为1080 * 1920,内存占用16.11M

把图片放置在drawable-xxhdpi文件夹下,图片大小为720 * 1280,内存占用11.86M

把图片放置在drawable-xxxhdpi文件夹下,图片大小为540 * 960,内存占用10.29M

从上面的测试结果,我们可以得出如下结论:

  1. 同一张图片,放在不同目录下,会生成不同大小的Bitmap
  2. Bitmap的长度和宽度越大,占用的内存就越大
  3. 图片在硬盘上占用的大小,与在内存中占用的大小完全不一样

下面我会对上面几个问题一一解释。

我们以放在drawable文件夹下面的图片为例,加载到内存之后,2160*3840大小的Bitmap占用的内存为

2160 * 3840 * 4 = 3317,7600 byte = 3,2400kb = 31.640625 M

所以drawable文件夹下的App内存占用 = 原始内存8.31M+图片内存31.64M= 39.95M ,与实际内存占用39.88M存在0.1755%的误差,在误差范围之内。

先简单解释一下上面的计算公式,长*宽是图片的像素总数,乘以4则是因为一个像素占用A、R、G、B四个通道,每个通道占用8位,所以描述一个像素需要32位即4个字节。

一个颜色通道需要8位描述,2^8=256,所以每个颜色通道就有256种状态。如果把彩色图转化成灰阶图的话,也有256种状态分割从白色到黑色之间的过渡颜色。

当然,也并不是所有格式的图片每个像素占用4字节,这和图片在加载时设置的Bitmap.Config有关,默认的是Bitmap.Config.ARGB_8888,其他类型如下:

  • Bitmap.Config.ALPHA_8 此时图片只有alpha值,没有RGB值,
    一1个像素占用一个字节
  • Bitmap.Config.ARGB_4444 一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites共16bites,即2个字节
  • Bitmap.Config.ARGB_8888 一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节。这是一种高质量的图片格式,在电脑上普通采用。它也是Android手机上一个Bitmap的默认格式。
  • Bitmap.Config.RGB_565 一个像素占用2个字节,没有alpha(A)值,即不支持透明和半透明,Red(R)值占5个bites ,Green(G)值占6个bites ,Blue(B)值占5个bites,共16bites,即2个字节。对于没有透明和半透明颜色的图片来说,该格式的图片能够达到比较的呈现效果,相对于ARGB_8888来说也能减少一半的内存开销。因此它是一个不错的选择。

那么为啥在硬盘上存储只需要77.11k,放到内存里面就需要30多M呢?

因为这根本不是一回事呀~

存放在硬盘上的图片文件,会根据各自的压缩规则进行压缩,比如Jpeg这种有损压缩的图片格式,最常使用可变字长编码的哈弗曼编码,会使用哈弗曼树,也就是最优二叉树,根据某些数据出现的频率对数据段编码,从而减少占用的硬盘大小。

比如说“10111”这个序列在图片的二进制数据中出现的概率最大,那我们可以用“01”来代替这一段数据,原来5位的数据,用2位就可以表示了,这就是压缩率60%。当然这只是打个比方,在实际操作中需要考虑“异前缀原则”等编码的基本原则。

而如果把图像读取到内存中就不一样了,因为我们需要每一个像素都能在屏幕上显示,所以会把每个像素点都加载至内存中,不会对相同像素进行压缩或者是替换,所以你也应该能明白前面提到的Bitmap占用内存大小的计算公式的由来了。

说到这里,其实后两个结论已经解释清楚了,那么为什么“同一张图片,放在不同目录下,会生成不同大小的Bitmap”呢?

如果你真的看懂了我之前写的文章,那么这个问题应该不算问题。

我的测试设备为锤子T1,1080*1960,xxhdpi,所以说,如果把这张放置在xxhdpi的话,应该不会对图像进行放缩,也就是原始大小,所以我们在前面得到drawable-xxhdpi文件夹下,图片大小为720 * 1280是完全可以理解的,就是图片本身的大小。

当图片放置在drawable-hdpi中时,图片大小为1440 * 2560,长宽变为原来的两倍,这是因为不同分辨率之间的倍数关系导致的,来一张图

我们可以很明显的看到xxhdpi是hdpi的2倍,所以如果单独放置在某个drawable文件夹,手机会自动根据当前的屏幕密度对图片进行放缩。

比如上面,当把图片放置在xxxhdpi里面的时候,在xxhdpi的设备上,图片长 = 720 * (3/4) = 540,图片宽 = 1280 * (3/4) = 960,这与上面的测试结果是完全一致的。

至于为什么在前面的测试中,drawable和drawable-mdpi是一样的大小,是因为drawable-mdpi是系统默认的像素密度,其他像素密度都以它为基数,当只在drawable中存在图片时,如果使用该图片,那么将按照drawable-mdpi的放缩比例进行放缩。

从上面的测试我们可以得出以下几个结论:

  1. 当图片放置在不同drawable文件夹中,且只有这一张图片时,运行设备会根据自身的屏幕密度,对图片进行放缩,放缩比例符合前面图上的规则
  2. 图片文件的大小与在内存中占用的大小没关系,内存中实际占用大小与图片分辨率、像素显示参数有关

所以,在一个App里面使用一套UI理论上应该是没有问题的,但是要注意

  • 最好使用较高分辨率的切图,并且放置在正确的drawable文件夹中,比如按照xxhdpi的分辨率进行切图,放置在drawable-xxhdpi中
  • 对于可以使用.9格式的图片,最好使用.9,减少资源大小
  • 如果有条件,最好提供多套UI切图。如果只有一套切图,系统需要对图片进行压缩,会进行大量运算,影响设备性能。同时,在某些情况下,系统对图片的压缩会可能会出现锯齿,造成信息的丢失
  • 如果是多套切图的话,最好不要直接用工具按照比例放缩,这样小图标会丢失一些细节。当然,这部分是美工来做的,可以让她参考这篇文章利用PS CS6的新功能保持ICON细节饱满完美

思考一下,如果把一个本来应该放在drawable-xxhdpi里面的图片放在了drawable文件夹中会出现什么问题呢?

在xxhdpi设备上,图片会被放大3倍,图片内存占用就会变为原来的9倍!

另外一个难以解释的问题

我还试着将上面那张图片放置在drawable-xxhdpi文件夹下,观察在不同屏幕密度设备的表现

xxhdpi设备(T1),图片大小为720 * 1280,内存占用 = 11.86M-8.31M= 3.55M

xxhdpi设备(N5) ,图片大小为720 * 1280,内存占用 = 20.19M-16.82M = 3.37M

xxhdpi设备(魅族m351),图片大小为720 * 1280,内存占用 = 20.17M-17.84M = 2.33M

xhdpi设备(华为g716-l070) 内存占用 = 7.72M-2.66M = 5.06M

hdpi设备(联想A360e),图片大小为360 * 640,内存占用 = 2.83M-2.96M = -0.7M

hdpi设备(酷派7269),图片大小为320 * 568,内存占用 = 6.85M-2.78M = 4.07M

然后我就凌乱了,这是什么玩意!除了俺的大锤子和原生的N5符合图片内存计算公式,其他的设备都是什么玩意!联想手机的还是负值!不过我在模拟机上测试得到的也是类似的效果,这个测试与上面的应该是同一种情况,具体原因不得而已。

有知晓原因的可以告诉我,谢谢。

尊重原创,转载请注明:From 凯子哥(http://blog.csdn.net/zhaokaiqiang1992) 侵权必究!

转载请注明:Android开发中文站 » 关于Android中图片大小、内存占用与drawable文件夹关系的研究与分析


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK