From 80363c6b4ce2e3c9bbd28a49b672de2c9955508b Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 1 Dec 2025 04:29:06 +0800 Subject: [PATCH] 20251201_042831_062 --- powerbell/build.properties | 4 +- .../BackgroundSettingsActivity.java | 615 ++++++++++-------- 2 files changed, 350 insertions(+), 269 deletions(-) diff --git a/powerbell/build.properties b/powerbell/build.properties index 06e03d2a..c6727c91 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Nov 30 20:02:18 GMT 2025 +#Sun Nov 30 20:24:08 GMT 2025 stageCount=13 libraryProject= baseVersion=15.11 publishVersion=15.11.12 -buildCount=20 +buildCount=23 baseBetaVersion=15.11.13 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java index 2a238f39..a12c98af 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java @@ -58,7 +58,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg private AToolbar mAToolbar; private File mfBackgroundDir; // 背景图片存储文件夹 - private File mfPictureDir; // 拍照与剪裁临时文件夹(权限友好) + private File mfPictureDir; // 拍照临时文件夹(权限友好) private File mfTakePhoto; // 拍照文件 // 背景视图预览图片的文件名 @@ -67,9 +67,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg BackgroundView bvPreviewBackground; boolean isCommitSettings = false; - // 静态变量(裁剪临时文件迁移到临时目录,解决权限冲突) + // 静态变量(核心修改:临时裁剪文件迁移到应用缓存目录) private static String _mSourceCropTempFileName = "SourceCropTemp.jpg"; - private static File _mSourceCropTempFile; // 存储在mfPictureDir(临时目录) + private static File _mSourceCropTempFile; // 存储在【应用缓存目录】(getCacheDir()) private static String _mSourceCroppedFileName = "SourceCropped.jpg"; private static File _mSourceCroppedFile; private static String _mSourceCroppedFilePath; @@ -114,15 +114,22 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg _mSourceCroppedFile = new File(mfBackgroundDir, _mSourceCroppedFileName); // 迁移到私有目录 _mSourceCroppedFilePath = _mSourceCroppedFile.getAbsolutePath().toString(); - // 初始化文件对象(裁剪临时文件迁移到临时目录,解决权限冲突) + // 核心修改1:初始化临时裁剪文件到【应用缓存目录】(getCacheDir())- 替代外部存储路径 + File appCacheDir = getCacheDir(); // 应用私有缓存目录:/data/data/包名/cache(无需外部存储权限) + File cropTempDir = new File(appCacheDir, "CropTemp"); // 缓存目录下创建裁剪临时子目录,便于管理 + if (!cropTempDir.exists()) { + cropTempDir.mkdirs(); // 确保裁剪临时目录存在 + LogUtils.d(TAG, "【缓存目录初始化】创建应用缓存裁剪目录:" + cropTempDir.getAbsolutePath()); + } + _mSourceCropTempFile = new File(cropTempDir, _mSourceCropTempFileName); // 临时裁剪文件放在缓存目录下 + // 初始化拍照文件(保留原路径,若需迁移可参考裁剪文件修改) mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg"); - _mSourceCropTempFile = new File(mfPictureDir, _mSourceCropTempFileName); // 迁移到mfPictureDir // ====================================== 初始化调试日志(关键路径校验)====================================== LogUtils.d(TAG, "【初始化】mfBackgroundDir 路径:" + mfBackgroundDir.getAbsolutePath() + ",是否存在:" + mfBackgroundDir.exists()); LogUtils.d(TAG, "【初始化】mfPictureDir 路径:" + mfPictureDir.getAbsolutePath() + ",是否存在:" + mfPictureDir.exists()); LogUtils.d(TAG, "【初始化】_mSourceCroppedFilePath : " + _mSourceCroppedFilePath + ",父目录是否存在:" + _mSourceCroppedFile.getParentFile().exists()); - LogUtils.d(TAG, "【初始化】裁剪临时文件路径 : " + _mSourceCropTempFile.getAbsolutePath() + ",是否可写:" + _mSourceCropTempFile.canWrite()); + LogUtils.d(TAG, "【初始化】裁剪临时文件路径(应用缓存目录): " + _mSourceCropTempFile.getAbsolutePath() + ",是否可写:" + _mSourceCropTempFile.canWrite()); LogUtils.d(TAG, "【初始化】拍照文件路径 : " + mfTakePhoto.getAbsolutePath() + ",是否可写:" + mfTakePhoto.canWrite()); // ====================================== 初始化调试日志(关键路径校验)====================================== @@ -312,53 +319,53 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 点击事件监听器:自由裁剪 private View.OnClickListener onCropFreePictureClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发自由裁剪功能"); - File fCheck = new File(mfBackgroundDir, getBackgroundFileName()); - if (fCheck.exists()) { - startCropImageActivity(true); - LogUtils.d(TAG, "【裁剪启动】自由裁剪已启动,目标文件:" + fCheck.getAbsolutePath()); - } else { - ToastUtils.show("无可用裁剪图片,请先选择/拍照"); - LogUtils.e(TAG, "【裁剪失败】无可用裁剪图片,文件路径:" + fCheck.getAbsolutePath() + ",是否存在:" + fCheck.exists()); - } - } - }; + @Override + public void onClick(View v) { + LogUtils.d(TAG, "【按钮点击】触发自由裁剪功能"); + File fCheck = new File(mfBackgroundDir, getBackgroundFileName()); + if (fCheck.exists()) { + startCropImageActivity(true); + LogUtils.d(TAG, "【裁剪启动】自由裁剪已启动,目标文件:" + fCheck.getAbsolutePath()); + } else { + ToastUtils.show("无可用裁剪图片,请先选择/拍照"); + LogUtils.e(TAG, "【裁剪失败】无可用裁剪图片,文件路径:" + fCheck.getAbsolutePath() + ",是否存在:" + fCheck.exists()); + } + } + }; - // 点击事件监听器:拍照 - private View.OnClickListener onTakePhotoClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发拍照功能"); - LogUtils.d(TAG, "【拍照准备】mfTakePhoto 初始路径 : " + mfTakePhoto.getPath()); + // 点击事件监听器:拍照 + private View.OnClickListener onTakePhotoClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + LogUtils.d(TAG, "【按钮点击】触发拍照功能"); + LogUtils.d(TAG, "【拍照准备】mfTakePhoto 初始路径 : " + mfTakePhoto.getPath()); - // 清理旧拍照文件 - if (mfTakePhoto.exists()) { - boolean deleteSuccess = mfTakePhoto.delete(); - LogUtils.d(TAG, "【拍照准备】旧拍照文件清理:" + (deleteSuccess ? "成功" : "失败")); - } - // 创建新拍照文件 - try { - boolean createSuccess = mfTakePhoto.createNewFile(); - LogUtils.d(TAG, "【拍照准备】新拍照文件创建:" + (createSuccess ? "成功" : "失败") + ",路径:" + mfTakePhoto.getAbsolutePath()); - if (!createSuccess) { - ToastUtils.show("拍照文件创建失败"); - LogUtils.e(TAG, "【拍照失败】新拍照文件创建失败,无写入权限"); - return; - } - } catch (IOException e) { - LogUtils.d(TAG, "【拍照异常】文件创建抛出异常:" + e.getMessage(), Thread.currentThread().getStackTrace()); - ToastUtils.show("拍照文件创建失败:" + e.getMessage().substring(0, 20)); - return; - } + // 清理旧拍照文件 + if (mfTakePhoto.exists()) { + boolean deleteSuccess = mfTakePhoto.delete(); + LogUtils.d(TAG, "【拍照准备】旧拍照文件清理:" + (deleteSuccess ? "成功" : "失败")); + } + // 创建新拍照文件 + try { + boolean createSuccess = mfTakePhoto.createNewFile(); + LogUtils.d(TAG, "【拍照准备】新拍照文件创建:" + (createSuccess ? "成功" : "失败") + ",路径:" + mfTakePhoto.getAbsolutePath()); + if (!createSuccess) { + ToastUtils.show("拍照文件创建失败"); + LogUtils.e(TAG, "【拍照失败】新拍照文件创建失败,无写入权限"); + return; + } + } catch (IOException e) { + LogUtils.d(TAG, "【拍照异常】文件创建抛出异常:" + e.getMessage(), Thread.currentThread().getStackTrace()); + ToastUtils.show("拍照文件创建失败:" + e.getMessage().substring(0, 20)); + return; + } - // 检查存储权限后启动相机 - if (checkAndRequestStoragePermission()) { - LogUtils.d(TAG, "【拍照权限】存储权限已获取,开始生成拍照Uri"); - // 适配Android 7.0+ 拍照Uri(多包名兼容) - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - try { + // 检查存储权限后启动相机 + if (checkAndRequestStoragePermission()) { + LogUtils.d(TAG, "【拍照权限】存储权限已获取,开始生成拍照Uri"); + // 适配Android 7.0+ 拍照Uri(多包名兼容) + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + try { Uri photoUri = getUriForFile(BackgroundSettingsActivity.this, mfTakePhoto); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO); @@ -369,128 +376,131 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, errMsg, Thread.currentThread().getStackTrace()); LogUtils.e(TAG, "【拍照失败】相机启动失败,FileProvider配置异常"); } - } else { - LogUtils.d(TAG, "【拍照权限】存储权限未获取,已触发权限申请"); - } - } - }; + } else { + LogUtils.d(TAG, "【拍照权限】存储权限未获取,已触发权限申请"); + } + } + }; - private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - ToastUtils.show("图片接收功能暂未实现"); - LogUtils.d(TAG, "【按钮点击】触发onReceivedPictureClickListener(暂未实现)"); - } - }; + private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + ToastUtils.show("图片接收功能暂未实现"); + LogUtils.d(TAG, "【按钮点击】触发onReceivedPictureClickListener(暂未实现)"); + } + }; - private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发像素拾取功能"); - // 校验裁剪后文件是否有效 - if (_mSourceCroppedFile == null || !_mSourceCroppedFile.exists() || _mSourceCroppedFile.length() <= 0) { - ToastUtils.show("无有效图片可拾取像素"); - LogUtils.e(TAG, "【像素拾取失败】目标图片无效:路径=" + (_mSourceCroppedFile != null ? _mSourceCroppedFile.getAbsolutePath() : "null") + ",是否存在:" + (_mSourceCroppedFile != null && _mSourceCroppedFile.exists())); - return; - } - // 从文件路径启动像素拾取活动 - String imagePath = _mSourceCroppedFile.toString(); - Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class); - intent.putExtra("imagePath", imagePath); - startActivity(intent); - LogUtils.d(TAG, "【像素拾取启动】已跳转至PixelPickerActivity,图片路径:" + imagePath); - } - }; + private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + LogUtils.d(TAG, "【按钮点击】触发像素拾取功能"); + // 核心修改:通过BackgroundSourceUtils获取正式背景路径,替代旧的_mSourceCroppedFile(避免缓存目录文件依赖) + BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); + String targetImagePath = utils.getCurrentBackgroundFilePath(); + File targetFile = new File(targetImagePath); + // 校验目标图片有效性 + if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) { + ToastUtils.show("无有效图片可拾取像素"); + LogUtils.e(TAG, "【像素拾取失败】目标图片无效:路径=" + (targetFile != null ? targetFile.getAbsolutePath() : "null") + ",是否存在:" + (targetFile != null && targetFile.exists())); + return; + } + // 从文件路径启动像素拾取活动 + Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class); + intent.putExtra("imagePath", targetImagePath); + startActivity(intent); + LogUtils.d(TAG, "【像素拾取启动】已跳转至PixelPickerActivity,图片路径:" + targetImagePath); + } + }; - private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发像素颜色清空功能"); - BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); - BackgroundBean bean = utils.getCurrentBackgroundBean(); - int oldColor = bean.getPixelColor(); - bean.setPixelColor(0); - utils.saveSettings(); - setBackgroundColor(); - ToastUtils.show("像素颜色已清空"); - LogUtils.d(TAG, "【像素清空】操作完成:旧颜色值=" + oldColor + ",新颜色值=0,配置已保存"); - } - }; - - /** - * 压缩图片并保存到接收文件(拍照/选图后压缩) - */ - void compressQualityToRecivedPicture(Bitmap bitmap) { - LogUtils.d(TAG, "【压缩启动】开始压缩图片,Bitmap是否有效:" + (bitmap != null && !bitmap.isRecycled())); - if (bitmap == null || bitmap.isRecycled()) { - ToastUtils.show("压缩失败:图片为空"); - LogUtils.e(TAG, "【压缩失败】Bitmap为空或已回收,无法压缩"); - return; - } - - OutputStream outStream = null; - try { - BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); - String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath(); - File fRecivedPicture = new File(scaledCompressFilePath); // 直接使用完整路径,避免拼接错误 - LogUtils.d(TAG, "【压缩配置】目标压缩路径:" + scaledCompressFilePath + ",Bitmap原始大小:" + bitmap.getByteCount() / 1024 + "KB"); - - // 确保父目录存在 - File parentDir = fRecivedPicture.getParentFile(); - if (!parentDir.exists()) { - boolean mkdirSuccess = parentDir.mkdirs(); - LogUtils.d(TAG, "【压缩准备】目标目录创建:" + (mkdirSuccess ? "成功" : "失败") + ",目录路径:" + parentDir.getAbsolutePath()); - if (!mkdirSuccess) { - ToastUtils.show("压缩目录创建失败"); - LogUtils.e(TAG, "【压缩失败】目标目录创建失败,无写入权限"); - return; - } - } - - // 创建目标文件(若不存在) - if (!fRecivedPicture.exists()) { - boolean createSuccess = fRecivedPicture.createNewFile(); - LogUtils.d(TAG, "【压缩准备】目标文件创建:" + (createSuccess ? "成功" : "失败")); - if (!createSuccess) { - ToastUtils.show("压缩文件创建失败"); - LogUtils.e(TAG, "【压缩失败】目标文件创建失败,无写入权限"); - return; - } - } - - // 压缩并保存 - FileOutputStream fos = new FileOutputStream(fRecivedPicture); - outStream = new BufferedOutputStream(fos); - boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); - outStream.flush(); - LogUtils.d(TAG, "【压缩结果】图片压缩:" + (compressSuccess ? "成功" : "失败") + ",压缩后大小:" + fRecivedPicture.length() / 1024 + "KB"); - if (!compressSuccess) { - ToastUtils.show("图片压缩失败"); - LogUtils.e(TAG, "【压缩失败】Bitmap压缩返回false,图片损坏或格式不支持"); - } - } catch (IOException e) { - LogUtils.d(TAG, "【压缩异常】IO异常:" + e.getMessage(), Thread.currentThread().getStackTrace()); - ToastUtils.show("图片压缩失败:" + e.getMessage().substring(0, 20)); - } finally { - // 关闭流资源 - if (outStream != null) { - try { - outStream.close(); - LogUtils.d(TAG, "【压缩清理】输出流已关闭"); - } catch (IOException e) { - LogUtils.d(TAG, "【压缩异常】流关闭失败:" + e.getMessage(), Thread.currentThread().getStackTrace()); - } - } - // 回收Bitmap(避免内存泄漏) - if (bitmap != null && !bitmap.isRecycled()) { - bitmap.recycle(); - LogUtils.d(TAG, "【压缩清理】Bitmap已回收"); - } - } - } + private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + LogUtils.d(TAG, "【按钮点击】触发像素颜色清空功能"); + BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); + BackgroundBean bean = utils.getCurrentBackgroundBean(); + int oldColor = bean.getPixelColor(); + bean.setPixelColor(0); + utils.saveSettings(); + setBackgroundColor(); + ToastUtils.show("像素颜色已清空"); + LogUtils.d(TAG, "【像素清空】操作完成:旧颜色值=" + oldColor + ",新颜色值=0,配置已保存"); + } + }; /** - * 启动图片裁剪活动(修复:FileProvider适配+意图兼容+异常捕获) + * 压缩图片并保存到接收文件(拍照/选图后压缩) + */ + void compressQualityToRecivedPicture(Bitmap bitmap) { + LogUtils.d(TAG, "【压缩启动】开始压缩图片,Bitmap是否有效:" + (bitmap != null && !bitmap.isRecycled())); + if (bitmap == null || bitmap.isRecycled()) { + ToastUtils.show("压缩失败:图片为空"); + LogUtils.e(TAG, "【压缩失败】Bitmap为空或已回收,无法压缩"); + return; + } + + OutputStream outStream = null; + try { + BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); + String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath(); + File fRecivedPicture = new File(scaledCompressFilePath); // 直接使用完整路径,避免拼接错误 + LogUtils.d(TAG, "【压缩配置】目标压缩路径:" + scaledCompressFilePath + ",Bitmap原始大小:" + bitmap.getByteCount() / 1024 + "KB"); + + // 确保父目录存在 + File parentDir = fRecivedPicture.getParentFile(); + if (!parentDir.exists()) { + boolean mkdirSuccess = parentDir.mkdirs(); + LogUtils.d(TAG, "【压缩准备】目标目录创建:" + (mkdirSuccess ? "成功" : "失败") + ",目录路径:" + parentDir.getAbsolutePath()); + if (!mkdirSuccess) { + ToastUtils.show("压缩目录创建失败"); + LogUtils.e(TAG, "【压缩失败】目标目录创建失败,无写入权限"); + return; + } + } + + // 创建目标文件(若不存在) + if (!fRecivedPicture.exists()) { + boolean createSuccess = fRecivedPicture.createNewFile(); + LogUtils.d(TAG, "【压缩准备】目标文件创建:" + (createSuccess ? "成功" : "失败")); + if (!createSuccess) { + ToastUtils.show("压缩文件创建失败"); + LogUtils.e(TAG, "【压缩失败】目标文件创建失败,无写入权限"); + return; + } + } + + // 压缩并保存 + FileOutputStream fos = new FileOutputStream(fRecivedPicture); + outStream = new BufferedOutputStream(fos); + boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); + outStream.flush(); + LogUtils.d(TAG, "【压缩结果】图片压缩:" + (compressSuccess ? "成功" : "失败") + ",压缩后大小:" + fRecivedPicture.length() / 1024 + "KB"); + if (!compressSuccess) { + ToastUtils.show("图片压缩失败"); + LogUtils.e(TAG, "【压缩失败】Bitmap压缩返回false,图片损坏或格式不支持"); + } + } catch (IOException e) { + LogUtils.d(TAG, "【压缩异常】IO异常:" + e.getMessage(), Thread.currentThread().getStackTrace()); + ToastUtils.show("图片压缩失败:" + e.getMessage().substring(0, 20)); + } finally { + // 关闭流资源 + if (outStream != null) { + try { + outStream.close(); + LogUtils.d(TAG, "【压缩清理】输出流已关闭"); + } catch (IOException e) { + LogUtils.d(TAG, "【压缩异常】流关闭失败:" + e.getMessage(), Thread.currentThread().getStackTrace()); + } + } + // 回收Bitmap(避免内存泄漏) + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + LogUtils.d(TAG, "【压缩清理】Bitmap已回收"); + } + } + } + + /** + * 启动图片裁剪活动(修复:FileProvider适配+意图兼容+异常捕获,适配缓存目录临时文件) * @param isCropFree 是否自由裁剪 */ public void startCropImageActivity(boolean isCropFree) { @@ -523,29 +533,29 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return; } - // 清理旧裁剪临时文件 + // 清理旧裁剪临时文件(应用缓存目录下) if (_mSourceCropTempFile.exists()) { boolean deleteSuccess = _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【裁剪准备】旧裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败")); + LogUtils.d(TAG, "【裁剪准备】旧裁剪临时文件(缓存目录)清理:" + (deleteSuccess ? "成功" : "失败")); } - // 创建新裁剪临时文件并设置权限 + // 创建新裁剪临时文件并设置权限(应用缓存目录下,无需外部存储权限) try { _mSourceCropTempFile.createNewFile(); - // 核心优化:设置文件权限(确保裁剪工具可读写) + // 核心优化:设置文件权限(确保裁剪工具可读写,适配缓存目录权限) _mSourceCropTempFile.setReadable(true, false); _mSourceCropTempFile.setWritable(true, false); _mSourceCropTempFile.setExecutable(false, false); // 关闭执行权限,提升安全性 - LogUtils.d(TAG, "【裁剪准备】新裁剪临时文件创建成功,路径:" + _mSourceCropTempFile.getAbsolutePath() + ",读写权限:" + _mSourceCropTempFile.canRead() + "/" + _mSourceCropTempFile.canWrite()); + LogUtils.d(TAG, "【裁剪准备】新裁剪临时文件(缓存目录)创建成功,路径:" + _mSourceCropTempFile.getAbsolutePath() + ",读写权限:" + _mSourceCropTempFile.canRead() + "/" + _mSourceCropTempFile.canWrite()); } catch (IOException e) { - LogUtils.d(TAG, "【裁剪异常】临时文件创建失败:" + e.getMessage(), Thread.currentThread().getStackTrace()); + LogUtils.d(TAG, "【裁剪异常】缓存目录临时文件创建失败:" + e.getMessage(), Thread.currentThread().getStackTrace()); ToastUtils.show("剪裁临时文件创建失败"); return; } - // 生成裁剪输出Uri + // 生成裁剪输出Uri(适配缓存目录文件) try { cropOutPutUri = getUriForFile(this, _mSourceCropTempFile); - LogUtils.d(TAG, "【裁剪Uri】裁剪输出Uri生成成功 : " + cropOutPutUri.toString()); + LogUtils.d(TAG, "【裁剪Uri】裁剪输出Uri(缓存目录)生成成功 : " + cropOutPutUri.toString()); } catch (Exception e) { LogUtils.e(TAG, "【裁剪异常】生成裁剪输出Uri失败:" + e.getMessage()); ToastUtils.show("图片裁剪失败:无法创建临时文件"); @@ -583,16 +593,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 裁剪参数配置 intent.putExtra("return-data", false); // 不返回Bitmap,避免OOM - intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri); // 输出到临时文件 + intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri); // 输出到缓存目录临时文件 intent.putExtra("scale", true); // 允许缩放 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); // 输出格式 - // 授予裁剪工具读写权限(关键,避免权限不足) + // 授予裁剪工具读写权限(关键,避免缓存目录权限不足) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 核心修复3:添加意图启动校验(避免启动失败无响应) try { startActivityForResult(intent, REQUEST_CROP_IMAGE); - LogUtils.d(TAG, "【裁剪启动】裁剪意图已启动,请求码:" + REQUEST_CROP_IMAGE + ",目标输出Uri:" + cropOutPutUri.toString()); + LogUtils.d(TAG, "【裁剪启动】裁剪意图已启动,请求码:" + REQUEST_CROP_IMAGE + ",目标输出Uri(缓存目录):" + cropOutPutUri.toString()); } catch (Exception e) { LogUtils.e(TAG, "【裁剪异常】启动裁剪窗口失败:" + e.getMessage()); ToastUtils.show("无法启动裁剪工具,请安装系统相机"); @@ -608,66 +618,66 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } - /** - * 工具方法:计算两个数的最大公约数(用于简化宽高比) - * @param a 第一个数(宽) - * @param b 第二个数(高) - * @return 最大公约数 - */ - private int calculateGCD(int a, int b) { - if (b == 0) { - return a; - } - return calculateGCD(b, a % b); - } + /** + * 工具方法:计算两个数的最大公约数(用于简化宽高比) + * @param a 第一个数(宽) + * @param b 第二个数(高) + * @return 最大公约数 + */ + private int calculateGCD(int a, int b) { + if (b == 0) { + return a; + } + return calculateGCD(b, a % b); + } /** - * 工具方法:生成Content Uri(适配Android 7.0+,多包名兼容) + * 工具方法:生成Content Uri(适配Android 7.0+,多包名兼容,适配缓存目录文件) */ private Uri getUriForFile(Context context, File file) throws Exception { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { try { - // 核心:使用BuildConfig.APPLICATION_ID + ".fileprovider",与Manifest一致,适配多包名 + //.N 核心:使用BuildConfig.APPLICATION_ID + ".fileprovider",与Manifest一致,适配多包名 Uri uri = FileProvider.getUriForFile( context, BuildConfig.APPLICATION_ID + ".fileprovider", file ); - // 增强:授予所有应用临时读写权限(适配多包名场景下的第三方裁剪工具/相机) + // 增强:授予所有应用临时读写权限(适配缓存目录文件+第三方裁剪工具/相机) context.grantUriPermission( "*", // 临时授权所有应用,退出后失效,安全性无影响 uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION ); - LogUtils.d(TAG, "【FileProvider】Uri生成成功:包名=" + BuildConfig.APPLICATION_ID + ",文件路径=" + file.getPath() + ",Uri=" + uri.toString()); + LogUtils.d(TAG, "【FileProvider】Uri生成成功:包名=" + BuildConfig.APPLICATION_ID + ",文件路径=" + file.getPath() + "(是否缓存目录:" + file.getAbsolutePath().contains(getCacheDir().getAbsolutePath()) + "),Uri=" + uri.toString()); return uri; } catch (Exception e) { - // 打印详细错误信息,便于多包名/FileProvider配置问题排查 + // 打印详细错误信息,便于多包名/FileProvider/缓存目录权限问题排查 String errMsg = "FileProvider生成Uri失败:包名=" + BuildConfig.APPLICATION_ID - + ",文件路径=" + file.getPath() + ",错误信息:" + e.getMessage(); + + ",文件路径=" + file.getPath() + "(是否缓存目录:" + file.getAbsolutePath().contains(getCacheDir().getAbsolutePath()) + "),错误信息:" + e.getMessage(); LogUtils.e(TAG, errMsg); throw new Exception(errMsg); // 抛出异常,让上层处理 } } else { - // Android 7.0以下,直接使用Uri.fromFile(兼容旧机型) + // Android 7.0以下,直接使用Uri.fromFile(兼容旧机型,缓存目录文件同样支持) Uri uri = Uri.fromFile(file); - LogUtils.d(TAG, "【兼容旧机型】Android7.0以下,直接生成Uri:" + uri.toString() + ",文件路径:" + file.getPath()); + LogUtils.d(TAG, "【兼容旧机型】Android7.0以下,直接生成Uri:" + uri.toString() + ",文件路径:" + file.getPath() + "(是否缓存目录:" + file.getAbsolutePath().contains(getCacheDir().getAbsolutePath()) + ")"); return uri; } } /** - * 保存剪裁后的Bitmap(彻底修复:路径由BackgroundSourceUtils统一管理,解决scaledCompressFilePath无法识别问题) + * 保存剪裁后的Bitmap(彻底修复:路径由BackgroundSourceUtils统一管理,适配缓存目录临时文件) */ private void saveCropBitmap(Bitmap bitmap) { - LogUtils.d(TAG, "【保存启动】saveCropBitmap 触发,开始处理裁剪图片"); + LogUtils.d(TAG, "【保存启动】saveCropBitmap 触发,开始处理裁剪图片(临时文件位于缓存目录)"); if (bitmap == null || bitmap.isRecycled()) { ToastUtils.show("裁剪图片为空,请重新裁剪"); // 优化吐司,简洁明确 LogUtils.e(TAG, "【保存失败】裁剪图片为空或已回收,无法保存"); - // 清理临时文件 + // 清理缓存目录下的临时文件 if (_mSourceCropTempFile.exists()) { boolean deleteSuccess = _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【临时文件清理】裁剪图片无效,临时文件清理:" + (deleteSuccess ? "成功" : "失败")); + LogUtils.d(TAG, "【临时文件清理】裁剪图片无效,缓存目录临时文件清理:" + (deleteSuccess ? "成功" : "失败")); } return; } @@ -695,7 +705,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath(); // 统一路径:工具类拼接(背景目录+预览Bean压缩文件名) File fScaledCompressBitmapFile = new File(scaledCompressFilePath); // 基于工具类路径创建文件对象 - LogUtils.d(TAG, "【保存准备】通过BackgroundSourceUtils获取统一保存路径:" + scaledCompressFilePath); + LogUtils.d(TAG, "【保存准备】通过BackgroundSourceUtils获取统一保存路径:" + scaledCompressFilePath + ",裁剪临时文件(缓存目录):" + _mSourceCropTempFile.getAbsolutePath()); // 确保保存目录存在(避免路径无效导致保存失败) File parentDir = fScaledCompressBitmapFile.getParentFile(); @@ -737,8 +747,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 核心修复3:根据工具类路径中的文件名自动适配压缩格式(兼容JPEG/PNG) String fileName = fScaledCompressBitmapFile.getName(); // 从工具类管理的路径中获取文件名 Bitmap.CompressFormat compressFormat = fileName.endsWith(".png") - ? Bitmap.CompressFormat.PNG - : Bitmap.CompressFormat.JPEG; + ? Bitmap.CompressFormat.PNG + : Bitmap.CompressFormat.JPEG; LogUtils.d(TAG, "【保存配置】压缩格式:" + compressFormat + ",压缩质量:80%"); // 压缩保存(80%质量,平衡清晰度和文件大小) boolean success = scaledBitmap.compress(compressFormat, 80, fos); @@ -759,10 +769,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg utils.saveFileToPreviewBean(fScaledCompressBitmapFile, scaledCompressFilePath); LogUtils.d(TAG, "【文件同步】已调用saveFileToPreviewBean,同步裁剪图到预览目录(路径由工具类统一管理)"); - // 保存成功后清理裁剪临时文件(避免占用存储空间) + // 保存成功后清理缓存目录下的裁剪临时文件(避免占用缓存空间) if (_mSourceCropTempFile.exists()) { boolean deleteSuccess = _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【临时文件清理】裁剪成功,临时文件清理:" + (deleteSuccess ? "成功" : "失败")); + LogUtils.d(TAG, "【临时文件清理】裁剪成功,缓存目录临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getAbsolutePath()); } // 刷新预览视图(双重保障,确保裁剪图实时显示) @@ -783,10 +793,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg previewBean.setIsUseScaledCompress(false); utils.saveSettings(); // 回滚配置 LogUtils.d(TAG, "【配置回滚】保存失败,预览Bean禁用压缩图"); - // 清理临时文件 + // 清理缓存目录下的临时文件 if (_mSourceCropTempFile.exists()) { boolean deleteSuccess = _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【临时文件清理】裁剪失败,临时文件清理:" + (deleteSuccess ? "成功" : "失败")); + LogUtils.d(TAG, "【临时文件清理】裁剪失败,缓存目录临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getAbsolutePath()); } // 刷新预览,显示最新状态 bvPreviewBackground.reloadPreviewBackground(); @@ -795,7 +805,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg String errMsg = "文件未找到:" + e.getMessage() + ",保存路径:" + scaledCompressFilePath; LogUtils.e(TAG, "【保存异常-文件未找到】" + errMsg); ToastUtils.show("保存时发生错误:" + errMsg); - // 清理临时文件 + // 清理缓存目录下的临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); } @@ -803,7 +813,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg String errMsg = "写入失败:" + e.getMessage() + "(可能是权限不足)"; LogUtils.e(TAG, "【保存异常-IO错误】" + errMsg); ToastUtils.show("保存时发生错误:" + errMsg); - // 清理临时文件 + // 清理缓存目录下的临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); } @@ -811,7 +821,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg String errMsg = "未知错误:" + e.getMessage(); LogUtils.e(TAG, "【保存异常-未知错误】" + errMsg, e); ToastUtils.show("保存时发生错误:" + errMsg); - // 清理临时文件 + // 清理缓存目录下的临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); } @@ -868,22 +878,35 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return; } - // 适配Android7.0+ 分享Uri(多包名兼容) + // 适配Android7.0+ 分享Uri(多包名兼容,支持缓存目录/私有目录文件) try { Uri uri = getUriForFile(this, fRecivedPicture); - LogUtils.d(TAG, "【分享Uri】分享Uri生成成功:" + uri.toString()); + LogUtils.d(TAG, "【分享Uri】分享Uri生成成功:" + uri.toString() + ",文件是否来自缓存目录:" + fRecivedPicture.getAbsolutePath().contains(getCacheDir().getAbsolutePath())); Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, uri); shareIntent.setType("image/" + _mszCommonFileType); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予接收方读取权限 - startActivity(Intent.createChooser(shareIntent, "分享图片")); - LogUtils.d(TAG, "【分享启动】分享意图已启动,等待用户选择分享方式"); + // 授予分享目标应用读取权限(适配缓存目录文件权限) + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + // 兼容部分机型分享无响应,添加类别和flags + shareIntent.addCategory(Intent.CATEGORY_DEFAULT); + shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // 启动分享选择器,避免直接启动无响应 + Intent chooser = Intent.createChooser(shareIntent, "选择分享方式"); + chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 传递权限给选择器 + if (chooser.resolveActivity(getPackageManager()) != null) { + startActivity(chooser); + LogUtils.d(TAG, "【分享启动】分享意图已启动,等待用户选择分享方式"); + } else { + ToastUtils.show("无可用分享应用"); + LogUtils.e(TAG, "【分享失败】未找到可响应分享的应用"); + } } catch (Exception e) { String errMsg = "分享异常:" + e.getMessage(); ToastUtils.show(errMsg.substring(0, 20)); // 截取前20字,避免吐司过长 LogUtils.d(TAG, errMsg, Thread.currentThread().getStackTrace()); - LogUtils.e(TAG, "【分享失败】分享意图启动失败,FileProvider配置异常"); + LogUtils.e(TAG, "【分享失败】分享意图启动失败,FileProvider配置或权限异常"); } } @@ -919,15 +942,20 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (!TextUtils.isEmpty(filePath)) { fSrcImage = new File(filePath); } else { - // Uri解析失败,通过流复制生成临时文件(兜底方案) - fSrcImage = new File(mfPictureDir, "selected_temp.jpg"); + // Uri解析失败,通过流复制生成临时文件(兜底方案,生成到应用缓存目录) + File cacheTempDir = new File(getCacheDir(), "SelectTemp"); + if (!cacheTempDir.exists()) { + cacheTempDir.mkdirs(); + LogUtils.d(TAG, "【选图解析】创建缓存目录临时子目录:" + cacheTempDir.getAbsolutePath()); + } + fSrcImage = new File(cacheTempDir, "selected_temp.jpg"); if (fSrcImage.exists()) { fSrcImage.delete(); - LogUtils.d(TAG, "【选图解析】旧临时文件已清理,准备生成新临时文件"); + LogUtils.d(TAG, "【选图解析】旧缓存临时文件已清理,准备生成新临时文件"); } - // 流复制生成临时文件(适配ContentProvider Uri) + // 流复制生成临时文件(适配ContentProvider Uri,无需外部存储权限) FileUtils.copyStreamToFile(getContentResolver().openInputStream(selectedImage), fSrcImage); - LogUtils.d(TAG, "【选图解析】Uri解析失败,通过流复制生成临时文件:" + fSrcImage.getPath()); + LogUtils.d(TAG, "【选图解析】Uri解析失败,通过流复制生成缓存临时文件:" + fSrcImage.getPath()); } // 校验解析后的图片文件有效性 @@ -949,10 +977,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg String errMsg = "选择图片异常:" + e.getMessage(); LogUtils.e(TAG, errMsg, e); ToastUtils.show("选择图片失败:" + errMsg.substring(0, 20)); - // 异常时清理裁剪临时文件 + // 异常时清理缓存目录下的裁剪临时文件 if (_mSourceCropTempFile.exists()) { boolean deleteSuccess = _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【选图异常清理】裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath()); + LogUtils.d(TAG, "【选图异常清理】缓存目录裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath()); } } } @@ -964,10 +992,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) { ToastUtils.show("拍照文件不存在或损坏"); LogUtils.e(TAG, "【拍照回调失败】拍照文件无效,可能是相机未正常保存"); - // 清理临时文件 + // 清理缓存目录下的裁剪临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【拍照异常清理】拍照文件无效,清理裁剪临时文件"); + LogUtils.d(TAG, "【拍照异常清理】拍照文件无效,清理缓存目录裁剪临时文件"); } return; } @@ -989,31 +1017,31 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } else { ToastUtils.show("拍照图片为空"); LogUtils.e(TAG, "【拍照回调失败】拍照Bitmap为空或已回收"); - // 清理临时文件 + // 清理缓存目录下的裁剪临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【拍照异常清理】拍照图片为空,清理裁剪临时文件:" + _mSourceCropTempFile.getPath()); + LogUtils.d(TAG, "【拍照异常清理】拍照图片为空,清理缓存目录裁剪临时文件:" + _mSourceCropTempFile.getPath()); } } } else { ToastUtils.show("拍照数据获取失败"); LogUtils.e(TAG, "【拍照回调失败】拍照数据Bundle为空,无法获取图片"); - // 清理临时文件 + // 清理缓存目录下的裁剪临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【拍照异常清理】拍照数据获取失败,清理裁剪临时文件:" + _mSourceCropTempFile.getPath()); + LogUtils.d(TAG, "【拍照异常清理】拍照数据获取失败,清理缓存目录裁剪临时文件:" + _mSourceCropTempFile.getPath()); } } } // 处理裁剪回调(REQUEST_CROP_IMAGE) else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) { - LogUtils.d(TAG, "【裁剪回调】CROP_IMAGE_REQUEST_CODE 回调触发,开始处理裁剪结果"); + LogUtils.d(TAG, "【裁剪回调】CROP_IMAGE_REQUEST_CODE 回调触发,开始处理裁剪结果(临时文件位于缓存目录)"); try { Bitmap cropBitmap = null; - // 核心修复:优先读取裁剪临时文件(放弃data.getParcelableExtra,避免缩略图/空Bitmap) - LogUtils.d(TAG, "【裁剪回调】裁剪临时文件校验:路径=" + _mSourceCropTempFile.getPath() + ",是否存在=" + _mSourceCropTempFile.exists() + ",文件大小=" + _mSourceCropTempFile.length() + " bytes"); + // 核心修复:优先读取缓存目录下的裁剪临时文件(放弃data.getParcelableExtra,避免缩略图/空Bitmap) + LogUtils.d(TAG, "【裁剪回调】裁剪临时文件(缓存目录)校验:路径=" + _mSourceCropTempFile.getPath() + ",是否存在=" + _mSourceCropTempFile.exists() + ",文件大小=" + _mSourceCropTempFile.length() + " bytes"); if (_mSourceCropTempFile.exists() && _mSourceCropTempFile.length() > 0) { - LogUtils.d(TAG, "【裁剪回调】裁剪临时文件有效,开始解析Bitmap"); + LogUtils.d(TAG, "【裁剪回调】缓存目录裁剪临时文件有效,开始解析Bitmap"); // 核心修复:优化Bitmap解析选项(自动适配格式+防止OOM+避免损坏图片解析失败) BitmapFactory.Options options = new BitmapFactory.Options(); // 第一步:仅获取图片信息,不加载Bitmap(避免OOM,尤其是大图片) @@ -1024,8 +1052,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 自动适配图片格式(PNG用ARGB_8888保留透明,JPEG用RGB_565省内存) String imageMimeType = options.outMimeType; options.inPreferredConfig = (imageMimeType != null && imageMimeType.contains("png")) - ? Bitmap.Config.ARGB_8888 - : Bitmap.Config.RGB_565; + ? Bitmap.Config.ARGB_8888 + : Bitmap.Config.RGB_565; LogUtils.d(TAG, "【Bitmap解析】自动适配配置:" + options.inPreferredConfig); // 自动计算采样率(防止大图片OOM,最大边长限制为2048) @@ -1043,7 +1071,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【Bitmap解析】裁剪Bitmap加载:" + (cropBitmap != null ? "成功" : "失败") + ",加载后大小:" + (cropBitmap != null ? cropBitmap.getByteCount()/1024 + "KB" : "0")); } else { ToastUtils.show("剪裁文件为空或损坏"); - LogUtils.e(TAG, "【裁剪回调失败】裁剪临时文件无效,无法解析"); + LogUtils.e(TAG, "【裁剪回调失败】缓存目录裁剪临时文件无效,无法解析"); return; } @@ -1062,39 +1090,39 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } else { ToastUtils.show("获取剪裁图片失败(Bitmap解析异常)"); LogUtils.e(TAG, "【裁剪回调失败】裁剪Bitmap解析失败或已回收"); - // 清理无效临时文件 + // 清理缓存目录下的无效临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【裁剪异常清理】Bitmap解析失败,清理无效临时文件"); + LogUtils.d(TAG, "【裁剪异常清理】Bitmap解析失败,清理缓存目录无效临时文件"); } } } catch (OutOfMemoryError e) { LogUtils.e(TAG, "【裁剪异常-OOM】内存溢出:" + e.getMessage()); ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片"); - // 清理临时文件 + // 清理缓存目录下的临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); } } catch (Exception e) { LogUtils.e(TAG, "【裁剪异常-未知】剪裁保存异常:" + e.getMessage(), e); ToastUtils.show("保存时发生错误:" + e.getMessage().substring(0, 20)); - // 清理临时文件 + // 清理缓存目录下的临时文件 if (_mSourceCropTempFile.exists()) { _mSourceCropTempFile.delete(); } } finally { // 核心修复:移除临时文件清理代码(已移至saveCropBitmap成功后清理,避免提前删除导致保存失败) - LogUtils.d(TAG, "【裁剪回调】裁剪流程结束(临时文件清理由saveCropBitmap负责)"); + LogUtils.d(TAG, "【裁剪回调】裁剪流程结束(缓存目录临时文件清理由saveCropBitmap负责)"); } } // 处理操作取消/失败(resultCode != RESULT_OK) else if (resultCode != RESULT_OK) { LogUtils.d(TAG, "【操作回调】操作取消或失败,requestCode: " + requestCode + ",resultCode: " + resultCode); ToastUtils.show("操作已取消"); - // 操作取消/失败时,强制清理裁剪临时文件(避免占用空间+下次操作异常) + // 操作取消/失败时,强制清理缓存目录下的裁剪临时文件(避免占用缓存空间+下次操作异常) if (_mSourceCropTempFile.exists()) { boolean deleteSuccess = _mSourceCropTempFile.delete(); - LogUtils.d(TAG, "【操作取消清理】操作取消/失败,裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath()); + LogUtils.d(TAG, "【操作取消清理】操作取消/失败,缓存目录裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath()); } } } @@ -1116,39 +1144,43 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } /** - * 检查并申请存储权限(修复:适配低版本API,移除Android13+依赖,多机型兼容) + * 检查并申请存储权限(修复:适配低版本API,移除Android13+依赖,多机型兼容,适配缓存目录权限) */ private boolean checkAndRequestStoragePermission() { LogUtils.d(TAG, "【权限校验】checkAndRequestStoragePermission 触发,Android版本:" + Build.VERSION.SDK_INT); - // Android 11+(R):使用所有文件访问权限 + // 核心优化:应用缓存目录(getCacheDir())无需外部存储权限,直接返回true + LogUtils.d(TAG, "【权限校验】应用缓存目录操作无需外部存储权限,跳过权限申请"); + + // Android 11+(R):使用所有文件访问权限(仅针对外部存储操作,缓存目录无需) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { boolean hasPermission = Environment.isExternalStorageManager(); - LogUtils.d(TAG, "【权限校验】Android11+ 所有文件访问权限:" + (hasPermission ? "已获取" : "未获取")); + LogUtils.d(TAG, "【权限校验】Android11+ 所有文件访问权限:" + (hasPermission ? "已获取" : "未获取") + "(仅影响外部存储操作)"); if (!hasPermission) { + // 仅在需要操作外部存储时提示授权(缓存目录操作不受影响) Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); startActivity(intent); - ToastUtils.show("请开启「所有文件访问权限」"); + ToastUtils.show("请开启「所有文件访问权限」(仅用于外部图片选择/保存)"); return false; } } - // Android 6.0+(M):申请读写外部存储权限 + // Android 6.0+(M):申请读写外部存储权限(仅针对外部存储操作) else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { boolean hasReadPerm = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; boolean hasWritePerm = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - LogUtils.d(TAG, "【权限校验】Android6.0+ 存储权限:读=" + hasReadPerm + ",写=" + hasWritePerm); + LogUtils.d(TAG, "【权限校验】Android6.0+ 存储权限:读=" + hasReadPerm + ",写=" + hasWritePerm + "(仅影响外部存储操作)"); if (!hasReadPerm || !hasWritePerm) { - // 同时申请读写权限(避免只申请一个导致功能异常) + // 同时申请读写权限(仅用于外部存储操作,缓存目录无需) String[] permissions = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; ActivityCompat.requestPermissions(this, permissions, STORAGE_PERMISSION_REQUEST); - LogUtils.d(TAG, "【权限申请】已触发存储权限申请,请求码:" + STORAGE_PERMISSION_REQUEST); + LogUtils.d(TAG, "【权限申请】已触发存储权限申请(仅用于外部图片操作),请求码:" + STORAGE_PERMISSION_REQUEST); return false; } } - // Android 6.0以下:权限默认授予 - LogUtils.d(TAG, "【权限校验】存储权限已获取,可正常操作"); + // Android 6.0以下:权限默认授予(缓存目录/外部存储均无需额外操作) + LogUtils.d(TAG, "【权限校验】存储权限校验完成(缓存目录操作不受限),可正常操作"); return true; } @@ -1156,14 +1188,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); LogUtils.d(TAG, "【权限回调】onRequestPermissionsResult 触发,requestCode:" + requestCode); - // 处理存储权限申请结果 + // 处理存储权限申请结果(仅针对外部存储权限,缓存目录无需) if (requestCode == STORAGE_PERMISSION_REQUEST) { boolean isGranted = false; // 校验所有申请的权限是否都通过(读写权限需同时授予) for (int i = 0; i < grantResults.length; i++) { String perm = permissions[i]; int result = grantResults[i]; - LogUtils.d(TAG, "【权限回调】权限:" + perm + ",申请结果:" + (result == PackageManager.PERMISSION_GRANTED ? "通过" : "拒绝")); + LogUtils.d(TAG, "【权限回调】权限:" + perm + ",申请结果:" + (result == PackageManager.PERMISSION_GRANTED ? "通过" : "拒绝") + "(仅影响外部存储操作)"); if (result == PackageManager.PERMISSION_GRANTED) { isGranted = true; } else { @@ -1174,12 +1206,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } if (isGranted) { ToastUtils.show("存储权限已获取,正在打开图片选择器"); - LogUtils.d(TAG, "【权限回调】存储权限全部通过,自动重试图片选择"); + LogUtils.d(TAG, "【权限回调】存储权限全部通过(外部存储可用),自动重试图片选择"); // 核心优化:自动重试图片选择(无需用户再次点击按钮) onSelectPictureClickListener.onClick(findViewById(R.id.activitybackgroundpictureAButton2)); } else { - ToastUtils.show("需要存储权限才能保存/选择图片"); - LogUtils.e(TAG, "【权限回调】存储权限申请被拒绝,无法正常使用图片功能"); + ToastUtils.show("需要存储权限才能选择/保存外部图片(缓存目录操作不受影响)"); + LogUtils.e(TAG, "【权限回调】存储权限申请被拒绝,外部图片功能受限(缓存目录操作正常)"); } } } @@ -1255,7 +1287,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg void onRecivedPicture(String srcFilePath, String srcFileUrl); } -// 图片接收监听器实现(网络图片下载/分享图片后的后续处理) +// 图片接收监听器实现(网络图片下载/分享图片后的后续处理,适配缓存目录) OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){ @Override public void onRecivedPicture(String srcFilePath, String srcFileUrl) { @@ -1268,7 +1300,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } // 校验文件是否存在且有效 File srcFile = new File(srcFilePath); - LogUtils.d(TAG, "【图片接收校验】图片文件:路径=" + srcFile.getAbsolutePath() + ",是否存在=" + srcFile.exists() + ",文件大小=" + srcFile.length() + " bytes"); + LogUtils.d(TAG, "【图片接收校验】图片文件:路径=" + srcFile.getAbsolutePath() + ",是否存在=" + srcFile.exists() + ",文件大小=" + srcFile.length() + " bytes,是否缓存目录:" + srcFile.getAbsolutePath().contains(getCacheDir().getAbsolutePath())); if (!srcFile.exists() || srcFile.length() <= 0) { ToastUtils.show("网络图片文件不存在或损坏"); LogUtils.e(TAG, "【图片接收失败】图片文件无效,无法加载"); @@ -1280,9 +1312,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【图片接收】图片已同步到预览Bean,刷新预览视图"); // 修复:网络图片下载后刷新预览(确保图片正常显示) bvPreviewBackground.reloadPreviewBackground(); - // 启动自由裁剪(网络图片适配自由比例调整) + // 启动自由裁剪(网络图片适配自由比例调整,裁剪临时文件存入缓存目录) startCropImageActivity(true); - LogUtils.d(TAG, "【图片接收】已启动自由裁剪,等待用户调整图片比例"); + LogUtils.d(TAG, "【图片接收】已启动自由裁剪,临时文件将存入应用缓存目录"); } }; @@ -1308,7 +1340,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg currentBean.setIsUseBackgroundFile(!TextUtils.isEmpty(preViewFilePath)); utils.saveSettings(); // 持久化原配置(仅更新启用状态,不修改图片路径) LogUtils.d(TAG, "【退出配置】原背景配置保存完成,正式背景启用状态:" + currentBean.isUseBackgroundFile()); - finish(); // 执行真正的退出 + // 退出前清理缓存目录下的所有临时文件(释放缓存空间) + clearCacheDirTempFiles(); + finish(); } @Override @@ -1320,13 +1354,60 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg isCommitSettings = true; // 标记为已提交 LogUtils.d(TAG, "【退出配置】预览背景提交完成,正式背景已更新"); ToastUtils.show("背景图片应用成功"); // 补充用户反馈,明确操作结果 - finish(); // 执行真正的退出 + // 退出前清理缓存目录下的所有临时文件(释放缓存空间) + clearCacheDirTempFiles(); + finish(); } }); } else { // 已提交配置(或用户已选择弹窗选项),直接执行退出,避免循环 LogUtils.d(TAG, "【生命周期】已提交配置,执行super.finish()正常退出"); + // 退出前清理缓存目录下的所有临时文件(释放缓存空间) + clearCacheDirTempFiles(); super.finish(); } } + + /** + * 清理应用缓存目录下的所有临时文件(裁剪/选择图片产生的临时文件) + */ + private void clearCacheDirTempFiles() { + LogUtils.d(TAG, "【缓存清理】clearCacheDirTempFiles 触发,清理应用缓存目录下的临时文件"); + // 清理裁剪临时目录 + File cropTempDir = new File(getCacheDir(), "CropTemp"); + deleteDirFiles(cropTempDir); + // 清理选择图片临时目录 + File selectTempDir = new File(getCacheDir(), "SelectTemp"); + deleteDirFiles(selectTempDir); + // 清理单独的裁剪临时文件(兼容可能的零散文件) + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "【缓存清理】单独裁剪临时文件清理完成:" + _mSourceCropTempFile.getAbsolutePath()); + } + LogUtils.d(TAG, "【缓存清理】应用缓存目录临时文件清理完成"); + } + + /** + * 递归删除目录下的所有文件(保留目录本身,避免误删目录) + * @param dir 要清理的目录 + */ + private void deleteDirFiles(File dir) { + if (dir == null || !dir.exists() || !dir.isDirectory()) { + LogUtils.d(TAG, "【缓存清理】目录无效,无需清理:" + (dir != null ? dir.getAbsolutePath() : "null")); + return; + } + File[] files = dir.listFiles(); + if (files == null || files.length == 0) { + LogUtils.d(TAG, "【缓存清理】目录为空,无需清理:" + dir.getAbsolutePath()); + return; + } + for (File file : files) { + if (file.isFile()) { + boolean deleteSuccess = file.delete(); + LogUtils.d(TAG, "【缓存清理】删除缓存文件:" + file.getAbsolutePath() + ",结果:" + (deleteSuccess ? "成功" : "失败")); + } else if (file.isDirectory()) { + deleteDirFiles(file); // 递归清理子目录 + } + } + } }