图片选择与剪裁阶段调试完成

This commit is contained in:
2025-12-03 16:29:52 +08:00
parent 8fb7147333
commit 16c44e5e0e
3 changed files with 257 additions and 292 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Dec 02 06:01:03 GMT 2025
#Wed Dec 03 08:28:19 GMT 2025
stageCount=13
libraryProject=
baseVersion=15.11
publishVersion=15.11.12
buildCount=110
buildCount=121
baseBetaVersion=15.11.13

View File

@@ -115,7 +115,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
selectTempDir.mkdirs();
LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath());
}
// 初始化UI及数据
initToolbar();
initClickListeners();
@@ -181,7 +181,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}
}
}
boolean isImageType(String lowerMimeType) {
return lowerMimeType.equals("image/jpeg")
|| lowerMimeType.equals("image/png")
@@ -237,7 +237,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override
public void onClick(View v) {
LogUtils.d(TAG, "【按钮点击】触发选择图片功能");
// 调用权限工具类校验存储权限
if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) {
LogUtils.d(TAG, "【选图权限】存储权限已获取,开始查找图片选择意图");
@@ -483,10 +483,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseBackgroundScaledCompressFile(false);
mBgSourceUtils.saveSettings();
doubleRefreshPreview();
File previewFile = mBgSourceUtils.getCropSourceFile(); // 裁剪缓存图片
File previewFile = new File(previewBean.getBackgroundFilePath()); // 裁剪缓存图片
// 2. 生成裁剪输入Uri强化MIUI权限授予直接用原图Uri
Uri inputUri = null;
@@ -503,7 +503,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 3. 【核心优化】直接使用预览图原图作为裁剪输入,删除原工具类创建裁剪路径的复制逻辑
// 仅创建裁剪结果临时文件(用于接收裁剪输出,不涉及原图复制)
File cropResultTempFile = mBgSourceUtils.getCropResultFile();
File cropResultTempFile = new File(previewBean.getBackgroundScaledCompressFilePath());
if (cropResultTempFile == null) {
ToastUtils.show("裁剪路径创建失败,请重试");
return;
@@ -595,7 +595,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
ToastUtils.show("无法启动裁剪工具");
}
}
/**
* 计算两个整数的最大公约数GCDGreatest Common Divisor
* 用途:裁剪时获取视图宽高的最简比例(避免比例值过大导致裁剪工具崩溃)
@@ -629,119 +629,119 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
* 保存裁剪后的图片(修复:路径同步时序+压缩图路径强绑定)
* 作用裁剪后保存原图→生成压缩图→同步双路径到预览Bean→清理临时文件
*/
private void saveCropBitmap(Bitmap bitmap) {
LogUtils.d(TAG, "【保存启动】开始保存裁剪图片仅更新预览Bean不影响正式Bean");
if (bitmap == null || bitmap.isRecycled()) {
ToastUtils.show("裁剪图片为空");
//mBgSourceUtils.clearCropTempFiles();
return;
}
// 内存优化大图片自动缩放保留原有逻辑避免OOM
Bitmap scaledBitmap = bitmap;
int originalSize = bitmap.getByteCount() / 1024 / 1024;
if (originalSize > 5) {
float scale = 1.0f;
while (scaledBitmap.getByteCount() / 1024 / 1024 > 2) {
scale -= 0.2f;
if (scale < 0.2f) break;
scaledBitmap = scaleBitmap(scaledBitmap, scale); // 复用原有缩放方法
}
LogUtils.d(TAG, "【内存优化】大图片自动缩放:原始大小=" + originalSize + "MB缩放后大小=" + scaledBitmap.getByteCount() / 1024 / 1024 + "MB");
}
File cropSaveFile = new File(mBgSourceUtils.getPreviewBackgroundFilePath());
FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
// 1. 清理旧的裁剪预览图(避免文件残留)
// if (cropSaveFile.exists()) {
// mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "旧裁剪预览图");
// private void saveCropBitmap(Bitmap bitmap) {
// LogUtils.d(TAG, "【保存启动】开始保存裁剪图片仅更新预览Bean不影响正式Bean");
// if (bitmap == null || bitmap.isRecycled()) {
// ToastUtils.show("裁剪图片为空");
// //mBgSourceUtils.clearCropTempFiles();
// return;
// }
//
// // 内存优化大图片自动缩放保留原有逻辑避免OOM
// Bitmap scaledBitmap = bitmap;
// int originalSize = bitmap.getByteCount() / 1024 / 1024;
// if (originalSize > 5) {
// float scale = 1.0f;
// while (scaledBitmap.getByteCount() / 1024 / 1024 > 2) {
// scale -= 0.2f;
// if (scale < 0.2f) break;
// scaledBitmap = scaleBitmap(scaledBitmap, scale); // 复用原有缩放方法
// }
// 确保父目录存在兼容Android 10+分区存储,多包名环境下目录适配)
File parentDir = cropSaveFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
mBgSourceUtils.copyFile(new File(""), parentDir); // 复用原有目录创建逻辑
}
if (parentDir == null) {
LogUtils.e(TAG, "【裁剪保存失败】目标文件父目录为空");
ToastUtils.show("裁剪图片保存失败");
return;
}
cropSaveFile.createNewFile();
// LogUtils.d(TAG, "【内存优化】大图片自动缩放:原始大小=" + originalSize + "MB缩放后大小=" + scaledBitmap.getByteCount() / 1024 / 1024 + "MB");
// }
//
// File cropSaveFile = new File(mBgSourceUtils.getPreviewBackgroundFilePath());
// FileOutputStream fos = null;
// BufferedOutputStream bos = null;
// try {
// // 1. 清理旧的裁剪预览图(避免文件残留)
//// if (cropSaveFile.exists()) {
//// mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "旧裁剪预览图");
//// }
// // 确保父目录存在兼容Android 10+分区存储,多包名环境下目录适配)
// File parentDir = cropSaveFile.getParentFile();
// if (parentDir != null && !parentDir.exists()) {
// parentDir.mkdirs();
// mBgSourceUtils.copyFile(new File(""), parentDir); // 复用原有目录创建逻辑
// }
// if (parentDir == null) {
// LogUtils.e(TAG, "【裁剪保存失败】目标文件父目录为空");
// ToastUtils.show("裁剪图片保存失败");
// return;
// }
// cropSaveFile.createNewFile();
//
// // 2. 写入裁剪原图到指定路径(覆盖旧文件)
// fos = new FileOutputStream(cropSaveFile);
// bos = new BufferedOutputStream(fos);
// scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 85, bos);
// bos.flush();
// // 强制同步到磁盘避免Android 10+异步写入导致文件读取不到)
// if (fos != null) {
// try {
// fos.getFD().sync();
// } catch (IOException e) {
// LogUtils.w(TAG, "【裁剪保存】sync()调用失败用flush()兜底:" + e.getMessage());
// bos.flush();
// }
// }
// LogUtils.d(TAG, "【裁剪保存】预览原图保存成功:" + cropSaveFile.getAbsolutePath() + ",大小=" + cropSaveFile.length() + "bytes");
//
// // 3. 生成新压缩图(关键:先获取/生成压缩路径,再压缩)
// String newCompressPath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath();
// // 兜底:若压缩路径为空,生成新的唯一路径(避免空指针,兼容多包名路径隔离)
// if (TextUtils.isEmpty(newCompressPath)) {
// String sourceDir = mBgSourceUtils.getBackgroundSourceDirPath();
// newCompressPath = new File(sourceDir, "ScaledCompress_" + System.currentTimeMillis() + ".jpg").getAbsolutePath();
// }
// // 调用重载方法压缩,传入指定压缩路径
// mBgSourceUtils.compressQualityToRecivedPicture(scaledBitmap, newCompressPath);
//
// // 4. 同步更新预览Bean核心修复同时绑定原图路径+压缩图路径)
// mBgSourceUtils.saveFileToPreviewBean(cropSaveFile, "裁剪后图片"); // 原有方法:更新原图路径
// BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
// if (previewBean != null) {
// previewBean.setBackgroundScaledCompressFilePath(newCompressPath); // 新增:绑定压缩图路径
// mBgSourceUtils.saveSettings(); // 持久化Bean配置避免路径丢失多包名环境下配置隔离
// LogUtils.d(TAG, "【路径同步】预览Bean双路径同步完成");
// LogUtils.d(TAG, "→ 原图路径:" + previewBean.getBackgroundFilePath());
// LogUtils.d(TAG, "→ 压缩图路径:" + previewBean.getBackgroundScaledCompressFilePath());
// } else {
// LogUtils.e(TAG, "【路径同步失败】预览Bean为空");
// }
//
// ToastUtils.show("裁剪图片保存成功");
// } catch (IOException e) {
// LogUtils.e(TAG, "【裁剪保存失败】IO异常" + e.getMessage(), e);
// ToastUtils.show("裁剪图片保存失败");
// } finally {
// // 资源回收避免内存泄漏兼容低版本Android
// if (bos != null) {
// try {
// bos.close();
// } catch (IOException e) {
// LogUtils.e(TAG, "【流关闭失败】BufferedOutputStream" + e.getMessage());
// }
// }
// if (fos != null) {
// try {
// fos.close();
// } catch (IOException e) {
// LogUtils.e(TAG, "【流关闭失败】FileOutputStream" + e.getMessage());
// }
// }
// // 回收缩放后的Bitmap避免重复回收
// if (scaledBitmap != bitmap && scaledBitmap != null && !scaledBitmap.isRecycled()) {
// scaledBitmap.recycle();
// }
// if (bitmap != null && !bitmap.isRecycled()) {
// bitmap.recycle();
// }
// }
//
// LogUtils.d(TAG, "【裁剪保存】流程结束。");
// }
// 2. 写入裁剪原图到指定路径(覆盖旧文件)
fos = new FileOutputStream(cropSaveFile);
bos = new BufferedOutputStream(fos);
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 85, bos);
bos.flush();
// 强制同步到磁盘避免Android 10+异步写入导致文件读取不到)
if (fos != null) {
try {
fos.getFD().sync();
} catch (IOException e) {
LogUtils.w(TAG, "【裁剪保存】sync()调用失败用flush()兜底:" + e.getMessage());
bos.flush();
}
}
LogUtils.d(TAG, "【裁剪保存】预览原图保存成功:" + cropSaveFile.getAbsolutePath() + ",大小=" + cropSaveFile.length() + "bytes");
// 3. 生成新压缩图(关键:先获取/生成压缩路径,再压缩)
String newCompressPath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath();
// 兜底:若压缩路径为空,生成新的唯一路径(避免空指针,兼容多包名路径隔离)
if (TextUtils.isEmpty(newCompressPath)) {
String sourceDir = mBgSourceUtils.getBackgroundSourceDirPath();
newCompressPath = new File(sourceDir, "ScaledCompress_" + System.currentTimeMillis() + ".jpg").getAbsolutePath();
}
// 调用重载方法压缩,传入指定压缩路径
mBgSourceUtils.compressQualityToRecivedPicture(scaledBitmap, newCompressPath);
// 4. 同步更新预览Bean核心修复同时绑定原图路径+压缩图路径)
mBgSourceUtils.saveFileToPreviewBean(cropSaveFile, "裁剪后图片"); // 原有方法:更新原图路径
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
if (previewBean != null) {
previewBean.setBackgroundScaledCompressFilePath(newCompressPath); // 新增:绑定压缩图路径
mBgSourceUtils.saveSettings(); // 持久化Bean配置避免路径丢失多包名环境下配置隔离
LogUtils.d(TAG, "【路径同步】预览Bean双路径同步完成");
LogUtils.d(TAG, "→ 原图路径:" + previewBean.getBackgroundFilePath());
LogUtils.d(TAG, "→ 压缩图路径:" + previewBean.getBackgroundScaledCompressFilePath());
} else {
LogUtils.e(TAG, "【路径同步失败】预览Bean为空");
}
ToastUtils.show("裁剪图片保存成功");
} catch (IOException e) {
LogUtils.e(TAG, "【裁剪保存失败】IO异常" + e.getMessage(), e);
ToastUtils.show("裁剪图片保存失败");
} finally {
// 资源回收避免内存泄漏兼容低版本Android
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
LogUtils.e(TAG, "【流关闭失败】BufferedOutputStream" + e.getMessage());
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
LogUtils.e(TAG, "【流关闭失败】FileOutputStream" + e.getMessage());
}
}
// 回收缩放后的Bitmap避免重复回收
if (scaledBitmap != bitmap && scaledBitmap != null && !scaledBitmap.isRecycled()) {
scaledBitmap.recycle();
}
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
LogUtils.d(TAG, "【裁剪保存】流程结束。");
}
/**
* 图片缩放核心用途大图片自动缩小降低内存占用避免OOM崩溃
* 逻辑按指定缩放比例0~1之间缩小Bitmap保持宽高比不变生成新的Bitmap并回收旧图
@@ -901,11 +901,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 同步预览并启动裁剪
mBgSourceUtils.saveFileToPreviewBean(mfTakePhoto, mfTakePhoto.getAbsolutePath());
doubleRefreshPreview();
startCropImageActivity(false);
LogUtils.d(TAG, "【拍照完成】拍照回调处理结束,已启动裁剪");
}
/**
* 解析拍照结果为Bitmap核心用途拍照回调后从拍照临时文件/Intent中获取Bitmap用于后续压缩保存
* 逻辑优先从拍照临时文件解析清晰原图失败则从Intent获取缩略图兜底双重保障避免解析失败
@@ -960,50 +960,65 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
*/
private void handleCropImageResult(int requestCode, int resultCode, Intent data) {
// 从工具类获取裁剪临时文件(唯一入口)
File cropTempFile = mBgSourceUtils.getCropSourceFile();
boolean isFileExist = cropTempFile != null && cropTempFile.exists();
File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
boolean isFileExist = cropTempFile.exists();
boolean isFileReadable = isFileExist ? cropTempFile.canRead() : false;
long fileSize = isFileExist ? cropTempFile.length() : 0;
// 适配MIUI仅resultCode=RESULT_OK且文件有效时视为成功
boolean isCropSuccess = (resultCode == RESULT_OK) && isFileExist && isFileReadable && fileSize > 100;
if (isCropSuccess) {
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseBackgroundScaledCompressFile(isCropSuccess);
mBgSourceUtils.saveSettings();
doubleRefreshPreview();
} else {
// 其他失败场景
handleOperationCancelOrFail();
}
// 打印校验日志
LogUtils.d(TAG, "【裁剪回调】校验resultCode=" + resultCode + ",文件存在=" + isFileExist + ",大小=" + fileSize + "bytes是否成功=" + isCropSuccess);
// 处理MIUI裁剪取消resultCode=0视为取消
if (resultCode == 0 && !isCropSuccess) {
LogUtils.d(TAG, "【裁剪回调】MIUI 裁剪工具已取消");
ToastUtils.show("裁剪已取消");
//mBgSourceUtils.clearCropTempFiles();
return;
}
// 处理裁剪文件为空(真正的裁剪失败)
if (isFileExist && fileSize == 0) {
LogUtils.e(TAG, "【裁剪失败】裁剪文件为空MIUI适配问题");
ToastUtils.show("裁剪失败,请尝试选择「系统相机」裁剪或更换图片");
//mBgSourceUtils.clearCropTempFiles();
return;
}
// 裁剪成功解析Bitmap并保存
if (isCropSuccess) {
Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile);
if (cropBitmap != null && !cropBitmap.isRecycled()) {
saveCropBitmap(cropBitmap); // 保存裁剪结果
doubleRefreshPreview(); // 双重刷新预览适配MIUI渲染延迟
LogUtils.d(TAG, "【裁剪完成】裁剪回调处理结束");
} else {
ToastUtils.show("获取裁剪图片失败");
LogUtils.e(TAG, "【裁剪回调失败】Bitmap解析异常");
//mBgSourceUtils.clearCropTempFiles();
}
} else {
// 其他失败场景
handleOperationCancelOrFail();
}
// LogUtils.d(TAG, "【裁剪回调】校验resultCode=" + resultCode + ",文件存在=" + isFileExist + ",大小=" + fileSize + "bytes是否成功=" + isCropSuccess);
//
// // 处理MIUI裁剪取消resultCode=0视为取消
// if (resultCode == 0 && !isCropSuccess) {
// LogUtils.d(TAG, "【裁剪回调】MIUI 裁剪工具已取消");
// ToastUtils.show("裁剪已取消");
// //mBgSourceUtils.clearCropTempFiles();
// return;
// }
//
// // 处理裁剪文件为空(真正的裁剪失败)
// if (isFileExist && fileSize == 0) {
// LogUtils.e(TAG, "【裁剪失败】裁剪文件为空MIUI适配问题");
// ToastUtils.show("裁剪失败,请尝试选择「系统相机」裁剪或更换图片");
// //mBgSourceUtils.clearCropTempFiles();
// return;
// }
//
// // 裁剪成功解析Bitmap并保存
// if (isCropSuccess) {
// BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
// previewBean.setIsUseBackgroundScaledCompressFile(isCropSuccess);
// mBgSourceUtils.saveSettings();
//
// doubleRefreshPreview();
//
// Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile);
// if (cropBitmap != null && !cropBitmap.isRecycled()) {
// saveCropBitmap(cropBitmap); // 保存裁剪结果
// // 更新预览数据集设置
// BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
// previewBean.setIsUseBackgroundScaledCompressFile(isCropSuccess);
// mBgSourceUtils.saveSettings();
//
// doubleRefreshPreview(); // 双重刷新预览适配MIUI渲染延迟
// LogUtils.d(TAG, "【裁剪完成】裁剪回调处理结束");
// } else {
// ToastUtils.show("获取裁剪图片失败");
// LogUtils.e(TAG, "【裁剪回调失败】Bitmap解析异常");
// //mBgSourceUtils.clearCropTempFiles();
// }
}
/**
* 解析裁剪临时文件为Bitmap核心用途裁剪回调后将裁剪生成的临时文件转为Bitmap用于后续保存/预览)
* 逻辑采用采样率加载避免OOM支持JPEG/PNG等主流格式兼容破损文件/空文件场景
@@ -1081,7 +1096,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return cropBitmap;
}
/**
* 双重刷新预览核心用途适配MIUI等机型裁剪/选图后预览渲染延迟,解决“图片已保存但界面空白”问题)
* 逻辑:先立即刷新一次,再延迟短时间二次刷新,确保控件能获取到最新的图片文件(规避异步渲染/文件写入延迟)
@@ -1134,78 +1149,61 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 关键修复1解析Uri为文件强制使用独立的选图临时文件避免路径混淆
mBgSourceUtils.createCropFileProviderPath(selectedImage);
if (mBgSourceUtils.getCropSourceFile() == null || !mBgSourceUtils.getCropSourceFile().exists() || mBgSourceUtils.getCropSourceFile().length() <= 0) {
ToastUtils.show("选择的图片文件无效或无法读取");
return;
}
LogUtils.d(TAG, "【选图解析】选图临时文件生成成功:" + mBgSourceUtils.getCropSourceFile().getAbsolutePath() + ",大小:" + mBgSourceUtils.getCropSourceFile().length() + "bytes");
// 关键修复2同步预览Bean强制绑定「原图路径+压缩图路径」双路径,避免路径错位)
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
if (previewBean == null) {
ToastUtils.show("预览配置初始化失败");
return;
}
// 关键强制绑定压缩图路径到BackgroundCrops目录统一存储避免路径错乱
String targetCompressPath = mBgSourceUtils.getCropResultFile().getAbsolutePath();
String targetCompressPath = mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath();
LogUtils.d(TAG, "【选图同步】预览Bean双路径绑定完成");
LogUtils.d(TAG, "→ 原图路径(选图临时文件):" + previewBean.getBackgroundFilePath());
LogUtils.d(TAG, "→ 压缩图路径BackgroundCrops" + previewBean.getBackgroundScaledCompressFilePath());
LogUtils.d(TAG, "→ 原图路径(选图临时文件):" + mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
LogUtils.d(TAG, "→ 压缩图路径 " + targetCompressPath);
startCropImageActivity(false);
// 关键修复3生成压缩图指定BackgroundCrops目录下的路径确保与预览Bean路径一致
LogUtils.d(TAG, "【选图压缩】开始生成预览压缩图,目标路径:" + targetCompressPath);
Bitmap selectBitmap = null;
try {
// 优化使用采样率加载Bitmap避免OOM原有直接decodeFile易导致大图片崩溃
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mBgSourceUtils.getCropSourceFile().getAbsolutePath(), options);
// 计算采样率最大尺寸限制为2048px
int maxSize = 2048;
int sampleSize = 1;
while (options.outWidth / sampleSize > maxSize || options.outHeight / sampleSize > maxSize) {
sampleSize *= 2;
}
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存
selectBitmap = BitmapFactory.decodeFile(mBgSourceUtils.getCropSourceFile().getAbsolutePath(), options);
} catch (Exception e) {
LogUtils.e(TAG, "【选图压缩】Bitmap加载失败" + e.getMessage(), e);
}
if (selectBitmap != null && !selectBitmap.isRecycled()) {
mBgSourceUtils.compressQualityToRecivedPicture(selectBitmap, targetCompressPath); // 调用重载函数,指定路径压缩
selectBitmap.recycle(); // 及时回收,避免内存泄漏
} else {
ToastUtils.show("选图后压缩图生成失败");
LogUtils.e(TAG, "【选图压缩失败】无法解析选图文件为Bitmap");
return;
}
//LogUtils.d(TAG, "【选图压缩】开始生成预览压缩图,目标路径:" + targetCompressPath);
// Bitmap selectBitmap = null;
// try {
// // 优化使用采样率加载Bitmap避免OOM原有直接decodeFile易导致大图片崩溃
// BitmapFactory.Options options = new BitmapFactory.Options();
// options.inJustDecodeBounds = true;
// BitmapFactory.decodeFile(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath(), options);
// // 计算采样率最大尺寸限制为2048px
// int maxSize = 2048;
// int sampleSize = 1;
// while (options.outWidth / sampleSize > maxSize || options.outHeight / sampleSize > maxSize) {
// sampleSize *= 2;
// }
// options.inJustDecodeBounds = false;
// options.inSampleSize = sampleSize;
// options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存
// selectBitmap = BitmapFactory.decodeFile(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath(), options);
// LogUtils.d(TAG, "");
// } catch (Exception e) {
// LogUtils.e(TAG, "【选图压缩】Bitmap加载失败" + e.getMessage(), e);
// }
// 关键修复4校验压缩图是否生成成功避免压缩失败但仍启动裁剪
File compressFile = new File(targetCompressPath);
if (!compressFile.exists() || compressFile.length() <= 0) {
ToastUtils.show("压缩图生成失败,请重新选择图片");
LogUtils.e(TAG, "【选图压缩失败】压缩图文件无效:" + targetCompressPath);
return;
}
// File compressFile = new File(targetCompressPath);
// if (!compressFile.exists() || compressFile.length() <= 0) {
// ToastUtils.show("压缩图生成失败,请重新选择图片");
// LogUtils.e(TAG, "【选图压缩失败】压缩图文件无效:" + targetCompressPath);
// return;
// }
// 刷新预览并启动裁剪无需重复同步预览Bean已提前绑定
doubleRefreshPreview();
// 新增延迟50ms启动裁剪适配部分机型选图后文件写入延迟
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
if (!isFinishing()) {
startCropImageActivity(false);
LogUtils.d(TAG, "【选图完成】选图回调处理结束,已启动裁剪(正式图+压缩图均已生成)");
}
}
}, 50);
/*doubleRefreshPreview();
// 新增延迟50ms启动裁剪适配部分机型选图后文件写入延迟
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
if (!isFinishing()) {
startCropImageActivity(false);
LogUtils.d(TAG, "【选图完成】选图回调处理结束,已启动裁剪(正式图+压缩图均已生成)");
}
}
}, 50);
*/
}
/**
@@ -1214,7 +1212,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private void handleOperationCancelOrFail() {
initBackgroundViewByPreviewBean();
doubleRefreshPreview();
LogUtils.d(TAG, "【操作回调】操作取消或失败");
ToastUtils.show("操作已取消");
}

View File

@@ -208,7 +208,7 @@ public class BackgroundSourceUtils {
*/
private void initAllFiles() {
// 1. 裁剪临时文件
mCropSourceFile = new File(fCropCacheDir, CROP_TEMP_FILE_NAME);
//mCropSourceFile = new File(fCropCacheDir, CROP_TEMP_FILE_NAME);
// 2. 裁剪结果文件
//cropResultFile = new File(fCropCacheDir, CROP_RESULT_FILE_NAME);
@@ -216,18 +216,32 @@ public class BackgroundSourceUtils {
clearCropTempFiles();
LogUtils.d(TAG, "【文件初始化】完成。");
}
public boolean createCropFileProviderPath(Uri uri) {
InputStream is = null;
FileOutputStream fos = null;
try {
clearCropTempFiles();
String szType = mContext.getContentResolver().getType(uri);
// 2. 截取MIME类型后缀如从image/jpeg中提取jpeg【核心新增逻辑】
String fileSuffix = "";
if (szType != null && szType.contains("/")) {
// 分割字符串,取"/"后面的部分(如"image/jpeg" → 分割后取索引1的"jpeg"
fileSuffix = szType.split("/")[1];
// 调试日志:打印截取后的文件后缀
} else {
// 异常处理若类型为空或格式错误默认后缀设为jpeg保留原逻辑兼容性
fileSuffix = "jpeg";
}
String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis();
mCropSourceFile = new File(fCropCacheDir, newCropFileName + ".jpg");
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + ".jpg");
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
mCropSourceFile.createNewFile();
mCropResultFile.createNewFile();
// 1. 打开Uri输入流兼容content:///file:// 等多种Uri格式
is = mContext.getContentResolver().openInputStream(uri);
if (is == null) {
@@ -255,13 +269,13 @@ public class BackgroundSourceUtils {
fos.flush();
}
}
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());
previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath());
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
// 6. 解析成功日志(打印文件信息,便于问题排查)
LogUtils.d(TAG, "【选图解析】Uri解析成功");
LogUtils.d(TAG, "→ 原Uri" + uri.toString());
@@ -458,14 +472,6 @@ public class BackgroundSourceUtils {
return fBackgroundCompressDir.getAbsolutePath();
}
public File getCropSourceFile() {
return mCropSourceFile;
}
public File getCropResultFile() {
return mCropResultFile;
}
public String getFileProviderAuthority() {
return FILE_PROVIDER_AUTHORITY;
}
@@ -654,7 +660,7 @@ public class BackgroundSourceUtils {
* 新增:清理压缩图目录下的旧文件(避免残留,初始化时调用)
*/
void clearCropTempFiles() {
for(File file : fCropCacheDir.listFiles()){
for (File file : fCropCacheDir.listFiles()) {
clearOldFile(file, "旧裁剪缓存文件(" + file.getAbsolutePath() + ")");
}
mCropSourceFile = null;
@@ -881,6 +887,7 @@ public class BackgroundSourceUtils {
*/
public void compressQualityToRecivedPicture(Bitmap bitmap, String targetCompressPath) {
LogUtils.d(TAG, "【压缩启动】开始压缩图片指定路径Bitmap状态" + (bitmap != null && !bitmap.isRecycled()));
if (bitmap == null || bitmap.isRecycled()) {
ToastUtils.show("压缩失败:图片为空");
LogUtils.e(TAG, "【压缩失败】Bitmap为空或已回收");
@@ -890,48 +897,16 @@ public class BackgroundSourceUtils {
OutputStream outStream = null;
FileOutputStream fos = null;
try {
String scaledCompressFilePath = targetCompressPath;
// 兜底若传入路径为空生成BackgroundCrops目录下的临时压缩路径兼容异常场景
if (TextUtils.isEmpty(scaledCompressFilePath)) {
LogUtils.e(TAG, "【压缩异常】指定路径为空使用BackgroundCrops临时兜底路径");
// 强制在BackgroundCrops目录下生成临时路径不再用其他目录
String tempCompressName = "preview_compress_" + System.currentTimeMillis() + ".jpg";
scaledCompressFilePath = new File(fBackgroundCompressDir, tempCompressName).getAbsolutePath();
LogUtils.d(TAG, "【压缩兜底】BackgroundCrops临时路径" + scaledCompressFilePath);
} else {
// 强制校验传入路径必须在BackgroundCrops目录下否则重置确保统一存储
if (!scaledCompressFilePath.contains(fBackgroundCompressDir.getAbsolutePath())) {
LogUtils.w(TAG, "【压缩校验】传入路径不在BackgroundCrops目录自动重置");
String compressFileName = new File(scaledCompressFilePath).getName();
scaledCompressFilePath = new File(fBackgroundCompressDir, compressFileName).getAbsolutePath();
LogUtils.d(TAG, "【压缩校验】重置后路径:" + scaledCompressFilePath);
}
}
LogUtils.d(TAG, "【压缩配置】目标路径BackgroundCrops" + targetCompressPath + "Bitmap原始大小" + bitmap.getByteCount() / 1024 + "KB");
File compressFile = new File(scaledCompressFilePath);
LogUtils.d(TAG, "【压缩配置】目标路径BackgroundCrops" + scaledCompressFilePath + "Bitmap原始大小" + bitmap.getByteCount() / 1024 + "KB");
// 确保压缩图目录存在(实例化时已创建,此处二次确认)
File parentDir = compressFile.getParentFile();
if (parentDir == null) {
LogUtils.e(TAG, "【压缩异常】父目录为空,无法创建文件");
ToastUtils.show("压缩失败:路径无效");
return;
File targetCompressFile = new File(targetCompressPath);
if (targetCompressFile.exists()) {
targetCompressFile.delete();
}
if (!parentDir.exists()) {
parentDir.mkdirs();
FileUtils.copyFile(new File(""), parentDir);
LogUtils.d(TAG, "【压缩准备】BackgroundCrops目录已创建" + parentDir.getAbsolutePath());
}
// 清理旧的压缩文件(避免文件残留)
// if (compressFile.exists()) {
// clearOldFileByExternal(compressFile, "旧压缩文件BackgroundCrops");
// }
compressFile.createNewFile();
targetCompressFile.createNewFile();
// 写入压缩图质量80平衡清晰度和内存
fos = new FileOutputStream(compressFile);
fos = new FileOutputStream(targetCompressFile);
outStream = new BufferedOutputStream(fos);
boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream);
outStream.flush();
@@ -946,21 +921,13 @@ public class BackgroundSourceUtils {
}
}
LogUtils.d(TAG, "【压缩结果】" + (compressSuccess ? "成功" : "失败") + ",大小:" + compressFile.length() / 1024 + "KB路径" + scaledCompressFilePath);
LogUtils.d(TAG, "【压缩结果】" + (compressSuccess ? "成功" : "失败") + ",大小:" + targetCompressFile.length() / 1024 + "KB路径" + targetCompressFile);
// 关键修复压缩成功后强制同步路径到预览Bean双重保障避免时序错位
if (compressSuccess) {
BackgroundBean previewBean = getPreviewBackgroundBean();
if (previewBean != null) {
previewBean.setBackgroundScaledCompressFilePath(scaledCompressFilePath);
saveSettings(); // 持久化配置,多包名环境下配置隔离
LogUtils.d(TAG, "【压缩路径同步】已绑定到预览BeanBackgroundCrops" + scaledCompressFilePath);
} else {
LogUtils.e(TAG, "【压缩路径同步失败】预览Bean为空");
}
ToastUtils.show("图片压缩成功");
} else {
ToastUtils.show("图片压缩失败");
LogUtils.e(TAG, "【压缩失败】Bitmap.compress()返回false");
}
} catch (IOException e) {
LogUtils.e(TAG, "【压缩异常】IO错误" + e.getMessage(), e);