Android Picasso 转换模块深度剖析(四)

146 阅读11分钟

Android Picasso 转换模块深度剖析

本人掘金号,欢迎点击关注:掘金号地址

本人公众号,欢迎点击关注:公众号地址

一、引言

在 Android 开发中,图片加载库 Picasso 凭借其简洁易用的 API 和高效的性能备受开发者青睐。其中,转换模块作为 Picasso 的重要组成部分,承担着对图片进行各种处理和转换的核心功能。无论是对图片进行缩放、裁剪、旋转,还是调整色彩、添加滤镜等操作,都依赖于转换模块的实现。本文将深入 Android Picasso 转换模块的源码,详细解析其工作原理和实现细节,帮助开发者更好地理解和运用该模块。

二、转换模块概述

2.1 模块功能

Picasso 的转换模块主要实现以下功能:

  • 图片尺寸调整:根据目标视图的大小或开发者指定的尺寸,对图片进行缩放和裁剪,以适配不同的显示需求。
  • 图片变换操作:包括旋转、翻转等操作,满足多样化的图片展示效果。
  • 图片格式转换:在必要时对图片的格式进行转换,例如将位图转换为特定格式以优化存储或显示。
  • 图片效果处理:实现一些基本的图片效果处理,如调整色彩、添加滤镜等,增强图片的视觉效果。

2.2 主要类和接口

在转换模块中,涉及到多个关键类和接口:

  • Transformation:这是一个接口,定义了图片转换的抽象方法,开发者可以通过实现该接口来自定义图片转换逻辑。
  • ResizeTransformation:用于实现图片尺寸调整的转换类,继承自 Transformation 接口。
  • RotateTransformation:负责图片旋转操作的转换类,同样继承自 Transformation 接口。
  • CenterCropFit:这两个类也是实现了 Transformation 接口,分别用于实现图片的中心裁剪和适配显示效果。

三、Transformation 接口分析

3.1 接口定义

// Transformation 接口定义了图片转换的抽象方法
public interface Transformation {
    // 执行图片转换操作的方法,传入原始 Bitmap 返回转换后的 Bitmap
    Bitmap transform(Picasso picasso, Bitmap source, int width, int height);

    // 获取转换的唯一标识,用于缓存等场景判断转换是否相同
    String key();
}

Transformation 接口定义了两个方法,transform 方法是核心方法,用于执行具体的图片转换逻辑,它接收 Picasso 实例、原始 Bitmap 以及目标宽度和高度作为参数,并返回转换后的 Bitmapkey 方法用于获取该转换的唯一标识,以便在缓存等场景中判断两个转换是否相同。

3.2 接口实现示例

// 自定义一个简单的灰度转换实现类,实现 Transformation 接口
public class GrayscaleTransformation implements Transformation {
    @Override
    public Bitmap transform(Picasso picasso, Bitmap source, int width, int height) {
        // 创建一个与原始 Bitmap 相同宽度和高度的 Bitmap 对象,用于存储转换后的结果
        Bitmap result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
        // 创建 Canvas 对象,用于在 result Bitmap 上绘制
        Canvas canvas = new Canvas(result);
        // 创建 Paint 对象,用于设置绘制的颜色和效果等属性
        Paint paint = new Paint();
        // 创建 ColorMatrix 对象,用于进行颜色转换操作
        ColorMatrix colorMatrix = new ColorMatrix();
        // 设置灰度转换的颜色矩阵,将 RGB 三个通道的值设置为相同,实现灰度效果
        colorMatrix.setSaturation(0);
        // 创建 ColorMatrixColorFilter 对象,将颜色矩阵应用到 Paint 对象上
        ColorMatrixColorFilter colorFilter = new ColorMatrixColorFilter(colorMatrix);
        paint.setColorFilter(colorFilter);
        // 在 Canvas 上绘制原始 Bitmap,应用颜色转换效果
        canvas.drawBitmap(source, 0, 0, paint);
        // 释放原始 Bitmap 的资源,避免内存泄漏
        source.recycle();
        return result;
    }

    @Override
    public String key() {
        return "GrayscaleTransformation";
    }
}

