BitmapFactory.Options中参数使用

BitmapFactory.Options基础

Options中变量命名风格

Options是一个内部类,该类中主要有下面一些成员变量。
BitmapFactory.java

inBitmap
inMutable
inJustDecodeBounds
inSampleSize
inPreferredConfig
inPreferredColorSpace
inPremultiplied
inDither
inDensity
inTargetDensity
inScreenDensity
inScaled
inPurgeable
inInputShareable
inPreferQualityOverSpeed
outWidth
outHeight
outMimeType
outConfig
outColorSpace
inTempStorage

BitmapFactory.Options主要的参数如上,查看成员变量命名发现有in和out两类命名风格,in开头的我们可以理解为设置参数,out开头的我们可以理解为获取某些参数。
通过使用上面的参数,在实际开发中我们可以很好的操作Bitmap,以减少资源的滥用,这样在保证ui效果的同时又降低了Bitmap的内存占用。

不同资源文件目录中图片的大小

在Android中计算Bitmap所占内存的大小时,可以使用下面的公式:

一张bitmap所占用的内存大小=宽度px高度px一个像素所占用的字节数

在确定图片宽度和高度的时候,不能仅仅简单的右键图片的属性查看宽度和高度的值,因为android中会根据屏幕分辨率匹配对应的资源文件目录,图片的大小会进行相应的缩放,android中不同的屏幕分辨率对应的density如下:
在这里插入图片描述
官方文档:支持不同的像素密度

  • densityDpi:屏幕密度,及Dpi(dots-per-inch)每英寸的像素数。
  • density:像素密度,表示dp(divices independent pixels)与px的换算比例 px= density*dp(dp为代码中设置的数值),其中density=dpi/160。

因此将相同的照片放到不同的资源目录中,因为dpi的不同,加载不同目录资源时得到的px大小是不同的,但是如果将图片放在非drawable资源目录,而是放在文件存储目录中,是不会进行缩放的。
代码验证:
我们加载一张2500*2500的图片进行验证:

  //首先在这里获取dpi	
  DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
  //查看日志我这个设备的dpi为480,相应的对映的资源文件为xxhdpi目录
  Log.i(TAG, "displayMetrics.densityDpi: " + displayMetrics.densityDpi);

看日志设备的dpi为480为xxhdpi,把图片放到xxhdpi(AS创建不同DPI目录的方法),加载该图片:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kite);
Log.i(TAG, "onCreate: bitmap.getWidth():" + bitmap.getWidth() + " bitmap.getHeight() " + bitmap.getHeight());

查看日志,加载的图片尺寸和原始尺寸一致: onCreate: bitmap.getWidth():2500 bitmap.getHeight() 2500
把图片放到xdpi目录,加载该图片查看日志:
onCreate: bitmap.getWidth():3750 bitmap.getHeight() 3750
日志看到图片进行了放大,xdpi为320,根据dpi进行计算:2500*(480/320)=3750,最终的图片大小为3750,所以,加载不同的dpi目录资源,得到的图片最终px大小是不一样的。

如果没有完全对应的dpi,那么该如何匹配呢?查看官方文档发现下面的描述:
在这里插入图片描述
当没有相匹配的dpi,例如当dpi为420时,则匹配的是xxhdpi的资源,这样就避免了xhdpi中的文件被放大而导致的图片模糊。
代码验证:

  DisplayMetrics displayMetrics = getResources().getDisplayMetrics();

MainActivity_main: displayMetrics.densityDpi: 420设备的dpi为420时,图片位于xxhdpi下,我们得到的图片大小为:
onCreate: bitmap.getWidth():2188 bitmap.getHeight() 2188
根据dpi进行px计算计算:(420/480)*2500=2187.5,可以看到对图片进行了缩小,取的是xxhdpi下面的图片文件。

使用inJustDecodeBounds获取图片信息

使用场景
当需要在将图片完全加载到内存之前得到图片的宽高,MIME等信息,可以将这个inJustDecodeBounds设置为true,这样就可以在不将图片加载到内存中的时候获取到图片的信息。
使用方式
在这里插入图片描述
查看官方注解,当这个值设置为true的时候,bitmap返回的是null,但是out开头的字段会携带图片相关信息。
代码

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kite, options);
        Log.i(TAG, "onCreate: bitmap:" + bitmap);
        Log.i(TAG, "onCreate: realwidth" + options.outWidth + " options.outHeight " + options.outHeight + " options.outMimeType " + options.outMimeType);

