【Android 内存优化】Bitmap 图像尺寸缩小 ( 设置 Options 参数 | inJustDecodeBounds | inSampleSize | 工具类实现 )

简介: 【Android 内存优化】Bitmap 图像尺寸缩小 ( 设置 Options 参数 | inJustDecodeBounds | inSampleSize | 工具类实现 )

文章目录

一、解码图片参数 inJustDecodeBounds

二、计算图片的缩小比例

三、设置图片缩小配置 inSampleSize

四、设置图片像素格式 inPreferredConfig

五、设置图片复用机制

六、Bitmap 图像尺寸缩小代码示例

1、图片缩小工具类

2、Activity 调用工具类代码

3、执行结果





一、解码图片参数 inJustDecodeBounds


1 . 解码图片参数 :



① 设置获取参数解码选项 : 设置解码时的 BitmapFactory.Options 对象的 inJustDecodeBounds 为 true ,


② 解码图像 : 解析器返回的 Bitmap 对象为 null ;


③ 解码选项 : BitmapFactory.Options 中的 outXxx 字段会被设置对应的图片属性值 ;


④ 解码选项参数示例 : 如 : outWidth 输出图像的 宽度 , outHeight 输出高度 , outMimeType 输出类型 , outConfig 像素格式 , outColorSpace 输出颜色空间 ;



2 . 代码示例 :


// Bitmap 图片加载选项

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, iamgeResId, options);






二、计算图片的缩小比例


计算图片的缩小比例 :



① 目标图片宽高要求 : 宽度和高度只要存在一个大于限定的最大值时 , 就进行缩小操作 ; 要求指定的图片必须能放到 maxBitmapWidth 宽度 , maxBitmapHeight 高度的矩形框中 ; 最终要求就是 宽度必须小于 maxBitmapWidth, 同时高度也要小于 maxBitmapHeight ;


② 缩小倍数要求 : 缩小倍数只能是 2 的幂次方值 , 1 , 2 , 4 , 8 , 16 , 32 , 64 ;


     

/*
            计算缩小的比例
            宽度和高度只要存在一个大于限定的最大值时 , 就进行缩小操作
            要求指定的图片必须能放到 maxBitmapWidth 宽度 , maxBitmapHeight 高度的矩形框中
            最终要求就是 宽度必须小于 maxBitmapWidth, 同时高度也要小于 maxBitmapHeight
         */
        if(imageWidth > maxBitmapWidth || imageHeight > maxBitmapHeight){
            // 如果需要启动缩小功能 , 那么进入如下循环 , 试探最小的缩放比例是多少
            while ( imageWidth / inSampleSize > maxBitmapWidth ||
                    imageHeight / inSampleSize > maxBitmapHeight ){
                // 注意该值必须是 2 的幂次方值 , 1 , 2 , 4 , 8 , 16 , 32 , 64
                inSampleSize = inSampleSize * 2;
            }
            // 执行到此处 , 说明已经找到了最小的缩放比例 , 打印下最小比例
            Log.w(TAG, "getResizedBitmap inSampleSize=" + inSampleSize);
        }






三、设置图片缩小配置 inSampleSize


1 . 图片缩小配置 inSampleSize :



① inSampleSize 设置大于 1 : 如果值大于 1 , 那么就会缩小图片 ;


② 解码器操作 : 此时解码器对原始的图片数据进行子采样 , 返回较小的 Bitmap 对象 ;


③ 样本个数 : 样本的大小是在两个维度计算的像素个数 , 每个像素对应一个解码后的图片中的单独的像素点 ;


④ 样本个数计算示例 : 如果 inSampleSize 值为 2 , 那么宽度的像素个数会缩小 2 倍 , 高度也会缩小两倍 ; 整体像素个数缩小 4 倍 , 内存也缩小了 4 倍 ;




2 . inSampleSize 取值要求 :



① 小于 1 取值 : 如果取值小于 1 , 那么就会被当做 1 , 1 相当于 2 的 0 次方 ;


② 取值要求 : 该值必须是 2 的幂次方值 , 2 的次方值 , 如 1 , 2 , 4 , 8 , 16 , 32


