Android优化之大图优化

84 阅读5分钟

一、BitmapFactory 设置优化

1.inJustDecodeBounds

BitmapFactory ,它提供了4类方法,分别是:

decodeResource、 从资源加载出Bitmap对象

decodeStream、从字节数组加载出Bitmap对象

decodeFile 从文件加载出Bitmap对象

decodeByteArray 从字节数组加载出Bitmap对象

这些方法都会为Bitmap分配内存,那就有可能发生OOM,想象一下一张分辨率超高的图片加载进内存 了!那有什么办法可以避免呢。这时候BitmapFactory.Options就要上场了,将它的属性

inJustDecodeBounds设置为true就可以让解析方法不给Bitmap分配内存,也就能防止OOM,返回值也

不是实际的bitmap,而是null,但是我们还是可以查询图片的相关信息比如宽、高。

bmOptions.inJustDecodeBounds = true;

这个参数设置为true 就不会

2.inSampleSize

BitmapFactory 提供了 inSampleSize 参数,可以通过它来控制加载的图像的分辨率,从而减少内存消耗。通过计算适当的 inSampleSize 值,你可以在加载图像时缩小图像的尺寸。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;  // 只解码边界信息,不加载实际的图像
BitmapFactory.decodeFile(filePath, options);

// 计算 inSampleSize
int scaleFactor = 1;
if (options.outHeight > targetHeight || options.outWidth > targetWidth) {
    final int halfHeight = options.outHeight / 2;
    final int halfWidth = options.outWidth / 2;
    while ((halfHeight / scaleFactor) > targetHeight && (halfWidth / scaleFactor) > targetWidth) {
        scaleFactor *= 2;
    }
}
options.inSampleSize = scaleFactor;
options.inJustDecodeBounds = false;  // 重新加载图像

Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

3.inBitmap

BitmapFactory.Options.inBitmap 是一个用于复用内存的关键字段,它允许你指定一个已经存在的 Bitmap 对象,BitmapFactory 会尝试将其作为新的解码目标,而不是为每张图片重新分配内存。

// 1. 创建一个可复用的 Bitmap 对象
Bitmap reusableBitmap = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_8888);

// 2. 设置 BitmapFactory.Options.inBitmap 为可复用的 Bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inBitmap = reusableBitmap;  // 设置复用内存
options.inJustDecodeBounds = true;  // 只解码边界信息,不加载图像

// 3. 读取图片的边界信息
BitmapFactory.decodeFile(filePath, options);

// 4. 根据图片的尺寸计算合适的 inSampleSize
options.inJustDecodeBounds = false;  // 重新设置为解码实际的图像
options.inSampleSize = 2;  // 通过 inSampleSize 控制采样率,减小内存使用

// 5. 解码图片并复用内存
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

// 6. 使用该 bitmap 进行显示或其他操作
imageView.setImageBitmap(bitmap);

二、区域解码

假设我们有一张非常大的照片,例如它的分辨率是4000 * 3000。

那么,在常规的的手机屏幕(1080 * 1920)上,如果不做压缩处理,我们的图片很明显是显示不开 的。因此,我们想到了这样的方案:让图片支持滑动,滑动到哪里,加载哪一部分。如下图片,我们要 在手机上按照1:1的比例高清展示出来,那么1080*1920的区域,大概只能让他显示黑框中的区域。那其 他的区域就需要我们滑动去加载。滑动到哪里,就展示哪一块区域。

// 1. 获取输入流(比如从文件或资源)
FileInputStream fileInputStream = new FileInputStream(imagePath);

// 2. 创建 BitmapRegionDecoder
BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(fileInputStream, false);

// 3. 定义要解码的区域(指定一个矩形区域)
Rect rect = new Rect(0, 0, 500, 500);  // 这里以 (0, 0) 到 (500, 500) 的区域为例

// 4. 解码指定区域的图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;  // 通过 inSampleSize 控制采样率,减小内存使用
Bitmap bitmap = regionDecoder.decodeRegion(rect, options);

