why set 'DecodeFormat.PREFER_RGB_565' is failed? this problem will use too much native memory。
- Dominant language
- Java
- Stars
- 35k
- Forks
- 6.2k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 8
Description
Tips: English description is below this comment!
一、问题
在使用glide的过程中发现默认设置的RGB_565没有生效,而此配置可以使图片内存减少一半,分析源码并尝试了修复方案,只是心里还有些疑问,是我其他配置有问题还是这本身是个bug?
二、分析glide内创建图片核心流程
1、业务调用
Glide.with(MainActivity.this).load("xxxx").format(DecodeFormat.PREFER_RGB_565).into(imageView);
3、创建图片核心流程
glide创建bitmap主要经过6个流程:
业务调用接口,传入参数
读取原始图片信息:getDimensions方法获取原始图片信息,如图片尺寸、类型等;
计算目标图片尺寸、解码配置:calculateScaling计算合适的目标图片尺寸,calculateConfig计算合适的目标图片解码配置;
预创建目标图片bitmap:从bitmapPool里取可以复用的bitmap,如果没有则新创建一个,这一步中有申请内存,但是像素数据并没有加载原始图片内容;
设置inbitmap:如上3中创建的bitmap放到options.inBitmap中;
加载原始图片到目标bitmap:根据inBitmap可复用的特性,decodeStream后,将原始图片的内容数据写入并返回,返回的和inBitmap是同一对象;
三、核心流程拆解
1、业务调用后传入参数
前面的流程冗长,我们直接看传入到DownSample.decodeFromWrappedStreams()的参数:
`
private Bitmap decodeFromWrappedStreams(
ImageReader imageReader,
BitmapFactory.Options options,
DownsampleStrategy downsampleStrategy,
DecodeFormat decodeFormat,
PreferredColorSpace preferredColorSpace,
boolean isHardwareConfigAllowed,
int requestedWidth,
int requestedHeight,
boolean fixBitmapToRequestedDimensions,
DecodeCallbacks callbacks)
throws IOException {`
其中:
decodeFormat= “PREFER_RGB_565"
options:
inPreferredConfig = null
outConfig = null
outHeight = 0
outMimeType = null
outWidth = 0
注意:decodeFormat为RGB_565,是我们预期的解码配置。
2、读取原始图片信息
在getDimensions方法中,通过设置options属性
options.inJustDecodeBounds = true,只读取图片信息,并不真的创建bitmap。
得到:options:
inPreferredConfig = null
outConfig = {Bitmap$Config@13579} "ARGB_8888"
outHeight = 1333
outMimeType = "image/jpeg"
outWidth = 750
疑问也就从这里开始了,为什么outConfig是ARGB_8888? 分析内部具体的流程:
在BitmapFactory.cpp的doDecode()函数中:
```
// 拿到传入的options参数
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
gOptions_configFieldID = GetFieldIDOrDie(env, options_class, "inPreferredConfig",
"Landroid/graphics/Bitmap$Config;");
// 实际上述:jconfig = null
// 找到对应的type
prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
// 返回:SkColorType.kUnknown_SkColorType,
SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
//返回:kN32_SkColorType,实际也就是 kRGBA_8888_SkColorType
jint configID = GraphicsJNI::colorTypeToLegacyBitmapConfig(decodeColorType);
// 返回:5
jobjectconfig = env->CallStaticObjectMethod(gBitmapConfig_class,
gBitmapConfig_nativeToConfigMethodID, configID);
// 返回 ARGB_8888
```
其中:
```
gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class,
"nativeToConfig", "(I)Landroid/graphics/Bitmap$Config;");
//Bitmap$Config.java
private static Config sConfigs[] = {
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
};
@UnsupportedAppUsage
static Config nativeToConfig(int ni) {
return sConfigs[ni];
}
```
综合以上,我们可以得知:
在调用getDimensions函数之后,由于传入的options.inPreferredConfig为null,走到了默认值,最后得到的outConfig=ARG_B8888。稍微注意下的是,这里在native层存的是数字5,通过JNI回调到Java层,最终拿到的是 sConfigs[5]。
3、计算目标图片尺寸、解码配置
图片尺寸本篇内容并不关心,暂且省略。
解码配置源码如下:
```
private void calculateConfig(
ImageReader imageReader,
DecodeFormat format,
boolean isHardwareConfigAllowed,
boolean isExifOrientationRequired,
BitmapFactory.Options optionsWithScaling,
int targetWidth,
int targetHeight) {
if (hardwareConfigState.setHardwareConfigIfAllowed(
targetWidth,
targetHeight,
optionsWithScaling,
isHardwareConfigAllowed,
isExifOrientationRequired)) {
return;
}
// Changing configs can cause skewing on 4.1, see issue #128.
if (format == DecodeFormat.PREFER_ARGB_8888
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
optionsWithScaling.inPreferredConfig = Bitmap.Config.ARGB_8888;
return;
}
boolean hasAlpha = false;
try {
hasAlpha = imageReader.getImageType().hasAlpha();
} catch (IOException e) {
if (kLog.isLoggable(TAG, Log.DEBUG)) {
Log.d(
TAG,
"Cannot determine whether the image has alpha or not from header"
+ ", format "
+ format,
e);
}
}
optionsWithScaling.inPreferredConfig =
hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
if (optionsWithScaling.inPreferredConfig == Config.RGB_565) {
optionsWithScaling.inDither = true;
}
}
```
有上可总结以下3点:
如果设置支持硬件解码,则无需关注是ARGB_8888还是RGB_565;
如果传入参数为ARGB_8888,则设置为ARGB_8888;
如果SDK版本为16,则直接设置为ARGB_8888;
如果上述条件不成立,则判断原始图片如果没有alpha透明通道,则设置为RGB_565;
经过这一步之后:options的值如下:
inPreferredConfig = {Bitmap$Config@13594} "RGB_565"
outConfig = {Bitmap$Config@13579} "ARGB_8888"
outHeight = 1333
outMimeType = "image/jpeg"
outWidth = 750
4、设置inBitmap
看源码:
```
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (options.inPreferredConfig == Config.HARDWARE) {
return;
}
expectedConfig = options.outConfig;// 由2可知,这里是ARGB_8888
}
if (expectedConfig == null) {
expectedConfig = options.inPreferredConfig;
}
// BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}
```
**关键的地方就在这一行:**
`expectedConfig = options.outConfig;// 由2可知,这里是ARGB_8888`
创建bitmap时,bitmap.comfg的配置用的是outConfig,也就是ARGB_8888,而不是inPreferredConfig。
四、尝试修复的方案
由上述可知,如果我们优先使用inPreferredConfig,则可以解决该问题:
```
private static void setInBitmap(
BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
@Nullable Bitmap.Config expectedConfig = null;
// Avoid short circuiting, it appears to break on some devices.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (options.inPreferredConfig == Config.HARDWARE) {
return;
}
}
expectedConfig = options.inPreferredConfig;
if (VERSION.SDK_INT >= VERSION_CODES.O && expectedConfig == null) {
expectedConfig = options.outConfig;
}
options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
}
```
同时,还得注意在calculateConfig方法中修复一处:
这样当外部设置未RGB565时,inPreferredConfig才能正确赋值到565。
```
// Changing configs can cause skewing on 4.1, see issue #128.
if (format == DecodeFormat.PREFER_ARGB_8888
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
optionsWithScaling.inPreferredConfig = Bitmap.Config.ARGB_8888;
LogUtil.i(TAG, "calculateConfig: 888" + format);
return;
} else if (format == DecodeFormat.PREFER_RGB_565) {//增加565的判断
optionsWithScaling.inPreferredConfig = Config.RGB_565;
optionsWithScaling.inDither = true;
LogUtil.i(TAG, "calculateConfig: 565" + format);
return;
}
```
Contributor guide
Assessment
This issue has not been assessed yet.