日志输出:
MainActivity_main: onCreate: bitmap:null
MainActivity_main: onCreate: realwidth2500 options.outHeight 2500 options.outMimeType image/png
可以看到bitmap为空,说明没有真正加载bitmap,同时我们获取到了图片的参数。

使用inSampleSize降低Bitmap内存占用

使用场景
android开发的过程中,很多时候我们所需要的图片大小比原图小,这时候我们就没有必要加载原图进行显示毕竟大图占用的内存要大。
使用方式
使用BitmapFactory.Options的inSampleSize来调整图片的尺寸,
查看inSampleSize的官方注解,inSampleSize在这里插入图片描述
当采样率大于一的时候 ,长和宽对应变为原来的 1/inSampleSize,对应的像素数量减少 1/(inSampleSize^2),bitmap的大小相应的也缩小为 1/(inSampleSize^2),因为bitmap的大小等于(图片长度*图片宽度*单位像素占用的字节数),同时强调解码器使用基于2的幂的最终值,任何其他值都将被舍入到最接近的2的幂次。
代码
下面的代码加载一个200200的图片,将其缩小为5050的图片,可以看到结果:
在这里插入图片描述
大小减小了:1440000/90000=16,缩小为1/16

如果calculateInSampleSize计算的采样率不满足,大家可以直接写自己想要的采样率

package com.example.customizeview;

import androidx.appcompat.app.AppCompatActivity;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;

public class TestBitmapSize extends AppCompatActivity {
    private static final String TAG = "TestBitmapSize";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test_200_200);
        Log.i(TAG, "onCreate: originBitmap " + bitmap.getByteCount());
        Bitmap sampledBitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.test_200_200, 50, 50);
        Log.i(TAG, "onCreate: sampledBitmap " + sampledBitmap.getByteCount());
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        // 检查bitmap的大小
        final BitmapFactory.Options options = new BitmapFactory.Options();
        // 设置为true,BitmapFactory会解析图片的原始宽高信息,并不会加载图片
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(res, resId, options);

        // 计算采样率
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // 设置为false,加载bitmap
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeResource(res, resId, options);
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int width = options.outWidth;
        int height = options.outHeight;
        Log.i(TAG, "calculateInSampleSize: out width and height is " + width + " height " + height);
        int inSampleWidth = 1;
        if (height > reqHeight || width > reqWidth) {
            int halfHeight = height / 2;
            int halfWidth = width / 2;

            // 采样率设置为2的指数
            while ((halfHeight / inSampleWidth) >= reqHeight && (halfWidth / inSampleWidth) >= reqWidth) {
                inSampleWidth *= 2;
            }
        }
        return inSampleWidth;
    }
}

代码:https://gitee.com/lxd15130140362/customize_view/blob/master/app/src/main/java/com/example/customizeview/TestBitmapSize.java

使用inBitmap重用Bitmap

使用场景
当需要多次重复的创建Bitmap的时候,可以考虑使用InBitmap实现Bitmap的重用

使用方式
使用BitmapFactory中的Options中的 inBitmap 参数
在这里插入图片描述
查看上面的注解,当前使用的时候需要Bitmap是可变的mutable,所以我们在设置options参数的时候我们同样需要将
inMutable参数设置为true,只有这样再为inBitmap设置重用的Bitmap的时候才能复用,但是文档中也说了,复用是可能失败的,所以大家可以通过打印两次的对象地址查看是否复用成功,见下方代码。
代码

        // 测试bitmap复用
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inMutable = true;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bigbackground, options);
        // 对象内存地址;
        Log.i(TAG, "bitmap = " + bitmap);
        Log.i(TAG, "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());

        options.inBitmap = bitmap;

        // 返回的bitmap还是可变的,这个属性可以不设置
//        options.inMutable = true;
        Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.drawable.smallbackground, options);
        Log.i(TAG, "onCreate: isMutable" + bitmapReuse.isMutable());
