android开发之图片压缩 | 南锋

南锋

南奔万里空,脱死锋镝余

android开发之图片压缩

在游戏中新增了一个客服功能,要求玩家能够在app内发送图片。测试时发现图片太大,发送出去,于是选择在前端压缩代码。

测试时发现图片因为太大了,直接被服务器拒绝。报错413 Request Entity Too Large,如下图:
错误截图

实现思路

使用BitmapFactoryBitmap类进行图片压缩,并保存压缩后的图片路径,然后在发送的时候发送压缩后的图片。

方式一:压缩图片质量,并修改图片尺寸

1、压缩图片路径

  • 压缩后的图片保存在应用的缓存目录下,文件名添加了compressed_前缀

2、图片压缩逻辑

  • 使用BitmapFactory.OptionsisSampleSize对图片进行采样缩小
  • 使用Bitmap.compress()以指定质量保存压缩图片

3、返回压缩路径

  • 将压缩后的图片路径通过 JSON 返回,替代原始图片路径。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private void handleGalleryResult(Intent data) {
Log.d("ImageUri", data.toString());
if (data.getData() != null) {
Uri selectedImageUri = data.getData();
String originalImagePath = getPathFromUri(selectedImageUri);
Log.d("ImageUri", "Original Path: " + originalImagePath);

try {
// 压缩图片
String compressedImagePath = compressImage(originalImagePath);
Log.d("ImageUri", "Compressed Path: " + compressedImagePath);

JSONObject jsonobj = new JSONObject();
jsonobj.put("ImageUrl", compressedImagePath); // 返回压缩后的图片路径
jsonobj.put("type", "selectedImageUrl");
String str = String.format("NativeAndroid.javaCallback('%s')", jsonobj.toString());
callJsGlobalFunc(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}

private String compressImage(String imagePath) throws Exception {
// 设置目标文件路径
File originalFile = new File(imagePath);
File compressedFile = new File(getActivity().getCacheDir(), "compressed_" + originalFile.getName());

// 加载原始图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);

// 计算压缩比例
options.inSampleSize = calculateInSampleSize(options, 800, 800); // 压缩到800x800以内
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(imagePath, options);

// 保存压缩后的图片
try (FileOutputStream fos = new FileOutputStream(compressedFile)) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); // 质量设置为80%
}

// 回收Bitmap资源
bitmap.recycle();

return compressedFile.getAbsolutePath();
}

private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;

if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;

while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}

return inSampleSize;
}

方式二:只压缩图片质量,不调整图片尺寸

关键改动

1、不调整图片分辨率:

  • 不使用BitmapFactory.OptionsinSampleSize 来缩小图片。
  • 直接加载原始图片,并保持其原始尺寸。

2、仅降低图片质量:

  • 使用 Bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos) 减少文件大小。
  • 参数 quality 设置为 80(可以根据需求调整,范围为 0-100)。

3、保存路径:

  • 保存到应用缓存目录,文件名前缀为 compressed_,以便与原始图片区分。
    注意事项
    如果图片格式是非 JPEG(如 PNG),可以根据需求替换为 Bitmap.CompressFormat.PNG,但 PNG 不支持质量压缩。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void handleGalleryResult(Intent data) {
Log.d("ImageUri", data.toString());
if (data.getData() != null) {
Uri selectedImageUri = data.getData();
String originalImagePath = getPathFromUri(selectedImageUri);
Log.d("ImageUri", "Original Path: " + originalImagePath);

try {
// 压缩图片
String compressedImagePath = compressImageWithoutChangingSize(originalImagePath);
Log.d("ImageUri", "Compressed Path: " + compressedImagePath);

JSONObject jsonobj = new JSONObject();
jsonobj.put("ImageUrl", compressedImagePath); // 返回压缩后的图片路径
jsonobj.put("type", "selectedImageUrl");
String str = String.format("NativeAndroid.javaCallback('%s')", jsonobj.toString());
callJsGlobalFunc(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}

private String compressImageWithoutChangingSize(String imagePath) throws Exception {
// 设置目标文件路径
File originalFile = new File(imagePath);
File compressedFile = new File(getActivity().getCacheDir(), "compressed_" + originalFile.getName());

// 加载原始图片
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);

// 保存压缩后的图片
try (FileOutputStream fos = new FileOutputStream(compressedFile)) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); // 仅降低质量,保持尺寸不变
}

// 回收Bitmap资源
bitmap.recycle();

return compressedFile.getAbsolutePath();
}

方式三:将图片压缩到指定尺寸和指定大小

经过前面两种方法测试后,发现部分图片还是会存在问题,因为现在手机的分辨率比较高,很多图片都比较大,如果按照比例压缩,还是可能出现图片太大的情况。我这里是将所有图片压缩到不超过300KB,这个可以根据自己想要的去调。

思路
根据前面两种方式的体验,将压缩的方式修改为循环压缩,每次压缩的比例都会小一点点,直到图片最后的大小小于300KB为止。如果图片原图就小于300KB。则直接不压缩。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
private void handleGalleryResult(Intent data) {
Log.d("ImageUri", data.toString());
if (data.getData() != null) {
Uri selectedImageUri = data.getData();
String originalImagePath = getPathFromUri(selectedImageUri);
Log.d("ImageUri", "Original Path: " + originalImagePath);

try {
// 检查并压缩图片
String finalImagePath = compressAndResizeImage(originalImagePath, 300 * 1024); // 300KB
Log.d("ImageUri", "Final Path: " + finalImagePath);

JSONObject jsonobj = new JSONObject();
jsonobj.put("ImageUrl", finalImagePath); // 返回最终图片路径
jsonobj.put("type", "selectedImageUrl");
String str = String.format("NativeAndroid.javaCallback('%s')", jsonobj.toString());
callJsGlobalFunc(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}

private String compressAndResizeImage(String imagePath, long targetSizeInBytes) throws Exception {
File originalFile = new File(imagePath);
long originalSize = originalFile.length();

// 如果图片小于目标大小,直接返回原始路径
if (originalSize <= targetSizeInBytes) {
Log.d("ImageCompression", "Image size is already within the target: " + originalSize + " bytes");
return imagePath;
}

// 加载原始图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false; // 加载完整图片
Bitmap originalBitmap = BitmapFactory.decodeFile(imagePath, options);

// 缩小图片尺寸到原尺寸的一半
int newWidth = options.outWidth / 2;
int newHeight = options.outHeight / 2;
Bitmap resizedBitmap = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true);

// 设置目标文件路径
File compressedFile = new File(getActivity().getCacheDir(), "compressed_" + originalFile.getName());

int quality = 100; // 初始质量
while (true) {
// 保存压缩图片
try (FileOutputStream fos = new FileOutputStream(compressedFile)) {
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos);
}

// 检查压缩后的文件大小
long compressedSize = compressedFile.length();
Log.d("ImageCompression", "Compressed size: " + compressedSize + " bytes at quality: " + quality);

if (compressedSize <= targetSizeInBytes || quality <= 10) {
// 达到目标大小或质量降低到最低限制
break;
}

// 减小质量继续压缩
quality -= 5;
}

// 回收Bitmap资源
originalBitmap.recycle();
resizedBitmap.recycle();

return compressedFile.getAbsolutePath();
}
+