③ 不合法值 : 如果出现了不合法的值 , 就会就近四舍五入到最近的 2 的幂次方值




3 . 代码示例 :


BitmapFactory.Options options = new BitmapFactory.Options();
// ... 
options.inSampleSize = inSampleSize;





四、设置图片像素格式 inPreferredConfig


1 . 解码像素格式 :



① 指定配置解码 : 如果配置为非空 , 解码器会将 Bitmap 的像素解码成该指定的非空像素格式 ;


② 自动匹配配置解码 : 如果该配置为空 , 或者像素配置无法满足 , 解码器会尝试根据系统的屏幕深度 , 源图像的特点 , 选择合适的像素格式 ; 如果源图像有透明度通道 , 那么自动匹配的默认配置也有对应通道 ;


③ 默认配置 : 默认使用 ARGB_8888 进行解码



2 . 代码示例 :


options.inPreferredConfig = Bitmap.Config.RGB_565;






五、设置图片复用机制


1 . 图片复用机制 :



① 图片复用 : 如果设置了一个 Bitmap 对象给 inBitmap 参数 , 解码方法会获取该 Bitmap 对象 , 当加载图片内容时 , 会尝试复用该 Bitmap 对象的内存


② 无法复用抛出异常 : 如果解码方法无法复用该 Bitmap 对象 , 解码方法可能会抛出 IllegalArgumentException 异常 ;


③ 图片可变性 : 当前的实现是很有必要的 , 被复用的图片必须是可变的 , 解码后的 Bitmap 对象也是可变的 , 即使当解码一个资源图片时 , 经常会得到一个不可变的 Bitmap 对象 ;




2 . 解码结果判定 :



① 解码可能失败 : 该解码方法返回的 Bitmap 对象是可以使用的 , 鉴于上述约束情况 和 可能发生的失败故障 , 不能假定该图片解码操作是成功的 ;


② 检查复用是否成功 : 解码检查解码返回的 Bitmap 对象是否与设置给 Options 对象的 inBitmap 相匹配 , 来判断该 inBitmap 是否被复用 ;


③ 后续操作 : 不管有没有复用成功 , 你应该使用解码函数返回的 Bitmap 对象 , 保证程序的正常运行 ;




3 . 与 BitmapFactory 配合使用 :



① Android 4.4 以后的复用机制 : 在 KITKAT 以后的代码中 , 只要被解码生成的 Bitmap 对象的字节大小 ( 缩放后的 ) , 小于等于 inBitmap 的字节大小 , 就可以复用成功 ;


② Android 4.4 之前的复用机制 : 在 KITKAT ( Android 4.4 系统 , android-19 平台 ) 之前的代码中 , 被解码的图像必须是


JPEG 或 PNG 格式 ,

并且 图像大小必须是相等的 ,

inssampleSize 设置为 1 ,

才能复用成功 , 另外被复用的图像的 像素格式 Config ( 如 RGB_565 ) 会覆盖设置的 inPreferredConfig 参数




4 . 代码示例 :


options.inBitmap = inBitmap;






六、Bitmap 图像尺寸缩小代码示例




1、图片缩小工具类


图片缩小工具类 :


package kim.hsl.bm.utils;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
/**
 * Bitmap 尺寸缩小
 */