在上述示例中,GrayscaleTransformation 类实现了 Transformation 接口,在 transform 方法中,通过创建 ColorMatrix 并设置其饱和度为 0,实现了将图片转换为灰度图的效果;key 方法返回该转换的唯一标识字符串。

四、ResizeTransformation 类分析

4.1 类的定义和基本属性

// ResizeTransformation 类用于实现图片尺寸调整,继承自 Transformation 接口
class ResizeTransformation implements Transformation {
    // 目标宽度
    private final int targetWidth;
    // 目标高度
    private final int targetHeight;
    // 是否保持宽高比
    private final boolean centerInside;
    // 图片的原始宽高比
    private float aspectRatio;

    // 构造函数,初始化目标宽度、高度、是否保持宽高比等属性
    public ResizeTransformation(int targetWidth, int targetHeight, boolean centerInside) {
        this.targetWidth = targetWidth;
        this.targetHeight = targetHeight;
        this.centerInside = centerInside;
    }
}

ResizeTransformation 类包含了目标宽度、目标高度、是否保持宽高比等属性,通过构造函数进行初始化,这些属性用于控制图片尺寸调整的具体方式。

4.2 图片尺寸调整逻辑

@Override
public Bitmap transform(Picasso picasso, Bitmap source, int width, int height) {
    // 获取原始图片的宽度和高度
    int sourceWidth = source.getWidth();
    int sourceHeight = source.getHeight();

    // 计算宽高比
    aspectRatio = (float) sourceWidth / (float) sourceHeight;

    // 如果目标宽度或高度为 0,则根据原始宽高比和另一个非零的目标尺寸计算相应的尺寸
    if (targetWidth == 0) {
        targetWidth = (int) (targetHeight * aspectRatio);
    } else if (targetHeight == 0) {
        targetHeight = (int) (targetWidth / aspectRatio);
    }

    // 根据是否保持宽高比和目标尺寸,计算最终的缩放尺寸
    int scaledWidth;
    int scaledHeight;
    if (centerInside) {
        if (sourceWidth > targetWidth || sourceHeight > targetHeight) {
            if (aspectRatio > 1f) {
                scaledWidth = targetWidth;
                scaledHeight = (int) (scaledWidth / aspectRatio);
            } else {
                scaledHeight = targetHeight;
                scaledWidth = (int) (scaledHeight * aspectRatio);
            }
        } else {
            scaledWidth = sourceWidth;
            scaledHeight = sourceHeight;
        }
    } else {
        if (aspectRatio > 1f) {
            scaledWidth = targetWidth;
            scaledHeight = (int) (scaledWidth / aspectRatio);
        } else {
            scaledHeight = targetHeight;
            scaledWidth = (int) (scaledHeight * aspectRatio);
        }
    }

    // 创建一个新的 Bitmap,用于存储调整尺寸后的图片
    Bitmap result = Bitmap.createScaledBitmap(source, scaledWidth, scaledHeight, true);
    // 释放原始 Bitmap 的资源
    source.recycle();
    return result;
}

transform 方法中,首先获取原始图片的宽高并计算宽高比,然后根据目标宽度和高度以及是否保持宽高比的条件,计算出最终的缩放尺寸。最后通过 Bitmap.createScaledBitmap 方法创建一个新的 Bitmap,将原始图片缩放至指定尺寸,并释放原始 Bitmap 的资源。

五、RotateTransformation 类分析

5.1 类的定义和基本属性

// RotateTransformation 类用于实现图片旋转操作,继承自 Transformation 接口
class RotateTransformation implements Transformation {
    // 旋转角度
    private final float degrees;
    // 旋转中心的 x 坐标,若为 -1 则表示使用图片中心
    private final float pivotX;
    // 旋转中心的 y 坐标,若为 -1 则表示使用图片中心
    private final float pivotY;

    // 构造函数,初始化旋转角度、旋转中心坐标等属性
    public RotateTransformation(float degrees, float pivotX, float pivotY) {
        this.degrees = degrees;
        this.pivotX = pivotX;
        this.pivotY = pivotY;
    }
}

RotateTransformation 类包含了旋转角度、旋转中心坐标等属性,通过构造函数进行初始化,这些属性决定了图片旋转的具体方式。

