glide
                                
                                
                                
                                    glide copied to clipboard
                            
                            
                            
                        why set 'DecodeFormat.PREFER_RGB_565' is failed? this problem will use too much native memory。
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;
}
                                    
                                    
                                    
                                
@sjudd hello ,excuse me, this problem has troubled me for a long time, can you help me? Thank you!
@zengjingfang Please translate this to English.
Also found out about this, PREFER_RGB_565 does not work.
There is this line in the docs for 565:
On Android O+, this format will use `ARGB_8888` only when it's not possible to use `android.graphics.Bitmap.Config#HARDWARE`
I'm using the latest MIUI 11, which is Android 9 and it should support hardware bitmap, then I expect PREFER_565 to work.
1、Question
when i use glide, I found that the setting of RGB_565 does not work, and this configuration can reduce the image memory by half. I analyzed the source code and tried a repair solution, but I still have some questions in my mind. Is there a problem with my other configuration or is this itself a bug?
2、Analyze the core process of creating bitmap in glide
2.1、Business call
Glide.with(MainActivity.this).load("xxxx").format(DecodeFormat.PREFER_RGB_565).into(imageView);
2.2、Glide creates bitmap through 6 main processes:
- Service call interface, input parameters Read the original picture information: getDimensions method to get the original picture information, such as picture size, type, etc.;
 - Calculate the target picture size and decoding configuration: calculateScaling calculates the appropriate target picture size, and calculateConfig calculates the appropriate target picture decoding configuration;
 - Pre-create the target image bitmap: take a reusable bitmap from the bitmapPool, if not, create a new one. In this step, memory is requested, but the pixel data does not load the original image content;
 - Set inbitmap: put the bitmap created in 3 above into options.inBitmap;
 - Load the original image to the target bitmap: According to the reusable characteristics of inBitmap, after decodeStream, the content data of the original image is written and returned, and the returned object is the same object as inBitmap;
 
3、 the core process disassembly
3.1. Pass in parameters after business call
The previous process is lengthy, let's look directly at the parameters passed into 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 {
}
Among Them:
decodeFormat= “PREFER_RGB_565"
options:
inPreferredConfig = null
outConfig = null
outHeight = 0
outMimeType = null
outWidth = 0
Pay attention this :decodeFormat is RGB_565, which is our expected decoding configuration.
3.2. Read the original picture information
In the getDimensions method, by setting the options property options.inJustDecodeBounds = true, only read the picture information, not really create a bitmap. Get: options:
inPreferredConfig = null
outConfig = {Bitmap$Config@13579} "ARGB_8888"
outHeight = 1333
outMimeType = "image/jpeg"
outWidth = 750
The question begins here, why is outConfig ARGB_8888? Analyze the internal specific process:
In the doDecode() function of BitmapFactory.cpp:
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); 
// return:jconfig = null
gOptions_configFieldID = GetFieldIDOrDie(env, options_class, "inPreferredConfig", 
"Landroid/graphics/Bitmap$Config;");
// found target type
prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
// return:SkColorType.kUnknown_SkColorType,
SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
//return:kN32_SkColorType,实际也就是 kRGBA_8888_SkColorType
jint configID = GraphicsJNI::colorTypeToLegacyBitmapConfig(decodeColorType); 
// return:5
jobjectconfig = env->CallStaticObjectMethod(gBitmapConfig_class, gBitmapConfig_nativeToConfigMethodID, configID);
// return: ARGB_8888
and
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];
}
Based on the above, we can know:
After calling the getDimensions function, because the options.inPreferredConfig passed in is null, the default value is reached, and finally outConfig=ARG_B8888 is obtained. A little bit of attention is that the number 5 stored in the native layer here is called back to the Java layer through JNI, and the final result is sConfigs[5]=ARGB_8888.
3.3. Calculate the target image size and decoding configuration
The picture size is not concerned with the content of this article, so it is omitted for now.
The decoding configuration source code is as follows:
  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;
    }
  }
There are three points that can be summarized as follows:
- If the setting supports hardware decoding, there is no need to pay attention to whether it is ARGB_8888 or RGB_565;
 - If the incoming parameter is ARGB_8888, set it to ARGB_8888;
 - If the SDK version is 16, set it directly to ARGB_8888;
 - If the above conditions are not established, it is judged that if the original picture does not have an alpha transparency channel, it is set to RGB_565;
 
