F:\Android5\android5.1\frameworks\base\core\jni\android\graphics
参考文章:
- Android内存管理详细介绍
- Android性能优化(五)之细说Bitmap
- Glide缓存机制
- Bitmap的图片压缩汇总
- Glide如何通过引用计数复用Bitmap
- Android中图片压缩分析(上)
- Android中图片压缩分析(下)
- Android系统源码分析-bitmap的加载
一、Bitmap像素存储的方式配置:
格式 | 解释 | 内存占用 |
---|---|---|
ALPHA_8 | 一个像素占8位 | 1字节 |
RGB_565 | 一个像素占5+6+5 = 16位 | 2字节 |
ARGB_4444 | 一个像素占4 * 4 = 16位 | 2字节 |
ARGB_8888 | 一个像素占8 * 4 = 32 | 4字节 |
二、图片所在文件夹对应的密度:
图片所在文件夹 | 密度density |
---|---|
mdpi | 160 |
hdpi | 240 |
xhdpi | 320 |
xxhdpi | 480 |
xxxhdpi | 640 |
三、Bitmap内存占用方面:
- 不考虑压缩, 加载一张Bitmap, 它占用的内存 = 图片width * 图片height * 一个像素所占的内存 * (nTargetDensity/inDensity)2;
- 假设使用一张128 * 128的图片, 放在drawable-xhdpi目录下, 默认像素模式采用Bitmap.Config.ARGB_8888;
public static void bitmapTest() {
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(Utils.getContext().getResources(), R.drawable.logo, options);
LogUtils.log("ByteCount:" + bitmap.getByteCount() + ",AllocationByteCount:" + bitmap.getAllocationByteCount());
LogUtils.log("width:" + bitmap.getWidth() + ",height:" + bitmap.getHeight());
LogUtils.log("inDensity:" + options.inDensity + ",inTargetDensity:" + options.inTargetDensity);
}
打印结果如下所示:
V/AndroidTest: BitmapTest->getByteCount:36864,getAllocationByteCount:36864
V/AndroidTest: BitmapTest->width:96,height:96
V/AndroidTest: BitmapTest->inDensity:320,inTargetDensity:240
注意:
36864 = 128 * 128 * 4 * (240/320) * (240/320);
36864 = 96 * 96 * 4;
注意这里的结果, 与前面的内存计算公式, 宽高指的是图片本身的宽高, 图片被加载进Bitmap之后,
通过Bitmap获取的width, height是缩放之后的宽高, 并不是图片本身的宽高, 此时要过要计算
内存, 直接使用bitmap.width * bitmap*height * (一个像素所占内存)即可;
至于为何会出现上面的计算情况, 从源码入手:
3.1 BitmapFactory.decodeResource:
public static Bitmap decodeResource(Resources res, int id, Options opts) {
...
bm = decodeResourceStream(res, value, is, null, opts);
return bm;
}
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
/**
* inTargetDensity为当前系统密度, 与设备分辨率有关;
*/
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
Bitmap bm = null;
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
/**
* 最终通过native将流数据加载进Bitmap中;
*/
bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
setDensityFromOptions(bm, opts);
return bm;
}
3.2 BitmapFactory.nativeDecodeAsset
static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset,
jobject padding, jobject options) {
Asset* asset = reinterpret_cast<Asset*>(native_asset);
SkAutoTDelete<SkStreamRewindable> stream(new AssetStreamAdaptor(asset));
/**
* 给Bitmap创建分配堆内存, 并且将数据流读入到Bitmap, 并且对Bitmap的宽高进行初始化;
*/
return doDecode(env, stream, padding, options);
}
3.3 BitmapFactory.nativeDecodeAsset:
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
int sampleSize = 1;
...
float scale = 1.0f;
...
if (options != NULL) {
...
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
const int density = env->GetIntField(options, gOptions_densityFieldID);
const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
if (density != 0 && targetDensity != 0 && density != screenDensity) {
/**
* 1. 根据density以及targetDensity算出缩放比例;
* 2. density对应java层的Options.inDensity, 图片所在文件夹对应的密度;
* 3. targetDensity对应java层的Options.inTargetDensity, 系统当前密度;
*/
scale = (float) targetDensity / density;
}
}
}
const bool willScale = scale != 1.0f;
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
SkBitmap decodingBitmap;
/**
* 流数据解码进SkBitmap中;
*/
if (decoder->decode(stream, &decodingBitmap, prefColorType, decodeMode)
!= SkImageDecoder::kSuccess) {
...
}
/**
* 获取解码后的Bitmap原始宽高;
*/
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
/**
* 通过这里算出缩放之后的图片的宽高;
*/
scaledWidth = int(scaledWidth * scale + 0.5f);
scaledHeight = int(scaledHeight * scale + 0.5f);
}
if (options != NULL) {
jstring mimeType = getMimeTypeString(env, decoder->getFormat());
/**
* 算出缩放后的图片宽高之后, 再回传给java层的Bitmap.mWidth, Bitmap.mHeight;
*/
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
}
SkBitmap outputBitmap;
if (willScale) {
/**
* 结合前面描述可知这里的sx, sy实际等于scale;
*/
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());
SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
colorType, decodingBitmap.alphaType()));
SkPaint paint;
paint.setFilterQuality(kLow_SkFilterQuality);
SkCanvas canvas(outputBitmap);
canvas.scale(sx, sy);
canvas.drawARGB(0x00, 0x00, 0x00, 0x00);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
outputBitmap.swap(decodingBitmap);
}
/**
* 创建Bitmap对象, 并将Bitmap对象返回给java层;
*/
return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}
- 1、结合上面代码可知, 图片在加载进Bitmap时, 会对原始尺寸进行缩放, 缩放系数 scale = targetDensity / density;图片被加载进Bitmap之后, 宽高分别为OriWidth * scale, OriHeight * scale, 因此图片被加载进Bitmap之后, 内存占用为OriWidth * OriHeight * (targetDensity / density)2 * 一个像素占用;
3.4 关于内存占用:
- 1、通过模块<3.1> ~ <3.3>的分析可知, 每次加载一张图片, 都需要创建一个Bitmap, Bitmap需要的内存与图片的分辨率有关, 越高清默认占用的内存也就越多;
- 2、通过源代码分析可知, 减小scale, 可以减少Bitmap占用的内存, scale实际与图片所在drawable文件夹有关, 而且影响也不是太明显, 通过模块<二>可知, 比例范围最大也就是4倍;
- 3、还可以通过采样率sampleSize的方式对图片进行压缩达到减少Bitmap内存占用;
- 4、第三个就是Bitmap复用, 关于Bitmap复用, 直接以Glide源码为例进行学习(https://www.jianshu.com/p/eaadeacd3848);
网友评论