5.2 图片旋转逻辑

@Override
public Bitmap transform(Picasso picasso, Bitmap source, int width, int height) {
    // 创建一个与原始 Bitmap 相同宽度和高度的 Bitmap 对象,用于存储旋转后的结果
    Bitmap result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
    // 创建 Canvas 对象,用于在 result Bitmap 上绘制
    Canvas canvas = new Canvas(result);
    // 创建 Paint 对象,用于设置绘制的颜色和效果等属性
    Paint paint = new Paint();

    // 计算旋转中心坐标,如果为 -1 则使用图片中心
    float x = pivotX == -1? source.getWidth() / 2f : pivotX;
    float y = pivotY == -1? source.getHeight() / 2f : pivotY;

    // 将画布围绕指定的旋转中心旋转指定的角度
    canvas.rotate(degrees, x, y);
    // 在 Canvas 上绘制原始 Bitmap
    canvas.drawBitmap(source, 0, 0, paint);
    // 释放原始 Bitmap 的资源
    source.recycle();
    return result;
}

transform 方法中,首先创建一个新的 BitmapCanvas,然后根据旋转中心坐标的设置计算出实际的旋转中心。接着使用 canvas.rotate 方法将画布围绕指定中心旋转指定角度,最后在旋转后的画布上绘制原始 Bitmap,并释放原始 Bitmap 的资源。

六、CenterCrop 类分析

6.1 类的定义和实现

// CenterCrop 类用于实现图片的中心裁剪,实现了 Transformation 接口
public class CenterCrop implements Transformation {
    @Override
    public Bitmap transform(Picasso picasso, Bitmap source, int width, int height) {
        int sourceWidth = source.getWidth();
        int sourceHeight = source.getHeight();

        // 计算裁剪的起始 x 坐标
        int x = (sourceWidth - width) / 2;
        // 计算裁剪的起始 y 坐标
        int y = (sourceHeight - height) / 2;

        // 创建一个矩形区域,用于指定裁剪的范围
        Rect srcRect = new Rect(x, y, x + width, y + height);
        // 创建一个与目标尺寸相同的 Bitmap,用于存储裁剪后的结果
        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        // 创建 Canvas 对象,用于在 result Bitmap 上绘制
        Canvas canvas = new Canvas(result);
        // 创建 Paint 对象,用于设置绘制的颜色和效果等属性
        Paint paint = new Paint();

        // 在 Canvas 上绘制原始 Bitmap 的指定区域,实现中心裁剪效果
        canvas.drawBitmap(source, srcRect, new Rect(0, 0, width, height), paint);
        // 释放原始 Bitmap 的资源
        source.recycle();
        return result;
    }

    @Override
    public String key() {
        return "CenterCrop";
    }
}

CenterCrop 类的 transform 方法中,通过计算裁剪的起始坐标,确定一个矩形区域,然后将原始 Bitmap 的该区域绘制到一个新的与目标尺寸相同的 Bitmap 上,从而实现中心裁剪的效果。key 方法返回该转换的唯一标识字符串。

七、Fit 类分析

7.1 类的定义和实现

// Fit 类用于实现图片的适配显示,实现了 Transformation 接口
public class Fit implements Transformation {
    @Override
    public Bitmap transform(Picasso picasso, Bitmap source, int width, int height) {
        int sourceWidth = source.getWidth();
        int sourceHeight = source.getHeight();

        // 计算宽高比
        float aspectRatio = (float) sourceWidth / (float) sourceHeight;

        // 根据目标宽度和高度以及宽高比,计算缩放后的宽度和高度
        int scaledWidth;
        int scaledHeight;
        if (width * aspectRatio > height) {
            scaledWidth = (int) (height * aspectRatio);
            scaledHeight = height;
        } else {
            scaledWidth = width;
            scaledHeight = (int) (width / aspectRatio);
        }

        // 创建一个新的 Bitmap,用于存储适配后的图片
        Bitmap result = Bitmap.createScaledBitmap(source, scaledWidth, scaledHeight, true);
        // 释放原始 Bitmap 的资源
        source.recycle();
        return result;
    }