After this step: the values of options are as follows:
inPreferredConfig = {Bitmap$Config@13594} "RGB_565"
outConfig = {Bitmap$Config@13579} "ARGB_8888"
outHeight = 1333
outMimeType = "image/jpeg"
outWidth = 750
3.4、Set 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;// By 3.2, here is 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);
  }
The key place is in this line:
expectedConfig = options.outConfig;// By 3.2, here is ARGB_8888
When creating bitmap, the configuration of bitmap.config is outConfig, which is ARGB_8888, instead of inPreferredConfig.
4、 try to repair the program
It can be seen from the above that if we prefer to use inPreferredConfig, we can solve this problem:
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);
}
At the same time, you have to pay attention to fix one in the calculateConfig method: In this way, when the external setting is not RGB565, inPreferredConfig can be correctly assigned to 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) {//add this for RGB_565
  optionsWithScaling.inPreferredConfig = Config.RGB_565;
  optionsWithScaling.inDither = true;
  LogUtil.i(TAG, "calculateConfig: 565" + format);
  return;
}
Through the above solution, I solved my problem, but I don’t understand why glide was designed in this way???
@nvmnghia thanks for your comment, i translated it on the top comment. From what you mentioned in the doc,maybe the glide is just deigned by this? This is not very suitable for our business。We need to save memory usage ,But I’m not sure that after changing to RGB565, there will be other effects besides the sharpness。
@zengjingfang I tested it again in API 22 emu, and it seems that PREFER_RGB_565 does work.
From what I've read, and the docs, I believe that:
- when Hardware Bitmap is available, Glide will use it, and
 - hardware bitmap always operates in 
ARGB_8888, which seems reasonable (whose screen runs at 16 bit?) 
Hardware bitmap stores one bitmap in memory. If you don't use hardware bitmap, there are 2 copies in memory. PREFER_RGB_565 will reduce memory footprint for 1 copy, the one in graphics memory is still ARGB_8888.
So what Glide does (and is able to do) is already optimized. PREFER_RGB_565 will do 565 on API < 26, and on API >= 26, it is ignored and you have both quality and space gain.
I'm just 5 weeks into Android, and not proficient enough to use the profiler to prove this lol.
As per the effects of 565, I see a green tint on the images.
@nvmnghia If according to what you say, when we use Config.HARDWARE,maybe is right.But i test it by AndroidStudio's Profiler,the result is below:
Test Base:
Device version:   29
Glide version:4.11.0
Bitmap:1080 x 1920 x 4=8100K, as 7.9M
Glide.with(MainActivity.this).load("xxxx").into(imageView);
Test A: Open HardWare
android:hardwareAccelerated="true"
Graphics:15.6-6.7=8.9M Native:27-12.5=14.5M

Test B:Close HardWare
android:hardwareAccelerated="false"
Graphics:0-0=0M Native:27-12.5=14.5M

From the above comparison test, we can know that: Native memory allocated is same , so there is must not right。when we use glide as this way:
Glide.with(MainActivity.this).load("xxxx").into(imageView);
It is just as ARGB_8888 mode ,but not HARDWARE。
Then, I test a sample demo:
live.jpg: 750x1333
android:hardwareAccelerated="true"
Config.HARDWARE
Options options = new Options();
 if (VERSION.SDK_INT >= VERSION_CODES.O) {
      options.inPreferredConfig = Config.HARDWARE;
 }
imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.live, options));
Result:
native: 21.4-16.4=5M
Graphics:11.2-6.7=4.5M

Config.RGB_565
Options options = new Options();
 if (VERSION.SDK_INT >= VERSION_CODES.O) {
      options.inPreferredConfig = Config.RGB_565;
 }
imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.live, options));
Result: native: 23.7-16.4=7.3M Graphics:10.2-7.7=2.5M

From the above comparison test, we can know that:
HARDWARE mode:
- native memory allocated 0M
 - Graphics memory allocated 4M
 
RGB_565 mode:
- native memory allocated 2M
 - Graphics memory allocated 2M
 