public class BitmapSizeReduce {
    private static final String TAG = "BitmapSizeReduce";
    /**
     * 获取指定尺寸于鏊求的 Bitmap 对象
     * 该方法有缺陷 , 计算值的时候没有考虑像素密度
     * 如果从不同像素密度的资源文件中加载
     * 可能计算出的值与指定的 maxBitmapWidth maxBitmapHeight 略有出入
     *
     * @param context           上下文对象
     * @param iamgeResId        要解析的图片资源 id
     * @param maxBitmapWidth    Bitmap 的最大宽度
     * @param maxBitmapHeight   Bitmap 的最大高度
     * @param hasAlphaChannel   是否包含 ALPHA 通道, 即透明度信息
     * @param inBitmap          复用的 Bitmap, 将新的 Bitmap 对象解析到该 Bitmap 内存中
     * @return  返回新的 Bitmap 对象
     */
    public static Bitmap getResizedBitmap(Context context,
                                          int iamgeResId, int maxBitmapWidth, int maxBitmapHeight,
                                          boolean hasAlphaChannel, Bitmap inBitmap){
        // 0. 声明方法中使用的局部变量
        // 用于解析资源
        Resources resources = context.getResources();
        // Bitmap 图片加载选项
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 图片宽度
        int imageWidth;
        // 图片高度
        int imageHeight;
        /*
            根据 图片宽度 imageWidth , 图片高度 imageHeight ,
            最大宽度 maxBitmapWidth , 最大高度 maxBitmapHeight ,
            计算出的图片缩放系数 , 该值最终要设置到 BitmapFactory.Options 对象中
         */
        int inSampleSize = 1;
        // 1. 解析图片参数 : 该阶段不解析所有的数据 , 否则会将实际的图片数据解析到内存中 , 这里只解析图片的宽高信息
        /*
            设置 inJustDecodeBounds 为 true , 解析器会返回 null
            但是 outXxx 字段会被设置对应的图片属性值 ,
            如 : outWidth 输出图像的 宽度 , outHeight 输出高度 , outMimeType 输出类型 ,
            outConfig 像素格式 , outColorSpace 输出颜色空间
         */
        options.inJustDecodeBounds = true;
        /*
            由于设置了 inJustDecodeBounds = true , 该方法返回值为空 ;
            但是传入的 BitmapFactory.Options 对象中的 outXxx 字段都会被赋值 ;
            如 outWidth , outHeight , outConfig , outColorSpace 等 ;
            可以获取该图片的宽高 , 像素格式 , 颜色空间等信息
         */
        BitmapFactory.decodeResource(resources, iamgeResId, options);
        // 获取 iamgeResId 图片资源对应的图片宽度
        imageWidth = options.outWidth;
        // 获取 iamgeResId 图片资源对应的图片高度
        imageHeight = options.outHeight;
        // 2. 计算图片缩小比例
        /*
            计算缩小的比例
            宽度和高度只要存在一个大于限定的最大值时 , 就进行缩小操作
            要求指定的图片必须能放到 maxBitmapWidth 宽度 , maxBitmapHeight 高度的矩形框中
            最终要求就是 宽度必须小于 maxBitmapWidth, 同时高度也要小于 maxBitmapHeight
         */
        if(imageWidth > maxBitmapWidth || imageHeight > maxBitmapHeight){
            // 如果需要启动缩小功能 , 那么进入如下循环 , 试探最小的缩放比例是多少
            while ( imageWidth / inSampleSize > maxBitmapWidth ||
                    imageHeight / inSampleSize > maxBitmapHeight ){
                // 注意该值必须是 2 的幂次方值 , 1 , 2 , 4 , 8 , 16 , 32 , 64
                inSampleSize = inSampleSize * 2;
            }
            // 执行到此处 , 说明已经找到了最小的缩放比例 , 打印下最小比例
            Log.w(TAG, "getResizedBitmap inSampleSize=" + inSampleSize);
        }
        // 3. 设置图像解码参数
        /*
            inSampleSize 设置大于 1 : 如果值大于 1 , 那么就会缩小图片 ;
            解码器操作 : 此时解码器对原始的图片数据进行子采样 , 返回较小的 Bitmap 对象 ;
            样本个数 : 样本的大小是在两个维度计算的像素个数 , 每个像素对应一个解码后的图片中的单独的像素点 ;
            样本个数计算示例 :
            如果 inSampleSize 值为 2 , 那么宽度的像素个数会缩小 2 倍 , 高度也会缩小两倍 ;
            整体像素个数缩小 4 倍 , 内存也缩小了 4 倍 ;
            小于 1 取值 : 如果取值小于 1 , 那么就会被当做 1 , 1 相当于 2 的 0 次方 ;
            取值要求 : 该值必须是 2 的幂次方值 , 2 的次方值 , 如 1 , 2 , 4 , 8 , 16 , 32
            如果出现了不合法的值 , 就会就近四舍五入到最近的 2 的幂次方值
         */
        options.inSampleSize = inSampleSize;
        // 用户设置的是否保留透明度选项 , 如果不保留透明度选项 , 设置像素格式为 RGB_565
        // 每个像素占 2 字节内存
        if (!hasAlphaChannel){
            /*
                指定配置解码 : 如果配置为非空 , 解码器会将 Bitmap 的像素解码成该指定的非空像素格式 ;
                自动匹配配置解码 : 如果该配置为空 , 或者像素配置无法满足 , 解码器会尝试根据系统的屏幕深度 ,
                源图像的特点 , 选择合适的像素格式 ;
                如果源图像有透明度通道 , 那么自动匹配的默认配置也有对应通道 ;
                默认配置 : 默认使用 ARGB_8888 进行解码
             */
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        /*
            注意解码真实图像的时候 , 要将 inJustDecodeBounds 设置为 false
            否则将不会解码 Bitmap 数据 , 只会将
            outWidth , outHeight , outConfig , outColorSpace 等 outXxx 图片参数解码出来
         */
        options.inJustDecodeBounds = false;
        /*
            设置图片可以被复用
         */
        options.inMutable = true;
        /*
            如果设置了一个 Bitmap 对象给 inBitmap 参数
            解码方法会获取该 Bitmap 对象 , 当加载图片内容时 , 会尝试复用该 Bitmap 对象的内存
            如果解码方法无法复用该 Bitmap 对象 , 解码方法可能会抛出 IllegalArgumentException 异常 ;
            当前的实现是很有必要的 , 被复用的图片必须是可变的 , 解码后的 Bitmap 对象也是可变的 ,
            即使当解码一个资源图片时 , 经常会得到一个不可变的 Bitmap 对象 ;
            确保是否解码成功 :
            该解码方法返回的 Bitmap 对象是可以使用的 ,
            鉴于上述约束情况 和 可能发生的失败故障 , 不能假定该图片解码操作是成功的 ;
            检查解码返回的 Bitmap 对象是否与设置给 Options 对象的 inBitmap 相匹配 ,
            来判断该 inBitmap 是否被复用 ;
            不管有没有复用成功 , 你应该使用解码函数返回的 Bitmap 对象 , 保证程序的正常运行 ;
            与 BitmapFactory 配合使用 :
            在 KITKAT 以后的代码中 , 只要被解码生成的 Bitmap 对象的字节大小 ( 缩放后的 )
            小于等于 inBitmap 的字节大小 , 就可以复用成功 ;
            在 KITKAT 之前的代码中 , 被解码的图像必须是
            JPEG 或 PNG 格式 ,
            并且 图像大小必须是相等的 ,
            inssampleSize 设置为 1 ,
            才能复用成功 ;
            另外被复用的图像的 像素格式 Config ( 如 RGB_565 ) 会覆盖设置的 inPreferredConfig 参数
         */
        options.inBitmap = inBitmap;
        // 4. 解码图片 , 并返回被解码的图片
        return BitmapFactory.decodeResource(resources, iamgeResId, options);
    }
}



2、Activity 调用工具类代码


Activity 代码 :


package kim.hsl.bm;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import kim.hsl.bm.utils.BitmapSizeReduce;
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
        // 缩小图像尺寸
        sizeReduce();
    }
    /**
     * 图像尺寸缩小
     */
    private void sizeReduce(){
        // 从资源文件中加载内存
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blog);
        // 打印 Bitmap 对象的宽高, 字节大小
        Log.i("Bitmap", "blog : " + bitmap.getWidth() + " , " +
                bitmap.getHeight() + " , " +
                bitmap.getByteCount());
        // 从资源文件中加载内存
        Bitmap reduceSizeBitmap = BitmapSizeReduce.getResizedBitmap(this, R.drawable.blog,
                100, 100 , false , null);
        // 打印 Bitmap 对象的宽高, 字节大小
        Log.i("Bitmap", "reduceSizeBitmap : " + reduceSizeBitmap.getWidth() + " , " +
                reduceSizeBitmap.getHeight() + " , " +
                reduceSizeBitmap.getByteCount());
    }
    public native String stringFromJNI();
}





