17、Android性能优化之图片优化:Bitmap高效加载、图片压缩、Glide与Coil图片加载库的使用

图片优化,说白了就是Android开发里绕不开的一道坎。我刚开始做项目时,总觉得图片嘛,加载出来就行了。结果有一次,一个列表页加载了十几张高清图,直接OOM,应用闪退。用户反馈炸了锅。嗯,从那以后,我再也不敢小看图片优化了。

今天我们就来聊聊Bitmap的高效加载、图片压缩,以及两个主流图片库——Glide和Coil的使用。你想想看,一张1080p的图片解码成Bitmap,内存占用可能高达十几MB。如果列表里有几十张,内存瞬间爆炸。所以,图片优化的核心就两个:怎么加载怎么压缩

核心原则:图片加载时,永远不要直接加载原图。要根据目标控件的尺寸,进行采样压缩。

17.1 Bitmap的高效加载

Bitmap的高效加载,核心就是采样压缩。Android提供了BitmapFactory.Options,我们可以通过设置inSampleSize来对图片进行降采样。

举个例子。假如ImageView的宽高是200x200,但图片是1920x1080。直接加载,内存占用巨大。我建议的做法是:先获取图片的原始尺寸,然后计算出合适的采样率。

// 第一步:只获取图片的宽高,不加载到内存
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.my_image, options);

int imageWidth = options.outWidth;
int imageHeight = options.outHeight;

// 第二步:计算采样率
int targetWidth = 200;
int targetHeight = 200;
int sampleSize = calculateInSampleSize(imageWidth, imageHeight, targetWidth, targetHeight);

// 第三步:设置采样率,真正加载
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_image, options);
imageView.setImageBitmap(bitmap);
// 计算采样率的工具方法
public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) {
    int inSampleSize = 1;
    if (height > reqHeight || width > reqWidth) {
        int halfHeight = height / 2;
        int halfWidth = width / 2;
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

个人经验:inSampleSize必须是2的幂次方,否则系统会向下取整。我曾经踩过这个坑,设置了个3,结果图片加载出来还是很大。后来查文档才发现这个细节。

17.2 图片压缩策略

图片压缩分两种:质量压缩尺寸压缩。质量压缩不改变图片的像素尺寸,只改变存储大小。尺寸压缩则是直接缩小图片的宽高。

在实际项目中,我通常这样用:

  • 质量压缩:用于上传图片前,减少网络传输量。比如用户拍照后,先压缩到200KB以内再上传。
  • 尺寸压缩:用于列表展示,配合采样加载,保证内存不爆炸。
// 质量压缩示例
public static byte[] compressQuality(Bitmap bitmap, int maxSize) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int quality = 100;
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
    while (baos.toByteArray().length > maxSize && quality > 0) {
        baos.reset();
        quality -= 10;
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
    }
    return baos.toByteArray();
}

注意:质量压缩对PNG格式效果很差,因为PNG是无损压缩。我建议对PNG先转成JPEG再压缩,或者直接用WebP格式。WebP在同等质量下,体积比JPEG小30%左右。

17.3 Glide图片加载库

Glide是我个人用得最多的图片加载库。它由Google官方维护,性能稳定,功能强大。说白了,Glide帮我们做了上面所有的事情——采样、缓存、内存管理,我们只需要一行代码。

// Glide基本用法
Glide.with(context)
     .load("https://example.com/image.jpg")
     .placeholder(R.drawable.placeholder)
     .error(R.drawable.error)
     .override(200, 200)  // 指定目标尺寸
     .centerCrop()
     .into(imageView);

Glide的几个核心特性:

  • 自动采样:根据ImageView的尺寸自动计算采样率。
  • 三级缓存:内存、磁盘、网络,层层缓存。
  • 生命周期绑定:Activity或Fragment销毁时,自动取消加载任务。

避坑指南:我曾经在RecyclerView里用Glide加载大量图片,发现滑动时图片闪烁。后来发现是没设置.dontAnimate()。Glide默认有淡入动画,频繁刷新会导致闪烁。列表场景建议禁用动画。

17.4 Coil图片加载库

Coil是Kotlin协程时代的图片库,全称是Coroutine Image Loader。如果你项目已经全面Kotlin化,我强烈推荐Coil。它轻量、快速,而且完全基于协程。

// Coil基本用法
imageView.load("https://example.com/image.jpg") {
    placeholder(R.drawable.placeholder)
    error(R.drawable.error)
    size(200, 200)
    crossfade(true)
}

Coil的优势很明显:

  • 协程原生:加载任务可以轻松取消,配合协程作用域。
  • 体积小:只有Glide的1/3大小。
  • Kotlin友好:DSL风格的API,写起来很舒服。

我的建议:如果团队以Java为主,用Glide。如果全面Kotlin化,用Coil。两者性能差距不大,但Coil的代码更简洁,维护成本更低。

17.5 Glide vs Coil 对比

特性 Glide Coil
语言 Java/Kotlin Kotlin
核心机制 基于线程池 基于协程
APK体积 约500KB 约150KB
缓存策略 三级缓存 二级缓存(内存+磁盘)
GIF支持 原生支持 需要额外配置
学习成本 中等 低(DSL风格)

17.6 知识体系总览

下面这张图,是我整理的图片优化知识体系。你可以把它当作一个检查清单,做项目时对照着看,基本不会漏掉什么。

图片优化 加载策略 采样压缩 (inSampleSize) inJustDecodeBounds 按需加载(分页/懒加载) 压缩方式 质量压缩 (Bitmap.compress) 尺寸压缩 (Matrix缩放) WebP格式转换 图片加载库 Glide(Google官方) Coil(协程原生) Fresco(Facebook) 缓存策略 内存缓存 (LruCache) 内存管理 Bitmap复用 (inBitmap)

17.7 总结

图片优化这件事,说难不难,说简单也不简单。核心就三点:

  1. 不要加载原图——用采样压缩控制内存。
  2. 选择合适的压缩方式——质量压缩省流量,尺寸压缩省内存。
  3. 用好图片加载库——Glide或Coil,选一个深入掌握。

我个人习惯是:列表页用Coil,配合尺寸压缩;详情页用Glide,配合质量压缩。两者结合,基本能覆盖大部分场景。你想想看,如果每个图片都省下几MB内存,整个应用的流畅度会提升多少?

好了,这一章就到这里。记住,图片优化不是一蹴而就的,需要在项目中不断调优。下次遇到OOM,先检查图片加载那块,八成能找到问题。


公众号:蓝海资料掘金营,微信deep3321