Android中一张图片占用的内存大小中一张图片占用的内存大小
最近面试过程中发现对Android中一些知识有些模棱两可,之前总是看别人的总结,自己没去实践过,这两天对个别问题进行专门研究
探讨:如何计算探讨:如何计算Android中一张图片占据内存的大小中一张图片占据内存的大小
解释:此处说的占据内存,是APP加载图片用的内存,即APP运行时图片占用的内存,不考虑图片显示到界面占用的内存(即只加载图片,不显示。因为显示图片时,系统有可能根据
控件的大小或其他因素对内存进行了优化,不确定因素太多),同时也不考虑使用三方库加载图片占用的内存,如glide、等三方图片库,三方库在加载图片的过程中有可能对图片进行
了优化,会<=图片加载占用的内存。
将通过BitmapFactory加载的方式,获取bitmap的字节数,来计算图片占用的内存大小。建议,为了避免因系统对bitmap对象的复用(有可能)导致的bitmap字节数的偏差,每次获取对
象大小前,最好杀死应用,从新打开App。
本片博客讲诉的内容,有不正确的地方,欢迎大家斧正。由于很多博客都已经讲过这个问题,这里只做基本知识梳理、补充。
图片内存大小跟占用空间大小有什么关系?图片内存大小跟占用空间大小有什么关系?
占用空间的大小不是图片占用内存的大小,占用空间是在磁盘(电脑硬盘或者手机SD卡)上占用的空间,内存大小是加载到内存中占用的内存大小。两个只是单位是一样的,但不是
一个概念,不要混淆。
这里所说的图片分两类:
硬盘中的图片硬盘中的图片
res中的图片中的图片
图片占用内存图片占用内存 = bitmap 宽宽 * bitmap 高高 * 每个像素占用的字节数每个像素占用的字节数
APP资源目录资源目录res中的图片,加载到内存时,占用的内存大小中的图片,加载到内存时,占用的内存大小
所得结论:
公式1:
图片占用内存图片占用内存 = bitmap 宽宽 * bitmap 高高 * 每个像素占用的字节数每个像素占用的字节数
或公式2:
图片占用的内存图片占用的内存 = 原图宽原图宽 * (inTargetDensity / inDensity) * 原图高原图高 * (inTargetDensity / inDensity) * 每个像素占用的字节数每个像素占用的字节数
即图片所有像素点占用的字节数
如果 inTargetDensity / inDensity > 1则,加载到内存 图片被放大,否则被缩小
图片占用内存图片占用内存 = bitmap 宽宽 * bitmap 高高 * 每个像素占用的字节数每个像素占用的字节数
bitmap 宽宽 = 原图的宽原图的宽 * (设备的(设备的 dpi / 目录对应的目录对应的 dpi))
bitmap 高高 = 原图的高原图的高 * (设备的(设备的 dpi / 目录对应的目录对应的 dpi))
注意 :如果 BitmapFactory.Options options = new BitmapFactory.Options(); 中 options.inSampleSize != 0 ,则以上公式中。默认options.inSampleSize为0,源码中关于此属性有一
段注释,如果为0,则nativate层按1来取值
inSampleSize > 1时
bitmap 宽宽 = (原图宽(原图宽 / inSampleSize)) * (inTargetDensity / inDensity)
bitmap 高高 = (原图高(原图高 / inSampleSize)) * (inTargetDensity / inDensity)
(注意inSampleSize只能是2的幂,如不是2的幂下转到最大的2的幂,而且inSampleSize>=1)
BitmapFactory.decodeResource 加载的图片可能会经过缩放
查看android 2.3的系统源码 可以发现 float scale = targetDensity / (float)density 相关的信息。android 比较高的系统(8.0 9.0),Bitmap分配内存相关的细节放到了native层,没有细
看。如下是2.3系统相关的代码
https://www.androidos.net.cn/android/2.3.7_r1/xref/frameworks/base/graphics/java/android/graphics/BitmapFactory.java
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
return null;
}
// we need mark/reset to work properly
if (!is.markSupported()) {
is = new BufferedInputStream(is, 16 * 1024);
}
// so we can call reset() if a given codec gives up after reading up to
// this many bytes. FIXME: need to find out from the codecs what this
// value should be.
is.mark(1024);
Bitmap bm;
if (is instanceof AssetManager.AssetInputStream) {
bm = nativeDecodeAsset(((AssetManager.AssetInputStream) is).getAssetInt(),
outPadding, opts);
} else {
// pass some temp storage down to the native code. 1024 is made up,
// but should be large enough to avoid too many small calls back
// into is.read(...) This number is not related to the value passed
// to mark(...) above.
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[16 * 1024];
bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
}
return finishDecode(bm, outPadding, opts);
}
private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
if (bm == null || opts == null) {
return bm;
}
final int density = opts.inDensity;
if (density == 0) {
return bm;
}