// 5. 使用该 bitmap,例如显示在 ImageView 中
imageView.setImageBitmap(bitmap);

内存复用的好处

  • Bitmap 大小限制: 复用内存的 Bitmap 对象必须与解码后的 Bitmap 在内存尺寸上兼容。即,如果目标图像比复用的 Bitmap 大,则会抛出 IllegalArgumentException。因此,通常需要根据图片的实际尺寸计算合适的内存复用对象。
  • 内存复用的条件: 使用 inBitmap 时,如果提供的复用 Bitmap 的尺寸不适应新的图片,系统会抛出 IllegalArgumentException。这就意味着你需要确保复用的 Bitmap 对象的大小足够大,或者计算适当的图像解码比例。
  • 减少垃圾回收压力: 在使用 inBitmap 进行内存复用时,由于不需要重新分配内存,会减少频繁的垃圾回收,从而提高性能。
  • 释放复用的 Bitmap: 使用完复用的 Bitmap 后,仍然需要手动回收它。调用 recycle() 方法可以释放 Bitmap 占用的内存。否则,Bitmap 会继续占用内存,直到系统回收。
  • 必须使用相同的配置: 复用内存的 Bitmap 必须具有相同的颜色配置和像素格式,否则可能导致图像解码失败。例如,你不能将 Bitmap.Config.ARGB_8888Bitmap 用于 Bitmap.Config.RGB_565 类型的图像

三、使用硬件加速

如果设备支持硬件加速,可以启用硬件加速来提高图片渲染性能。硬件加速可以让图像绘制过程更加高效,减少 CPU 的负担。

<application

android:hardwareAccelerated="true">

四、Bitmap 回收处理

如果你不再需要某个 Bitmap 对象,可以调用 recycle() 方法来手动回收它占用的内存。

if (bitmap != null && !bitmap.isRecycled()) {
    bitmap.recycle();
    bitmap = null;
}

如果不适用recycle() 那么bitmap的释放不一定及时,要等 垃圾回收才会被清理掉,使用recycle 会释放掉 bitmap的内存

五、Glide Picasso 优化

1.适时的裁剪图片

Glide.with(context)
    .load(imageUrl)
    .override(targetWidth, targetHeight)  // 设置目标尺寸
    .centerCrop()  // 裁剪图片以适应目标尺寸
    .into(imageView);

Picasso.get()
    .load(imageUrl)
    .resize(targetWidth, targetHeight)  // 设置目标尺寸
    .centerCrop()  // 裁剪图片以适应目标尺寸
    .into(imageView);

2.使用内存缓存

Glide.with(context)
    .load(imageUrl)
    .diskCacheStrategy(DiskCacheStrategy.ALL)  // 缓存所有图像版本(原始、压缩图像等)
    .skipMemoryCache(false)  // 启用内存缓存
    .into(imageView);

Picasso.get()
    .load(imageUrl)
    .memoryPolicy(MemoryPolicy.NO_CACHE)  // 禁用内存缓存(如果需要)
    .networkPolicy(NetworkPolicy.NO_CACHE)  // 禁用网络缓存
    .into(imageView);

六、图片压缩处理

1.compress 压缩

可以使用 Bitmap.compress() 方法,将图片压缩成指定质量的 JPEG 格式,从而减少内存消耗。

public void compressBitmap(Bitmap bitmap, File outputFile, int quality) throws IOException {
    FileOutputStream fos = new FileOutputStream(outputFile);
    // 有损压缩,质量范围是 0(最差)到 100(最佳)
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos);
    fos.close();
}
  • 压缩质量quality 参数控制压缩后的质量,0 表示最差质量,100 表示最优质量。
  • 常见格式Bitmap.CompressFormat.JPEG(有损压缩)和 Bitmap.CompressFormat.PNG(无损压缩)。
  • 文件大小与质量的平衡:一般来说,压缩质量设置为 80-90 左右可以获得比较好的平衡,既能够减少文件大小,又能保证图像质量。

2.luban压缩

项目地址:github.com/Curzibn/Lub…

目前比较好用的压缩第三方