// 复用对象的内存地址;
        Log.i(TAG, "bitmapReuse = " + bitmapReuse);
        Log.i(TAG, "bitmapReuse:ByteCount = " + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount = " + bitmapReuse.getAllocationByteCount());

查看日志输出:
在这里插入图片描述
两个Bitmap对象的内存地址一样,可以判断对象进行了复用。

inScaled,inDensity和inTargetDensity

使用场景

  • inScaled的默认值为true,允许缩放,即当屏幕分辨率和资源文件夹分辨率不匹配时允许缩放,设置为false则不会进行缩放,这样加载出来的图片的大小和原始图片大小相同。
  • inDensity用来设置资源文件夹的像素密度。
  • inTargetDensity用来设置显示时的像素密度。

使用方式
查看两个参数的注释:
在这里插入图片描述
在这里插入图片描述
当inDensity和inTargetDensity不匹配的时候会进行缩放,在允许缩放inScaled=true时,一张图片的缩放比例scale=inTargetDensity/inDensity
代码验证

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inDensity = 1;
        options.inTargetDensity = 2;
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kite, options);
        Log.i(TAG, "onCreate: bitmap.getWidth():" + bitmap.getWidth() + " bitmap.getHeight() " + bitmap.getHeight());

查看日志图片宽高变为原来的两倍:
MainActivity_main: onCreate: bitmap.getWidth():5000 bitmap.getHeight() 5000

inPreferredConfig参数的使用

使用场景
这个参数主要用来设置Bitmap的存储格式,不同的存储格式占用的内存大小和图像的质量是不同的,默认情况下,图像使用ARGB_8888。
使用方式
可以给该参数设置Bitmap.Config支持的格式:
在这里插入图片描述
Bitmap.Config支持的格式如上,在需要的时候,根据自己需要显示图像,我们可以调节相应的图片质量和内存占用。
但是在使用该参数的时候需要注意,inPreferredConfig无法对png图片生效,不管设置什么格式,png占用的内存大小不会减少

inPurgeable和inInputShareable

inPurgeable

Deprecated: As of android.os.Build.VERSION_CODES#LOLLIPOP, this is ignored. In android.os.Build.VERSION_CODES#KITKAT and below, if this is set to true, then the resulting bitmap will allocate its pixels such that they can be purged if the system needs to reclaim memory. In that instance, when the pixels need to be accessed again (e.g. the bitmap is drawn, getPixels() is called), they will be automatically re-decoded.

设置为true的话,当内存不足的时候bitmap会被回收,当再次需要Bitmap的时候,资源会被自动重新加载。

inInputShareable

Deprecated: As of android.os.Build.VERSION_CODES#LOLLIPOP, this is ignored. In android.os.Build.VERSION_CODES#KITKAT and below, this field works in conjuction with inPurgeable. If inPurgeable is false, then this field is ignored. If inPurgeable is true, then this field determines whether the bitmap can share a reference to the input data (inputstream, array, etc.) or if it must make a deep copy.

当inPurgeable设置为false的时候,这个参数无效,当设置为true的时候,表示可以共享引用比如(inputstream,array等)或者需要执行深拷贝。

Bitmap内存在哪里分配的

参考:
https://blog.csdn.net/longlong2015/article/details/80726985

Bitmap的存储在N和O版本上是不同的:
创建Bitmap的时候,需要使用BitmapFactory提供的decodeXXX()接口创建Bitmap。
内存分配:
Android N和Android O分配区别
Android N上的Bitmap像素点数据都是分配到Dalvik heap区,而Android O上的Bitmap像素点数据是分配在Native heap中的。
内存回收
Android N上Bitmap的内存主要在java heap中,内存的回收基本靠GC机制,而Android O内存主要在native heap,但是不管是N还是O,Bitmap的内存在java heap和native heap上都会有部分内存,native内存的回收可以调用recycle方法,同时也可以当native heap中的对象没有引用的时候,会自动回收内存。
android为什么将内存的分配移动到native heap呢

  1. 可以分配的内存空间更大,java 虚拟机的内存有一个上限,且这个上限不会太高,因为android要同时运行多个app,分配在java heap会增加oom的风险,图片被移动到native heap之后,可以使用系统更多的内存,同时降低了虚拟机的内存,减少了虚拟机oom的风险。
    一般虚拟机的可用内存是几百M,而放在native层可以使用的内存达到G级别。至于为什么之前将native heap存储调整到java heap可能是由于native内存释放不及时,andorid8GC算法的优化,使得内存可以快速被回收,而后重新使用。

参考:
https://www.cnblogs.com/mingfeng002/p/11400433.html
https://www.jianshu.com/p/3f6f6e4f1c88

Bitmap内存管理

此处进行记录,可以参考官方提供的demo对内存进行管理复用
https://developer.android.google.cn/topic/performance/graphics/manage-memory.html#inBitmap

Java有垃圾回收,为什么要调用recycle方法

Java的内存回收针对Java虚拟机管理的对象,Bitmap在c层分配的数据需要主动调用去释放。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值