So , only when we set Config.HARDWARE before create bitmap ,the hardware is worked. But, glide did not do it on my business。
At last, thanks for your help。I think the question now is why "hardware" does not work ?
So I suspect you're running into a mismatch between intention and reality.
Glide does not in fact default to allowing hardware bitmaps. The goal was to do so, but I keep running into roadblocks. Behavior is inconsistent across different versions of Android and I've seen a bunch of OEM or device specific issues with hardware Bitmaps.
If you'd like to enable hardware Bitmaps in your application, you need to set Downsampler's allowHardwareConfig option to true: https://github.com/bumptech/glide/blob/5acc99f8fc7e772aeef8aa959ff2a9c203ab1aa6/library/src/main/java/com/bumptech/glide/manager/DefaultConnectivityMonitor.java#L72.
The documentation here is unfortunately incorrect: http://bumptech.github.io/glide/doc/hardwarebitmaps.html#how-do-we-enable-hardware-bitmaps.
Right now I'd only recommend enabling hardware bitmaps on Android P or higher. I'll leave this open as a todo to update the docs. Thanks everyone for a great thread.
@sjudd I am so happy to see your response, I think there is another question, when i set ".format(DecodeFormat.PREFER_RGB_565",but at last is ARGB_8888 ?
we just guess ARGB_8888 is for Hardware?
I think RGB_565 should still be preferred even on versions where HARDWARE bitmaps could be supported.
It's possible that ARGB_8888 is being used because the format of the image you're decoding supports transparency: https://github.com/bumptech/glide/blob/5acc99f8fc7e772aeef8aa959ff2a9c203ab1aa6/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java#L652
@sjudd
On my test :
Glide.with(MainActivity.this).load("xxxx").format(DecodeFormat.PREFER_RGB_565).into(imageView); 
and the picture need not supports transparency,
but at last, the real is ARGB_8888, but not RGB_565。
I can confirm that PREFER_RGB_565 is ignored for me as well, I'm using jpeg images which have no alpha.
I beleive the reason why RGB_565 is ignored is because we are ignoring inPreferredConfig if outConfig is set (see Downsampler.java#L872). The outConfig field in turn is populated when we are reading image size in Downsampler.java#L280, or more precisely in Downsampler.java#L742. So basically whenever we want to read image size the outConfig is populated with ARGB_8888 by BitmapFactory.decodeStream and we use it as-is. I think we should either ignore outConfig and always use inPreferredConfig or we can try to set inPreferredConfig before reding the image size, which will hopefully return outConfig=RGB_565.
If the above is correct then it looks like PREFER_RGB_565 is broken since this commit made 3 years ago.
Hello, I have met a similar problem, for example, when I use
Glide.with(imageView)
            .asBitmap()
            .load(url)
            .format(DecodeFormat.PREFER_RGB_565)
            .listener(object :RequestListener<Bitmap>{
                override fun onLoadFailed(
                    e: GlideException?,
                    model: Any?,
                    target: Target<Bitmap>?,
                    isFirstResource: Boolean
                ): Boolean {
                    return false
                }
                override fun onResourceReady(
                    resource: Bitmap?,
                    model: Any?,
                    target: Target<Bitmap>?,
                    dataSource: DataSource?,
                    isFirstResource: Boolean
                ): Boolean {
                    Log.i("TAG",resource?.config?.toString()?:"123")
                    return false
                }
            })
            .into(imageView)
the log printed is [TAG, RGB_565], but if I use this
Glide.with(imageView)
            .asBitmap()
            .load(url)
            .circleCrop() // this is the difference
            .format(DecodeFormat.PREFER_RGB_565)
            .listener(object :RequestListener<Bitmap>{
                override fun onLoadFailed(
                    e: GlideException?,
                    model: Any?,
                    target: Target<Bitmap>?,
                    isFirstResource: Boolean
                ): Boolean {
                    return false
                }
                override fun onResourceReady(
                    resource: Bitmap?,
                    model: Any?,
                    target: Target<Bitmap>?,
                    dataSource: DataSource?,
                    isFirstResource: Boolean
                ): Boolean {
                    Log.i("TAG",resource?.config?.toString()?:"123")
                    return false
                }
            })
            .into(imageView)
the log printed is [TAG, ARGB_8888].
The reason is when I use circleCrop(), the outConfig is set "ARGB8888", and then in method setInBitmap() expectedConfig is set options.outConfig(ARGB8888).
Can you help me solve the problem? I just want to load a bitmap in circle and in rgb565.
@basti-shi031 I doubt you can use ~ARGB_8888~ RGB_565 with circle-croped images because they require alpha channel. If you'll use RGB_565 then you'll have to fill the background with solid color. So in your case I would say ARGB_8888 is a correct format.
@basti-shi031 I doubt you can use ARGB_8888 with circle-croped images because they require alpha channel. If you'll use RGB_565 then you'll have to fill the background with solid color. So in your case I would say ARGB_8888 is a correct format.
I think you mean "I doubt u can use RGB 565". "doubt" here means "don't believe". If so, I agree with the rest.
thank you. I got it!
@zengjingfang Hi 看了您的全民K歌native 内存优化相关的博客,里面提到了Glide 565无效的这个点,想问下最终项目里面有去处理这个问题吗。带来的收益如何?
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; }
expectedConfig = options.inPreferredConfig; 这个是尝试的方案?可行么
@
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; }expectedConfig = options.inPreferredConfig; 这个是尝试的方案?可行么
可以,千万级app验证
@zengjingfang Hi 看了您的全民K歌native 内存优化相关的博客,里面提到了Glide 565无效的这个点,想问下最终项目里面有去处理这个问题吗。带来的收益如何?
最终落地稳定运行两年多了,当时我们很多内存相关优化是一起在做的,无法明确单算,整体oom之类内存相关crash率显著下降。自己单独跑过对比, glide内部内存有自己的缓存机制,整体内存减少10-20M左右。
Hello, I have met a similar problem, for example, when I use
Glide.with(imageView) .asBitmap() .load(url) .format(DecodeFormat.PREFER_RGB_565) .listener(object :RequestListener<Bitmap>{ override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean ): Boolean { return false } override fun onResourceReady( resource: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { Log.i("TAG",resource?.config?.toString()?:"123") return false } }) .into(imageView)the log printed is [TAG, RGB_565], but if I use this
Glide.with(imageView) .asBitmap() .load(url) .circleCrop() // this is the difference .format(DecodeFormat.PREFER_RGB_565) .listener(object :RequestListener<Bitmap>{ override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean ): Boolean { return false } override fun onResourceReady( resource: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { Log.i("TAG",resource?.config?.toString()?:"123") return false } }) .into(imageView)the log printed is [TAG, ARGB_8888].
The reason is when I use circleCrop(), the outConfig is set "ARGB8888", and then in method setInBitmap() expectedConfig is set options.outConfig(ARGB8888).
Can you help me solve the problem? I just want to load a bitmap in circle and in rgb565.
you need fix in glide source code ,just look my 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; }expectedConfig = options.inPreferredConfig; 这个是尝试的方案?可行么
可以,千万级app验证
源码依赖glide改的?
@
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; }expectedConfig = options.inPreferredConfig; 这个是尝试的方案?可行么
可以,千万级app验证
源码依赖glide改的?
是的,修改源码
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); }
// 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; }
还想请教下: 这两处改动后,的确让 RGB 565 生效了,但是会把原本 「Glide 有透明通道的图片默认RGB8888 这个逻辑」忽略了,譬如一张透明图可能会变黑,关于这点,你们应用遇到过问题么,是怎么权衡考虑的呢
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); }
// 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; }
还想请教下: 这两处改动后,的确让 RGB 565 生效了,但是会把原本 「Glide 有透明通道的图片默认RGB8888 这个逻辑」忽略了,譬如一张透明图可能会变黑,关于这点,你们应用遇到过问题么,是怎么权衡考虑的呢
1、我们仅极少数的图片才使用RGB8888,外边调用glide接口时声明 其他我们默认用565。 2、不过如果原图文件是带透明通道的话,glide内部最终还是会强制走RGB8888。(很久没搞了,印象是这样的,你可以自测验证下)
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); } // 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; } 还想请教下: 这两处改动后,的确让 RGB 565 生效了,但是会把原本 「Glide 有透明通道的图片默认RGB8888 这个逻辑」忽略了,譬如一张透明图可能会变黑,关于这点,你们应用遇到过问题么,是怎么权衡考虑的呢
1、我们仅极少数的图片才使用RGB8888,外边调用glide接口时声明 其他我们默认用565。 2、不过如果原图文件是带透明通道的话,glide内部最终还是会强制走RGB8888。(很久没搞了,印象是这样的,你可以自测验证下)
不是不是,我意思是:按你的描述,改两处地方:
一处 DownSampler.setInBitmap里 expectedConfig = options.inPreferredConfig;
一处是DownSampler.calculateConfig 里的
第二处的改动影响到 Glide 源码DownSampler.calculateConfig 关于透明度判断逻辑??
所以只要改第一处就好了?
我也遇到了这个问题,为啥这么久了,官方还不修这个问题? 我觉得mikelhm说的对,只修复第一处就好了,第二处会改出问题。