3、执行结果


执行结果 :


2020-06-30 22:04:22.959 3766-3766/? I/Bitmap: blog : 5224 , 2678 , 55959488
2020-06-30 22:04:22.960 3766-3766/? W/BitmapSizeReduce: getResizedBitmap inSampleSize=32
2020-06-30 22:04:22.980 3766-3766/? I/Bitmap: reduceSizeBitmap : 163 , 81 , 26406



分析结果 :


① 源图像分析 : 从资源中加载 , 普通情况下宽度 5224 像素 , 高度 2678 像素 , ARGB_8888 像素格式 , 每个像素 4 44 字节 , 计算公式为


5224 × 2678 × 4 = 55 , 959 , 488 5224 \times 2678 \times 4 = 55,959,488

5224×2678×4=55,959,488



② 缩小后的图像分析 : 从资源中加载 , 普通情况下宽度 163 像素 , 高度 81 像素 , RGB_565 像素格式 , 每个像素 2 22 字节 , 计算公式为


163 × 81 × 2 = 26 , 406 ‬ 163 \times 81 \times 2 = 26,406‬

163×81×2=26,406‬


目录
相关文章
|
9月前
|
Android开发 开发者
Android设置View是否可用
在Android开发中,有时需要将布局设置为不可点击状态(失去焦点)。常见的解决方法是使用`setOnClickListener(null)`,但本文介绍一种更通用的方式:通过封装`setViewEnabled`方法实现。该方法可递归设置View及其子View的启用状态,支持传入目标View和布尔值(`true`为可用,`false`为禁用)。例如,调用`setViewEnabled(edittext, false)`即可禁用EditText。文章附有源码及示例动图,帮助开发者快速理解与应用。
206 1
|
9月前
|
Android开发 开发者
Android中Dialog位置+样式的设置
本文介绍了在Android开发中如何设置Dialog的位置和样式。通过自定义`MyDialog`类,可以灵活调整Dialog的显示位置,例如将其固定在屏幕底部,并设置宽度匹配父布局。同时,文章还展示了如何模仿Android原生Dialog样式,通过定义`MyDialogStyle`去除标题栏、设置背景透明度、添加阴影效果以及配置点击外部关闭等功能,从而实现更加美观和符合需求的Dialog效果。代码示例详细,便于开发者快速上手实现。
473 2
|
11月前
|
SQL Oracle 关系型数据库
【YashanDB知识库】崖山有哪些内存参数,Share Pool各个参数之间有什么关系
【YashanDB知识库】崖山有哪些内存参数,Share Pool各个参数之间有什么关系
【YashanDB知识库】崖山有哪些内存参数,Share Pool各个参数之间有什么关系
|
12月前
|
SQL Oracle 关系型数据库
【YashanDB 知识库】崖山有哪些内存参数,Share Pool 各个参数之间有什么关系
在使用YashanDB时,用户常对内存参数配置有疑问,尤其是23.2及以上版本中,如SQL_POOL_SIZE+DICTIONARY_CACHE_SIZE超100报错,影响跑批性能。主要内存参数包括SHARE_POOL_SIZE、SQL_POOL_SIZE、DICTIONARY_CACHE_SIZE等,需合理配置以优化性能。SHARE POOL内含多个POOL,可动态调整。具体配置方法及观察使用情况的方式详见官网文档。
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
253 20
Android经典面试题之图片Bitmap怎么做优化
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
Java Shell Linux
从 am start 的 --user 参数说到 Android 多用户
am start 命令有时并不会乖乖如我们所愿,这时候我们需要知对策并知其所以然。
460 16
|
Android开发
Android经典实战之Textview文字设置不同颜色、下划线、加粗、超链接等效果
本文介绍了 `SpannableString` 在 Android 开发中的强大功能,包括如何在单个字符串中应用多种样式,如颜色、字体大小、风格等,并提供了详细代码示例,展示如何设置文本颜色、添加点击事件等,助你实现丰富文本效果。
1169 4
|
Python
Python变量的作用域_参数类型_传递过程内存分析
理解Python中的变量作用域、参数类型和参数传递过程,对于编写高效和健壮的代码至关重要。正确的应用这些概念,有助于避免程序中的错误和内存泄漏。通过实践和经验积累,可以更好地理解Python的内存模型,并编写出更优质的代码。
184 2
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
709 0

热门文章

最新文章