    @Override
    public String key() {
        return "Fit";
    }
}

Fit 类的 transform 方法根据目标宽度和高度以及原始图片的宽高比,计算出缩放后的尺寸,然后通过 Bitmap.createScaledBitmap 方法创建一个新的 Bitmap,将原始图片缩放至适配尺寸,并释放原始 Bitmap 的资源。key 方法返回该转换的唯一标识字符串。

八、转换模块的工作流程

8.1 转换请求的发起

当开发者在使用 Picasso 加载图片时,通过 transform 方法或其他相关 API 传入自定义的 Transformation 实现类或使用 Picasso 内置的转换类(如 CenterCropFit 等),从而发起图片转换请求。例如:

Picasso.get()
      .load("https://example.com/image.jpg")
      .transform(new GrayscaleTransformation())  // 传入自定义的灰度转换类
      .into(imageView);

在上述代码中,通过 transform 方法传入了自定义的 GrayscaleTransformation 类,告诉 Picasso 在加载图片后要进行灰度转换操作。

8.2 转换操作的执行

在图片加载完成后,Picasso 会根据传入的转换类调用相应的 transform 方法。以 ResizeTransformation 为例,其执行过程如下:

  1. 首先获取原始图片的宽度和高度,计算宽高比。
  2. 根据目标宽度和高度以及是否保持宽高比的条件,计算出最终的缩放尺寸。
  3. 使用 Bitmap.createScaledBitmap 方法创建一个新的 Bitmap,将原始图片缩放至指定尺寸。
  4. 释放原始 Bitmap 的资源,返回转换后的 Bitmap

对于其他转换类,如 RotateTransformationCenterCropFit 等,也是按照类似的流程,根据各自的逻辑在 transform 方法中对原始图片进行相应的转换操作。

8.3 转换结果的处理

转换后的 Bitmap 会被传递给后续的流程,如显示在 ImageView 中或进行进一步的处理。如果开启了缓存功能,转换后的 Bitmap 及其对应的转换标识(通过 key 方法获取)会被存入缓存中,以便下次相同的转换请求可以直接从缓存中获取,提高图片加载和转换的效率。

九、转换模块的优化策略

9.1 缓存优化

  • 转换结果缓存:Picasso 会根据转换类的 key 方法返回的唯一标识,将转换后的 Bitmap 存入缓存中。在下次遇到相同的转换请求时,可以直接从缓存中获取,避免重复的转换操作。例如,对于多次请求相同图片的灰度转换,由于 GrayscaleTransformationkey 方法返回固定的字符串,第二次及以后的请求可以直接从缓存中获取转换后的灰度图。
// 假设第一次请求图片并进行灰度转换
Picasso.get()
      .load("https://example.com/image.jpg")
      .transform(new GrayscaleTransformation())
      .into(imageView1);

// 第二次请求相同图片的灰度转换,此时会从缓存中获取
Picasso.get()
      .load("https://example.com/image.jpg")
      .transform(new GrayscaleTransformation())
      .into(imageView2);
  • 缓存淘汰策略:当缓存空间不足时,Picasso 会采用一定的缓存淘汰策略,如 LRU(最近最少使用)算法,删除最近最少使用的缓存项,以腾出空间存储新的转换结果。

9.2 性能优化

  • 减少内存占用:在进行图片转换操作时,及时释放原始 Bitmap 的资源,避免内存泄漏和过多的内存占用。例如,在每个 transform 方法的最后都调用 source.recycle() 方法释放原始 Bitmap
  • 优化转换算法:对于一些复杂的转换操作,如图片的缩放和旋转,可以采用更高效的算法来减少计算量和时间开销。例如,在进行缩放时,可以使用一些优化的插值算法来提高缩放后的图片质量和计算效率。

9.3 兼容性优化

  • 不同设备和系统版本适配:考虑到 Android 设备的多样性和系统版本的差异,转换模块需要确保在各种设备和系统上都能正常工作。例如,在处理不同分辨率和屏幕密度的设备时,要准确计算图片的尺寸调整和转换参数,以保证图片在不同设备上都能正确显示。
  • 图片格式兼容性:支持多种常见的图片格式,如 JPEG、PNG