diff --git a/powerbell/build.properties b/powerbell/build.properties index b28509e6..c1f1327c 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Dec 01 18:48:19 GMT 2025 +#Mon Dec 01 21:56:35 GMT 2025 stageCount=13 libraryProject= baseVersion=15.11 publishVersion=15.11.12 -buildCount=103 +buildCount=106 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 656d6de9..29d990f6 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 @@ -75,8 +75,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg private BackgroundView bvPreviewBackground; // 拍照临时文件(仅拍照用,路径由工具类间接管理) private File mfTakePhoto; - // 新增:选图临时文件(独立管理,与拍照临时文件隔离) - private File mSelectTempFile; // 配置标记(是否提交设置) boolean isCommitSettings = false; // 预览图片信息(用于退出确认) @@ -117,8 +115,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg selectTempDir.mkdirs(); LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath()); } - mSelectTempFile = new File(selectTempDir, "selected_temp_" + System.currentTimeMillis() + ".jpg"); - + // 初始化UI及数据 initToolbar(); initClickListeners(); @@ -240,12 +237,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg @Override public void onClick(View v) { LogUtils.d(TAG, "【按钮点击】触发选择图片功能"); - // 新增:选图前清理旧的选图临时文件(避免残留文件干扰) - if (mSelectTempFile != null && mSelectTempFile.exists()) { - boolean deleteSuccess = mSelectTempFile.delete(); - LogUtils.d(TAG, "【选图准备】旧选图临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + mSelectTempFile.getAbsolutePath()); - } - + // 调用权限工具类校验存储权限 if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) { LogUtils.d(TAG, "【选图权限】存储权限已获取,开始查找图片选择意图"); @@ -285,7 +277,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg Intent chooser = Intent.createChooser(validIntent, "选择图片"); chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); startActivityForResult(chooser, REQUEST_SELECT_PICTURE); - LogUtils.d(TAG, "【选图意图】找到有效意图,已启动图片选择(临时文件路径:" + mSelectTempFile.getAbsolutePath() + ")"); + LogUtils.d(TAG, "【选图意图】找到有效意图,已启动图片选择。"); } else { LogUtils.d(TAG, "【选图意图】未找到有效图片选择应用,提示用户安装"); runOnUiThread(new Runnable() { @@ -489,24 +481,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg public void startCropImageActivity(boolean isCropFree) { LogUtils.d(TAG, "【裁剪启动】startCropImageActivity 触发,自由裁剪:" + isCropFree); BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); - previewBean.setIsUseBackgroundScaledCompressFile(true); + previewBean.setIsUseBackgroundScaledCompressFile(false); mBgSourceUtils.saveSettings(); + bvPreviewBackground.reloadPreviewBackground(); - // 1. 预览图片有效性校验(直接使用预览图原图,不复制) - String previewFilePath = mBgSourceUtils.getPreviewBackgroundFilePath(); - if (TextUtils.isEmpty(previewFilePath)) { - ToastUtils.show("预览图片路径为空"); - LogUtils.e(TAG, "【裁剪失败】预览图片路径为空"); - return; - } - File previewFile = new File(previewFilePath); // 裁剪原图:直接使用预览图文件 - LogUtils.d(TAG, "【裁剪优化】直接使用预览图原图启动裁剪(不复制):" + previewFile.getAbsolutePath()); - LogUtils.d(TAG, "【裁剪校验】预览图片状态:是否存在=" + previewFile.exists() + ",是否为文件=" + previewFile.isFile() + ",大小=" + (previewFile.exists() ? previewFile.length() : 0) + "bytes"); - if (!previewFile.exists() || !previewFile.isFile() || previewFile.length() <= 100) { - ToastUtils.show("预览图片不存在或损坏"); - LogUtils.e(TAG, "【裁剪失败】预览图片无效"); - return; - } + File previewFile = mBgSourceUtils.getCropSourceFile(); // 裁剪缓存图片 // 2. 生成裁剪输入Uri(强化MIUI权限授予,直接用原图Uri) Uri inputUri = null; @@ -518,13 +497,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } catch (Exception e) { LogUtils.e(TAG, "【裁剪异常】生成输入Uri失败:" + e.getMessage(), e); ToastUtils.show("图片裁剪失败:无法获取图片权限"); - mBgSourceUtils.clearCropTempFiles(); return; } // 3. 【核心优化】直接使用预览图原图作为裁剪输入,删除原工具类创建裁剪路径的复制逻辑 // 仅创建裁剪结果临时文件(用于接收裁剪输出,不涉及原图复制) - File cropResultTempFile = mBgSourceUtils.createCropResultTempFile(); + File cropResultTempFile = mBgSourceUtils.getCropResultFile(); if (cropResultTempFile == null) { ToastUtils.show("裁剪路径创建失败,请重试"); return; @@ -609,13 +587,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg startActivityForResult(chooser, REQUEST_CROP_IMAGE); } else { ToastUtils.show("无可用裁剪工具,请安装系统相机"); - mBgSourceUtils.clearCropTempFiles(); } } } catch (Exception e) { LogUtils.e(TAG, "【裁剪异常】启动裁剪工具失败:" + e.getMessage(), e); ToastUtils.show("无法启动裁剪工具"); - mBgSourceUtils.clearCropTempFiles(); } } @@ -656,7 +632,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【保存启动】开始保存裁剪图片(仅更新预览Bean,不影响正式Bean)"); if (bitmap == null || bitmap.isRecycled()) { ToastUtils.show("裁剪图片为空"); - mBgSourceUtils.clearCropTempFiles(); + //mBgSourceUtils.clearCropTempFiles(); return; } @@ -678,9 +654,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg BufferedOutputStream bos = null; try { // 1. 清理旧的裁剪预览图(避免文件残留) - if (cropSaveFile.exists()) { - mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "旧裁剪预览图"); - } +// if (cropSaveFile.exists()) { +// mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "旧裁剪预览图"); +// } // 确保父目录存在(兼容Android 10+分区存储,多包名环境下目录适配) File parentDir = cropSaveFile.getParentFile(); if (parentDir != null && !parentDir.exists()) { @@ -737,10 +713,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } catch (IOException e) { LogUtils.e(TAG, "【裁剪保存失败】IO异常:" + e.getMessage(), e); ToastUtils.show("裁剪图片保存失败"); - // 异常时清理无效文件 - if (cropSaveFile.exists()) { - mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "异常裁剪图"); - } } finally { // 资源回收(避免内存泄漏,兼容低版本Android) if (bos != null) { @@ -766,9 +738,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } - // 5. 清理裁剪临时文件(保留原有逻辑,避免临时文件堆积) - mBgSourceUtils.clearCropTempFiles(); - LogUtils.d(TAG, "【裁剪保存】流程结束,临时文件已清理"); + LogUtils.d(TAG, "【裁剪保存】流程结束。"); } /** @@ -883,7 +853,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } catch (Exception e) { LogUtils.e(TAG, "【回调异常】onActivityResult 全局异常:" + e.getMessage(), e); ToastUtils.show("操作失败,请重试"); - mBgSourceUtils.clearCropTempFiles(); // 异常时清理临时文件,避免残留 + //mBgSourceUtils.clearCropTempFiles(); // 异常时清理临时文件,避免残留 //clearSelectTempFile(); // 新增:异常时清理选图临时文件 } } @@ -913,7 +883,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 校验拍照文件有效性 if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) { ToastUtils.show("拍照文件不存在或损坏"); - mBgSourceUtils.clearCropTempFiles(); + //mBgSourceUtils.clearCropTempFiles(); return; } @@ -923,7 +893,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mBgSourceUtils.compressQualityToRecivedPicture(photoBitmap); } else { ToastUtils.show("拍照图片为空"); - mBgSourceUtils.clearCropTempFiles(); + //mBgSourceUtils.clearCropTempFiles(); return; } @@ -988,7 +958,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg */ private void handleCropImageResult(int requestCode, int resultCode, Intent data) { // 从工具类获取裁剪临时文件(唯一入口) - File cropTempFile = mBgSourceUtils.getCropTempFile(); + File cropTempFile = mBgSourceUtils.getCropSourceFile(); boolean isFileExist = cropTempFile != null && cropTempFile.exists(); boolean isFileReadable = isFileExist ? cropTempFile.canRead() : false; long fileSize = isFileExist ? cropTempFile.length() : 0; @@ -1002,7 +972,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (resultCode == 0 && !isCropSuccess) { LogUtils.d(TAG, "【裁剪回调】MIUI 裁剪工具已取消"); ToastUtils.show("裁剪已取消"); - mBgSourceUtils.clearCropTempFiles(); + //mBgSourceUtils.clearCropTempFiles(); return; } @@ -1010,7 +980,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (isFileExist && fileSize == 0) { LogUtils.e(TAG, "【裁剪失败】裁剪文件为空(MIUI适配问题)"); ToastUtils.show("裁剪失败,请尝试选择「系统相机」裁剪或更换图片"); - mBgSourceUtils.clearCropTempFiles(); + //mBgSourceUtils.clearCropTempFiles(); return; } @@ -1024,7 +994,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } else { ToastUtils.show("获取裁剪图片失败"); LogUtils.e(TAG, "【裁剪回调失败】Bitmap解析异常"); - mBgSourceUtils.clearCropTempFiles(); + //mBgSourceUtils.clearCropTempFiles(); } } else { // 其他失败场景 @@ -1153,8 +1123,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg Uri selectedImage = data.getData(); if (selectedImage == null) { ToastUtils.show("选择的图片Uri为空"); - //clearSelectTempFile(); // 修复:取消时清理选图临时文件 - mBgSourceUtils.clearCropTempFiles(); return; } LogUtils.d(TAG, "【选图回调】选择图片Uri : " + selectedImage.toString()); @@ -1163,36 +1131,23 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg grantPersistableUriPermission(selectedImage); // 关键修复1:解析Uri为文件(强制使用独立的选图临时文件,避免路径混淆) - boolean parseSuccess = parseUriToSelectTempFile(selectedImage); - if (!parseSuccess || mSelectTempFile == null || !mSelectTempFile.exists() || mSelectTempFile.length() <= 0) { + mBgSourceUtils.createCropFileProviderPath(selectedImage); + if (mBgSourceUtils.getCropSourceFile() == null || !mBgSourceUtils.getCropSourceFile().exists() || mBgSourceUtils.getCropSourceFile().length() <= 0) { ToastUtils.show("选择的图片文件无效或无法读取"); - //clearSelectTempFile(); - mBgSourceUtils.clearCropTempFiles(); return; } - LogUtils.d(TAG, "【选图解析】选图临时文件生成成功:" + mSelectTempFile.getAbsolutePath() + ",大小:" + mSelectTempFile.length() + "bytes"); + LogUtils.d(TAG, "【选图解析】选图临时文件生成成功:" + mBgSourceUtils.getCropSourceFile().getAbsolutePath() + ",大小:" + mBgSourceUtils.getCropSourceFile().length() + "bytes"); // 关键修复2:同步预览Bean(强制绑定「原图路径+压缩图路径」双路径,避免路径错位) BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); if (previewBean == null) { ToastUtils.show("预览配置初始化失败"); - //clearSelectTempFile(); - mBgSourceUtils.clearCropTempFiles(); return; } - // 绑定原图路径(选图临时文件路径) - previewBean.setBackgroundFilePath(mSelectTempFile.getAbsolutePath()); + // 关键:强制绑定压缩图路径到BackgroundCrops目录(统一存储,避免路径错乱) - String targetCompressPath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath(); - File fPreviewBackgroundScaledCompressFile = new File(targetCompressPath); - File fBackgroundCompressDir =fPreviewBackgroundScaledCompressFile.getParentFile(); - if (TextUtils.isEmpty(targetCompressPath) || !targetCompressPath.contains(fBackgroundCompressDir.getAbsolutePath())) { - // 兜底生成BackgroundCrops目录下的压缩路径 - targetCompressPath = new File(fBackgroundCompressDir, "SelectCompress_" + System.currentTimeMillis() + ".jpg").getAbsolutePath(); - LogUtils.d(TAG, "【选图压缩】压缩路径为空/非法,生成兜底路径:" + targetCompressPath); - } - previewBean.setBackgroundScaledCompressFilePath(targetCompressPath); - mBgSourceUtils.saveSettings(); // 立即持久化,避免旋转/退后台丢失路径 + String targetCompressPath = mBgSourceUtils.getCropResultFile().getAbsolutePath(); + LogUtils.d(TAG, "【选图同步】预览Bean双路径绑定完成:"); LogUtils.d(TAG, "→ 原图路径(选图临时文件):" + previewBean.getBackgroundFilePath()); LogUtils.d(TAG, "→ 压缩图路径(BackgroundCrops):" + previewBean.getBackgroundScaledCompressFilePath()); @@ -1204,7 +1159,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 优化:使用采样率加载Bitmap,避免OOM(原有直接decodeFile易导致大图片崩溃) BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(mSelectTempFile.getAbsolutePath(), options); + BitmapFactory.decodeFile(mBgSourceUtils.getCropSourceFile().getAbsolutePath(), options); // 计算采样率(最大尺寸限制为2048px) int maxSize = 2048; int sampleSize = 1; @@ -1214,7 +1169,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg options.inJustDecodeBounds = false; options.inSampleSize = sampleSize; options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存 - selectBitmap = BitmapFactory.decodeFile(mSelectTempFile.getAbsolutePath(), options); + selectBitmap = BitmapFactory.decodeFile(mBgSourceUtils.getCropSourceFile().getAbsolutePath(), options); } catch (Exception e) { LogUtils.e(TAG, "【选图压缩】Bitmap加载失败:" + e.getMessage(), e); } @@ -1225,8 +1180,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } else { ToastUtils.show("选图后压缩图生成失败"); LogUtils.e(TAG, "【选图压缩失败】无法解析选图文件为Bitmap"); - //clearSelectTempFile(); - mBgSourceUtils.clearCropTempFiles(); return; } @@ -1235,8 +1188,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (!compressFile.exists() || compressFile.length() <= 0) { ToastUtils.show("压缩图生成失败,请重新选择图片"); LogUtils.e(TAG, "【选图压缩失败】压缩图文件无效:" + targetCompressPath); - //clearSelectTempFile(); - mBgSourceUtils.clearCropTempFiles(); return; } @@ -1258,10 +1209,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg * 处理所有操作取消/失败(统一清理+提示) */ private void handleOperationCancelOrFail() { + initPreviewBeanFromFormal(); + bvPreviewBackground.reloadPreviewBackground(); LogUtils.d(TAG, "【操作回调】操作取消或失败"); ToastUtils.show("操作已取消"); - //clearSelectTempFile(); // 新增:清理选图临时文件 - mBgSourceUtils.clearCropTempFiles(); } // ======================================== 权限回调(转发给工具类处理) ======================================== @@ -1287,92 +1238,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } - /** - * 解析选图Uri到独立的选图临时文件(修复:避免与拍照临时文件混淆,强化可读性校验) - */ - private boolean parseUriToSelectTempFile(Uri uri) { - // 清理旧的选图临时文件(避免残留文件干扰解析结果) - if (mSelectTempFile != null && mSelectTempFile.exists()) { - boolean deleteSuccess = mSelectTempFile.delete(); - LogUtils.d(TAG, "【选图解析】旧选图临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + mSelectTempFile.getAbsolutePath()); - } - - InputStream is = null; - FileOutputStream fos = null; - try { - // 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式) - is = getContentResolver().openInputStream(uri); - if (is == null) { - LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString()); - return false; - } - - // 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources) - fos = new FileOutputStream(mSelectTempFile); - byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,平衡读写性能与内存占用 - int readLen; // 每次读取的字节长度 - - // 3. 流复制(Java7 标准while循环,避免Java8+语法) - while ((readLen = is.read(buffer)) != -1) { - fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充 - } - - // 4. 强制同步写入磁盘(解决Android 10+ 异步写入导致的文件无效问题) - fos.flush(); - if (fos != null) { - try { - fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存 - } catch (IOException e) { - LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,用flush()兜底:" + e.getMessage()); - fos.flush(); - } - } - - // 5. 双重校验临时文件有效性(解决Android14+ canRead()假阳性问题) - if (!isFileActuallyReadable(mSelectTempFile)) { - LogUtils.e(TAG, "【选图解析】临时文件存在但无实际读取权限,路径:" + mSelectTempFile.getAbsolutePath()); - if (mSelectTempFile.exists()) { - mSelectTempFile.delete(); // 删除不可读文件,避免后续流程异常 - } - return false; - } - - // 6. 解析成功日志(打印文件信息,便于问题排查) - LogUtils.d(TAG, "【选图解析】Uri解析成功!"); - LogUtils.d(TAG, "→ 原Uri:" + uri.toString()); - LogUtils.d(TAG, "→ 目标临时文件:" + mSelectTempFile.getAbsolutePath()); - LogUtils.d(TAG, "→ 文件大小:" + mSelectTempFile.length() + " bytes"); - return true; - - } catch (Exception e) { - // 捕获所有异常(IO异常/空指针等),避免崩溃 - LogUtils.e(TAG, "【选图解析】流复制异常:" + e.getMessage(), e); - // 异常时清理无效文件,防止残留 - if (mSelectTempFile != null && mSelectTempFile.exists()) { - mSelectTempFile.delete(); - LogUtils.d(TAG, "【选图解析】异常时清理无效临时文件:" + mSelectTempFile.getAbsolutePath()); - } - return false; - - } finally { - // 7. 手动关闭流资源(Java7 标准写法,避免内存泄漏) - if (is != null) { - try { - is.close(); - } catch (IOException e) { - LogUtils.e(TAG, "【选图解析】输入流关闭失败:" + e.getMessage()); - } - } - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - LogUtils.e(TAG, "【选图解析】输出流关闭失败:" + e.getMessage()); - } - } - } - } - /** * 精准校验文件是否实际可读取(核心用途:解决Android14+ 中 File.canRead() 假阳性问题,避免“文件存在但无法读取”) * 逻辑:不依赖canRead(),而是通过实际打开文件流读取1字节,验证是否真的有读取权限(最可靠的校验方式) diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java index a6f11063..7fccf4db 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java @@ -3,12 +3,12 @@ package cc.winboll.studio.powerbell.utils; import android.content.Context; import android.graphics.Bitmap; import android.media.ExifInterface; +import android.net.Uri; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.ToastUtils; -import cc.winboll.studio.powerbell.App; import cc.winboll.studio.powerbell.BuildConfig; import cc.winboll.studio.powerbell.model.BackgroundBean; import java.io.BufferedOutputStream; @@ -18,6 +18,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.UUID; /** * @Author ZhanGSKen @@ -28,14 +29,15 @@ public class BackgroundSourceUtils { public static final String TAG = "BackgroundSourceUtils"; // 裁剪相关常量(统一定义,避免硬编码) - private static final String CROP_TEMP_DIR_NAME = "cache"; // 裁剪缓存目录(基础目录下) + private static final String CROP_CACHE_DIR_NAME = "cache"; // 裁剪缓存目录(基础目录下) private static final String CROP_TEMP_FILE_NAME = "SourceCropTemp.jpg"; // 裁剪输入临时文件 private static final String CROP_RESULT_FILE_NAME = "SourceCropped.jpg"; // 裁剪输出结果文件 public static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider"; // 多包名兼容 // 图片操作基础目录(核心:系统公共图片目录) private static final String PICTURE_BASE_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + File.separator + "PowerBell"; // 新增:压缩图统一存储目录(图片基础目录下/BackgroundCrops) - private static final String COMPRESS_BASE_DIR_NAME = "BackgroundCrops"; + private static final String SOURCE_DIR_NAME = "BackgroundSource"; + private static final String COMPRESS_DIR_NAME = "BackgroundCompress"; // 1. 静态实例加volatile,禁止指令重排,保证可见性(双重校验锁单例核心) private static volatile BackgroundSourceUtils sInstance; @@ -48,15 +50,15 @@ public class BackgroundSourceUtils { // 2. 统一文件目录(分两类:图片目录→系统公共目录,JSON目录→应用外置存储) // 图片操作目录(系统公共目录:/storage/emulated/0/Pictures/PowerBell/) private File fPictureBaseDir; // 图片基础目录 - private File fPictureCacheDir; // 裁剪缓存目录(基础目录下/cache) + private File fCropCacheDir; // 裁剪缓存目录(基础目录下/cache) private File fBackgroundSourceDir; // 图片存储目录(基础目录下,存储正式/预览原图) private File fBackgroundCompressDir; // 新增:压缩图统一存储目录(基础目录下/BackgroundCrops) // JSON配置目录(原应用外置存储目录,不改变) private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundSourceUtils) private File fModelDir; // 模型文件目录(存储JSON配置) // 裁剪文件(统一放入图片基础目录下的cache) - private File cropTempFile; // 裁剪临时文件(fPictureCacheDir下) - private File cropResultFile; // 裁剪结果文件(fBackgroundSourceDir下) + private File mCropSourceFile; // 裁剪临时文件(fCropCacheDir下) + private File mCropResultFile; // 裁剪临时文件(fCropCacheDir下) // 3. 私有构造器(加防反射逻辑+初始化所有目录/文件) private BackgroundSourceUtils(Context context) { @@ -107,25 +109,26 @@ public class BackgroundSourceUtils { // 1. 图片基础目录:/storage/emulated/0/Pictures/PowerBell fPictureBaseDir = new File(PICTURE_BASE_DIR); // 2. 图片存储目录:基础目录下(存储正式/预览原图) - fBackgroundSourceDir = new File(fPictureBaseDir, "BackgroundSource"); + fBackgroundSourceDir = new File(fPictureBaseDir, SOURCE_DIR_NAME); // 3. 裁剪缓存目录:基础目录下/cache(所有裁剪操作在此目录) - fPictureCacheDir = new File(fPictureBaseDir, CROP_TEMP_DIR_NAME); + fCropCacheDir = new File(fPictureBaseDir, CROP_CACHE_DIR_NAME); // 4. 新增:压缩图统一存储目录(基础目录下/BackgroundCrops,所有压缩图放这里) - fBackgroundCompressDir = new File(fPictureBaseDir, COMPRESS_BASE_DIR_NAME); + fBackgroundCompressDir = new File(fPictureBaseDir, COMPRESS_DIR_NAME); // 5. 强制创建所有图片目录(带二次校验+降级兜底) - createDirWithPermission(fPictureBaseDir, "图片基础目录(/Pictures/PowerBell)", true); - createDirWithPermission(fBackgroundSourceDir, "图片存储目录(基础目录下)", true); - createDirWithPermission(fPictureCacheDir, "裁剪缓存目录(基础目录/cache)", true); - createDirWithPermission(fBackgroundCompressDir, "压缩图统一存储目录(基础目录/BackgroundCrops)", true); + createDirWithPermission(fPictureBaseDir, "图片基础目录(" + PICTURE_BASE_DIR + ")"); + createDirWithPermission(fBackgroundSourceDir, "图片存储目录(基础目录下/" + SOURCE_DIR_NAME + ")"); + createDirWithPermission(fCropCacheDir, "裁剪缓存目录(基础目录/" + CROP_CACHE_DIR_NAME + ")"); + createDirWithPermission(fBackgroundCompressDir, "裁剪压缩图存储目录(基础目录/" + COMPRESS_DIR_NAME + ")"); // 6. 目录创建后最终校验(确保所有目录已就绪) validatePictureDirs(); LogUtils.d(TAG, "【图片目录初始化】完成:" + "基础目录=" + fPictureBaseDir.getAbsolutePath() + - ",裁剪缓存目录=" + fPictureCacheDir.getAbsolutePath() + - ",压缩图目录=" + fBackgroundCompressDir.getAbsolutePath()); + "图片存储目录=" + fBackgroundSourceDir.getAbsolutePath() + + ",裁剪缓存目录=" + fCropCacheDir.getAbsolutePath() + + ",裁剪压缩图存储目录=" + fBackgroundCompressDir.getAbsolutePath()); } /** @@ -137,12 +140,12 @@ public class BackgroundSourceUtils { fUtilsDir = mContext.getExternalFilesDir(TAG); if (fUtilsDir == null) { LogUtils.e(TAG, "【JSON目录】应用外置存储不可用,切换到应用内部缓存目录"); - fUtilsDir = mContext.getCacheDir(); + fUtilsDir = mContext.getDataDir(); } // 2. 模型文件目录(存储JSON配置) fModelDir = new File(fUtilsDir, "ModelDir"); // 强制创建JSON目录(带二次校验+降级兜底) - createDirWithPermission(fModelDir, "JSON配置目录(应用外置存储)", true); + createDirWithPermission(fModelDir, "JSON配置目录(应用外置存储)"); // 3. 初始化JSON文件对象(两份独立文件,对应两份Bean实例) currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json"); @@ -155,114 +158,20 @@ public class BackgroundSourceUtils { * 【核心强化】创建目录并设置权限(适配系统公共目录/Pictures/PowerBell,确保实例化时目录就绪) * @param dir 要创建的目录 * @param dirDesc 目录描述(用于日志打印) - * @param needFallback 是否需要降级兜底(实例化初期必须为true,确保目录可用) */ - private void createDirWithPermission(File dir, String dirDesc, boolean needFallback) { + private void createDirWithPermission(File dir, String dirDesc) { if (dir == null) { LogUtils.e(TAG, "【文件管理】创建目录失败:目录对象为null(描述:" + dirDesc + ")"); return; } // 第一步:主动检测并创建目录(递归创建所有父目录) - boolean isCreated = true; - if (!dir.exists()) { + if (!dir.exists()) { LogUtils.d(TAG, "【文件管理】" + dirDesc + "不存在,开始创建:" + dir.getAbsolutePath()); - isCreated = dir.mkdirs(); // 递归创建所有父目录 + dir.mkdirs(); // 递归创建所有父目录 } else { LogUtils.d(TAG, "【文件管理】" + dirDesc + "已存在:" + dir.getAbsolutePath()); } - - // 第二步:创建后二次校验(确保目录真的存在且是目录) - if (!dir.exists() || !dir.isDirectory()) { - LogUtils.w(TAG, "【文件管理】" + dirDesc + "创建/校验失败,路径:" + dir.getAbsolutePath()); - // 第三步:需要降级时,自动切换到应用内部缓存目录(兜底保障) - if (needFallback) { - File fallbackDir = getFallbackDir(dirDesc); - if (fallbackDir != null) { - LogUtils.w(TAG, "【文件管理】" + dirDesc + "降级到备选目录:" + fallbackDir.getAbsolutePath()); - // 更新目录引用为备选目录(确保后续业务调用使用有效目录) - updateDirReference(dir, fallbackDir); - // 强制创建备选目录 - if (!fallbackDir.exists()) { - fallbackDir.mkdirs(); - } - // 二次校验备选目录 - if (fallbackDir.exists() && fallbackDir.isDirectory()) { - LogUtils.d(TAG, "【文件管理】" + dirDesc + "备选目录创建成功:" + fallbackDir.getAbsolutePath()); - } else { - LogUtils.e(TAG, "【文件管理】" + dirDesc + "备选目录创建失败,功能可能异常!"); - } - } - } - return; - } - - // 第四步:强制设置目录权限(递归设置,确保系统公共目录所有层级可读写) - setDirPermissionsRecursively(dir); - // 第五步:校验目录实际可写性(解决Android14+ canWrite()假阳性问题) - if (!isDirActuallyWritable(dir)) { - LogUtils.w(TAG, "【文件管理】" + dirDesc + "存在但不可写,尝试重新设置权限"); - setDirPermissionsRecursively(dir); - // 再次校验仍失败,降级兜底 - if (needFallback && !isDirActuallyWritable(dir)) { - File fallbackDir = getFallbackDir(dirDesc); - if (fallbackDir != null) { - LogUtils.w(TAG, "【文件管理】" + dirDesc + "不可写,降级到备选目录:" + fallbackDir.getAbsolutePath()); - updateDirReference(dir, fallbackDir); - fallbackDir.mkdirs(); - setDirPermissionsRecursively(fallbackDir); - } - } - } - - LogUtils.d(TAG, "【文件管理】" + dirDesc + "创建+权限设置完成:路径=" + dir.getAbsolutePath() + ",可写=" + dir.canWrite()); - } - - /** - * 【新增】获取目录降级备选目录(实例化初期目录创建失败时兜底) - * 优先使用应用内部缓存目录(无需额外权限,兼容性最好) - */ - private File getFallbackDir(String dirDesc) { - File cacheDir = mContext.getCacheDir(); - if (cacheDir == null) { - LogUtils.e(TAG, "【文件管理】应用内部缓存目录不可用,无法降级:" + dirDesc); - return null; - } - // 根据目录类型创建对应备选目录(保持目录结构一致) - if (dirDesc.contains("图片基础目录")) { - return new File(cacheDir, "PowerBell"); - } else if (dirDesc.contains("图片存储目录")) { - return new File(cacheDir, "PowerBell/BackgroundSource"); - } else if (dirDesc.contains("裁剪缓存目录")) { - return new File(cacheDir, "PowerBell/cache"); - } else if (dirDesc.contains("压缩图统一存储目录")) { - return new File(cacheDir, "PowerBell/BackgroundCrops"); - } else if (dirDesc.contains("JSON配置目录")) { - return new File(cacheDir, "BackgroundSourceUtils/ModelDir"); - } else { - return new File(cacheDir, "FallbackDir"); - } - } - - /** - * 【新增】更新目录引用(目录创建失败降级时,同步更新全局目录变量) - */ - private void updateDirReference(File oldDir, File newDir) { - if (oldDir == null || newDir == null) { - return; - } - // 匹配全局目录变量,更新为备选目录 - if (oldDir.equals(fPictureBaseDir)) { - fPictureBaseDir = newDir; - } else if (oldDir.equals(fBackgroundSourceDir)) { - fBackgroundSourceDir = newDir; - } else if (oldDir.equals(fPictureCacheDir)) { - fPictureCacheDir = newDir; - } else if (oldDir.equals(fBackgroundCompressDir)) { - fBackgroundCompressDir = newDir; - } else if (oldDir.equals(fModelDir)) { - fModelDir = newDir; - } } /** @@ -279,8 +188,8 @@ public class BackgroundSourceUtils { LogUtils.e(TAG, "【图片目录校验】图片存储目录未就绪:" + fBackgroundSourceDir.getAbsolutePath()); allReady = false; } - if (!fPictureCacheDir.exists() || !fPictureCacheDir.isDirectory()) { - LogUtils.e(TAG, "【图片目录校验】裁剪缓存目录未就绪:" + fPictureCacheDir.getAbsolutePath()); + if (!fCropCacheDir.exists() || !fCropCacheDir.isDirectory()) { + LogUtils.e(TAG, "【图片目录校验】裁剪缓存目录未就绪:" + fCropCacheDir.getAbsolutePath()); allReady = false; } if (!fBackgroundCompressDir.exists() || !fBackgroundCompressDir.isDirectory()) { @@ -298,156 +207,138 @@ public class BackgroundSourceUtils { * 初始化所有文件(裁剪文件→图片缓存目录,结果文件→图片存储目录) */ private void initAllFiles() { - // 1. 裁剪临时文件(图片基础目录/cache下,系统裁剪可读写) - cropTempFile = new File(fPictureCacheDir, CROP_TEMP_FILE_NAME); - // 2. 裁剪结果文件(图片存储目录下,最终保存的裁剪图) - cropResultFile = new File(fBackgroundSourceDir, CROP_RESULT_FILE_NAME); + // 1. 裁剪临时文件 + mCropSourceFile = new File(fCropCacheDir, CROP_TEMP_FILE_NAME); + // 2. 裁剪结果文件 + //cropResultFile = new File(fCropCacheDir, CROP_RESULT_FILE_NAME); - // 3. 初始化时清理旧文件(复用FileUtils简化清理逻辑) - clearOldFile(cropTempFile, "旧裁剪临时文件(/Pictures/PowerBell/cache)"); - clearOldFile(cropResultFile, "旧裁剪结果文件(/Pictures/PowerBell/BackgroundSource)"); - // 新增:清理压缩图目录下的旧文件(避免残留) - clearOldCompressFiles(); - - LogUtils.d(TAG, "【文件初始化】完成:裁剪临时文件=" + cropTempFile.getAbsolutePath() + ",裁剪结果文件=" + cropResultFile.getAbsolutePath()); + // 新增:清理压缩图目录下的旧文件(避免残留) + clearCropTempFiles(); + LogUtils.d(TAG, "【文件初始化】完成。"); } + + + public boolean createCropFileProviderPath(Uri uri) { + InputStream is = null; + FileOutputStream fos = null; + + try { + clearCropTempFiles(); + String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis(); + mCropSourceFile = new File(fCropCacheDir, newCropFileName + ".jpg"); + mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + ".jpg"); + + // 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式) + is = mContext.getContentResolver().openInputStream(uri); + if (is == null) { + LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString()); + return false; + } + + // 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources) + fos = new FileOutputStream(mCropSourceFile); + byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,平衡读写性能与内存占用 + int readLen; // 每次读取的字节长度 + + // 3. 流复制(Java7 标准while循环,避免Java8+语法) + while ((readLen = is.read(buffer)) != -1) { + fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充 + } + + // 4. 强制同步写入磁盘(解决Android 10+ 异步写入导致的文件无效问题) + fos.flush(); + if (fos != null) { + try { + fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存 + } catch (IOException e) { + LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,用flush()兜底:" + e.getMessage()); + 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()); + LogUtils.d(TAG, "→ 目标临时文件:" + mCropSourceFile.getAbsolutePath()); + LogUtils.d(TAG, "→ 目标临时文件大小:" + mCropSourceFile.length() + " bytes"); + LogUtils.d(TAG, "→ 目标剪裁临时文件:" + mCropResultFile.getAbsolutePath()); + LogUtils.d(TAG, "→ 目标剪裁临时文件大小:" + mCropResultFile.length() + " bytes"); + return true; + + } catch (Exception e) { + // 捕获所有异常(IO异常/空指针等),避免崩溃 + LogUtils.e(TAG, "【选图解析】流复制异常:" + e.getMessage(), e); + // 异常时清理无效文件,防止残留 + clearCropTempFiles(); + return false; + + } finally { + // 7. 手动关闭流资源(Java7 标准写法,避免内存泄漏) + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LogUtils.e(TAG, "【选图解析】输入流关闭失败:" + e.getMessage()); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + LogUtils.e(TAG, "【选图解析】输出流关闭失败:" + e.getMessage()); + } + } + } + } /** * 核心优化函数:带原图参数的裁剪路径创建(优先使用原图,不复制) * 替代原逻辑中"复制原图到BackgroundSource再裁剪"的流程 */ - public File createCropFileProviderPath(File originalImageFile) { - final String TAG = "BackgroundSourceUtils"; - Log.d(TAG, "【裁剪优化】createCropFileProviderPath(原图) 触发,优先使用原图路径"); - - // 核心逻辑1:直接使用裁剪前的原图(不复制),仅校验合法性和权限 - if (originalImageFile != null && originalImageFile.exists() - && originalImageFile.isFile() && originalImageFile.length() > 0) { - // 校验原图目录是否可写(系统裁剪工具需读写权限) - if (isDirectoryWritable(originalImageFile.getParentFile())) { - // 强制开放原图权限(解决跨进程访问问题) - setFileReadWritePermission(originalImageFile); - Log.d(TAG, "【裁剪优化】直接使用原图启动裁剪(无复制):" + originalImageFile.getAbsolutePath()); - return originalImageFile; // 直接返回原图,不做任何复制 - } else { - Log.w(TAG, "【裁剪优化】原图目录不可写,切换到临时文件兜底"); - } - } else { - Log.w(TAG, "【裁剪优化】原图无效(空/不存在/0大小),切换到临时文件兜底"); - } - - // 兜底逻辑(仅原图不可用时触发,避免多复制):使用应用缓存目录(不涉及BackgroundSource) - File cacheDir = mContext.getCacheDir(); // 应用内部缓存,无需额外权限 - if (isDirectoryWritable(cacheDir)) { - try { - File cropTempFile = new File(cacheDir, "crop_temp_" + System.currentTimeMillis() + ".jpg"); - // 清理旧临时文件(避免堆积) - if (cropTempFile.exists()) { - cropTempFile.delete(); - } - cropTempFile.createNewFile(); - setFileReadWritePermission(cropTempFile); - Log.d(TAG, "【裁剪优化】原图不可用,使用缓存临时文件:" + cropTempFile.getAbsolutePath()); - return cropTempFile; - } catch (IOException e) { - Log.e(TAG, "【裁剪优化】创建缓存临时文件失败:" + e.getMessage(), e); - } - } - - // 终极兜底:公共图片缓存目录(极端情况) - File publicCacheDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Cache"); - if (isDirectoryWritable(publicCacheDir)) { - try { - if (!publicCacheDir.exists()) { - publicCacheDir.mkdirs(); - } - File cropTempFile = new File(publicCacheDir, "crop_temp_" + System.currentTimeMillis() + ".jpg"); - if (cropTempFile.exists()) { - cropTempFile.delete(); - } - cropTempFile.createNewFile(); - setFileReadWritePermission(cropTempFile); - Log.w(TAG, "【裁剪优化】应用缓存不可用,使用公共图片缓存:" + cropTempFile.getAbsolutePath()); - return cropTempFile; - } catch (IOException e) { - Log.e(TAG, "【裁剪优化】创建公共临时文件失败:" + e.getMessage(), e); - } - } - - Log.e(TAG, "【裁剪优化】所有裁剪路径创建失败,裁剪功能不可用"); - return null; - } - - /** - * 【新增辅助方法】创建裁剪结果临时文件(仅用于接收裁剪输出,不涉及原图复制) - * 避免使用工具类的createCropFileProviderPath(原方法可能存在复制逻辑) - */ - public File createCropResultTempFile() { - // 优先使用应用缓存目录(无需额外权限,避免存储权限问题) - File cacheDir = mContext.getCacheDir(); - if (isDirectoryWritable(cacheDir)) { - try { - String tempFileName = "crop_result_" + System.currentTimeMillis() + ".jpg"; - File cropTempFile = new File(cacheDir, tempFileName); - // 清理旧临时文件(避免堆积) - if (cropTempFile.exists()) { - cropTempFile.delete(); - } - cropTempFile.createNewFile(); - // 开放读写权限(供系统裁剪工具访问) - setFileReadWritePermission(cropTempFile); - LogUtils.d(TAG, "【裁剪优化】裁剪结果临时文件创建成功:" + cropTempFile.getAbsolutePath()); - return cropTempFile; - } catch (IOException e) { - LogUtils.e(TAG, "【裁剪优化】创建缓存临时文件失败:" + e.getMessage(), e); - } - } - - // 兜底:应用外部临时目录 - File externalTempDir = new File(App.getTempDirPath()); - if (isDirectoryWritable(externalTempDir)) { - try { - String tempFileName = "crop_result_" + System.currentTimeMillis() + ".jpg"; - File cropTempFile = new File(externalTempDir, tempFileName); - if (cropTempFile.exists()) { - cropTempFile.delete(); - } - cropTempFile.createNewFile(); - setFileReadWritePermission(cropTempFile); - LogUtils.w(TAG, "【裁剪优化】应用缓存不可用,使用外部临时目录:" + cropTempFile.getAbsolutePath()); - return cropTempFile; - } catch (IOException e) { - LogUtils.e(TAG, "【裁剪优化】创建外部临时文件失败:" + e.getMessage(), e); - } - } - - LogUtils.e(TAG, "【裁剪优化】裁剪结果临时文件创建失败"); - return null; - } - - /** - * 兼容旧调用的无参方法(避免影响其他代码) - */ - public File createCropFileProviderPath() { - return createCropFileProviderPath(null); // 无原图时走兜底逻辑 - } +// File createFileProviderPath(File originalImageFile) { +// Log.d(TAG, "【裁剪优化】createCropFileProviderPath(原图) 触发,优先使用原图路径"); +// +// // 核心逻辑1:直接使用裁剪前的原图(不复制),仅校验合法性和权限 +// if (originalImageFile != null && originalImageFile.exists() +// && originalImageFile.isFile() && originalImageFile.length() > 0) { +// // 校验原图目录是否可写(系统裁剪工具需读写权限) +// if (isDirectoryWritable(originalImageFile.getParentFile())) { +// // 强制开放原图权限(解决跨进程访问问题) +// //setFileReadWritePermission(originalImageFile); +// Log.d(TAG, "【裁剪优化】直接使用原图启动裁剪(无复制):" + originalImageFile.getAbsolutePath()); +// return originalImageFile; // 直接返回原图,不做任何复制 +// } else { +// Log.w(TAG, "【裁剪优化】原图目录不可写,切换到临时文件兜底"); +// } +// } else { +// Log.w(TAG, "【裁剪优化】原图无效(空/不存在/0大小),切换到临时文件兜底"); +// } +// Log.e(TAG, "【裁剪优化】所有裁剪路径创建失败,裁剪功能不可用"); +// return null; +// } // 辅助方法:校验目录是否可写(新增,保障裁剪权限) - private boolean isDirectoryWritable(File dir) { - if (dir == null) return false; - // 目录存在且可写,或目录不存在但能创建 - return (dir.exists() && dir.isDirectory() && dir.canWrite()) - || (!dir.exists() && dir.mkdirs()); - } +// private boolean isDirectoryWritable(File dir) { +// if (dir == null) return false; +// // 目录存在且可写,或目录不存在但能创建 +// return (dir.exists() && dir.isDirectory() && dir.canWrite()) +// || (!dir.exists() && dir.mkdirs()); +// } // 辅助方法:设置文件读写权限(新增,解决系统裁剪跨进程访问) - private void setFileReadWritePermission(File file) { - if (file == null || !file.exists()) return; - // 开放读写权限(兼容Android 10+) - file.setReadable(true, false); - file.setWritable(true, false); - file.setExecutable(true, false); - } +// private void setFileReadWritePermission(File file) { +// if (file == null || !file.exists()) return; +// // 开放读写权限(兼容Android 10+) +// file.setReadable(true, false); +// file.setWritable(true, false); +// file.setExecutable(true, false); +// } /** * 加载背景图片配置数据(核心:确保current/preview是两份独立的BackgroundBean实例) @@ -567,12 +458,12 @@ public class BackgroundSourceUtils { return fBackgroundCompressDir.getAbsolutePath(); } - public File getCropTempFile() { - return cropTempFile; + public File getCropSourceFile() { + return mCropSourceFile; } public File getCropResultFile() { - return cropResultFile; + return mCropResultFile; } public String getFileProviderAuthority() { @@ -680,69 +571,69 @@ public class BackgroundSourceUtils { * 工具方法:递归设置目录及子目录/文件的读写权限(适配系统公共目录/Pictures/PowerBell) * @param dir 要设置权限的目录 */ - private void setDirPermissionsRecursively(File dir) { - if (dir == null || !dir.exists()) { - String dirPath = (dir != null) ? dir.getAbsolutePath() : "null"; - LogUtils.d(TAG, "【权限管理】目录无效,无需设置权限:" + dirPath); - return; - } - try { - // 设置目录权限(允许所有用户读写,系统裁剪应用/预览功能必需) - dir.setReadable(true, false); - dir.setWritable(true, false); - dir.setExecutable(false, false); - - LogUtils.d(TAG, "【权限管理】目录权限设置完成:路径=" + dir.getAbsolutePath() + ",可写=" + dir.canWrite() + ",可读=" + dir.canRead()); - - // 递归处理子目录和文件(Java7 普通for循环,兼容语法) - File[] files = dir.listFiles(); - if (files != null && files.length > 0) { - for (int i = 0; i < files.length; i++) { - File file = files[i]; - if (file.isDirectory()) { - setDirPermissionsRecursively(file); - } else { - // 设置文件权限(与目录一致,确保可读写) - file.setReadable(true, false); - file.setWritable(true, false); - file.setExecutable(false, false); - // 裁剪/压缩/预览相关文件单独打印日志 - if (file.getName().contains(CROP_TEMP_FILE_NAME) || - file.getName().contains(CROP_RESULT_FILE_NAME) || - file.getName().startsWith("ScaledCompress_")) { - LogUtils.d(TAG, "【权限管理】关键文件权限设置:文件名=" + file.getName() + ",可写=" + file.canWrite()); - } - } - } - } - } catch (SecurityException e) { - LogUtils.e(TAG, "【权限管理】设置目录权限失败(系统禁止):" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e); - ToastUtils.show("目录权限设置失败,请授予应用存储权限"); - } catch (Exception e) { - LogUtils.e(TAG, "【权限管理】设置目录权限异常:" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e); - } - } +// private void setDirPermissionsRecursively(File dir) { +// if (dir == null || !dir.exists()) { +// String dirPath = (dir != null) ? dir.getAbsolutePath() : "null"; +// LogUtils.d(TAG, "【权限管理】目录无效,无需设置权限:" + dirPath); +// return; +// } +// try { +// // 设置目录权限(允许所有用户读写,系统裁剪应用/预览功能必需) +// dir.setReadable(true, false); +// dir.setWritable(true, false); +// dir.setExecutable(false, false); +// +// LogUtils.d(TAG, "【权限管理】目录权限设置完成:路径=" + dir.getAbsolutePath() + ",可写=" + dir.canWrite() + ",可读=" + dir.canRead()); +// +// // 递归处理子目录和文件(Java7 普通for循环,兼容语法) +// File[] files = dir.listFiles(); +// if (files != null && files.length > 0) { +// for (int i = 0; i < files.length; i++) { +// File file = files[i]; +// if (file.isDirectory()) { +// setDirPermissionsRecursively(file); +// } else { +// // 设置文件权限(与目录一致,确保可读写) +// file.setReadable(true, false); +// file.setWritable(true, false); +// file.setExecutable(false, false); +// // 裁剪/压缩/预览相关文件单独打印日志 +// if (file.getName().contains(CROP_TEMP_FILE_NAME) || +// file.getName().contains(CROP_RESULT_FILE_NAME) || +// file.getName().startsWith("ScaledCompress_")) { +// LogUtils.d(TAG, "【权限管理】关键文件权限设置:文件名=" + file.getName() + ",可写=" + file.canWrite()); +// } +// } +// } +// } +// } catch (SecurityException e) { +// LogUtils.e(TAG, "【权限管理】设置目录权限失败(系统禁止):" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e); +// ToastUtils.show("目录权限设置失败,请授予应用存储权限"); +// } catch (Exception e) { +// LogUtils.e(TAG, "【权限管理】设置目录权限异常:" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e); +// } +// } /** * 工具方法:设置单个文件权限(确保系统裁剪应用/预览功能可读写,适配/Pictures/PowerBell目录) * 【关键调整】public修饰,适配BackgroundSettingsActivity的外部调用 * @param file 要设置权限的文件 */ - public void setFilePermissions(File file) { - if (file == null || !file.exists()) { - LogUtils.d(TAG, "【权限管理】文件无效,无需设置权限:" + (file != null ? file.getAbsolutePath() : "null")); - return; - } - try { - // 核心:允许所有用户读写(系统裁剪应用非本应用进程,需开放权限) - file.setReadable(true, false); - file.setWritable(true, false); - file.setExecutable(false, false); - LogUtils.d(TAG, "【权限管理】文件权限设置完成(/Pictures/PowerBell下):路径=" + file.getAbsolutePath() + ",可写=" + file.canWrite() + ",可读=" + file.canRead()); - } catch (Exception e) { - LogUtils.e(TAG, "【权限管理】设置文件权限失败:" + file.getAbsolutePath() + ",错误:" + e.getMessage(), e); - } - } +// public void setFilePermissions(File file) { +// if (file == null || !file.exists()) { +// LogUtils.d(TAG, "【权限管理】文件无效,无需设置权限:" + (file != null ? file.getAbsolutePath() : "null")); +// return; +// } +// try { +// // 核心:允许所有用户读写(系统裁剪应用非本应用进程,需开放权限) +// file.setReadable(true, false); +// file.setWritable(true, false); +// file.setExecutable(false, false); +// LogUtils.d(TAG, "【权限管理】文件权限设置完成(/Pictures/PowerBell下):路径=" + file.getAbsolutePath() + ",可写=" + file.canWrite() + ",可读=" + file.canRead()); +// } catch (Exception e) { +// LogUtils.e(TAG, "【权限管理】设置文件权限失败:" + file.getAbsolutePath() + ",错误:" + e.getMessage(), e); +// } +// } /** * 工具方法:清理旧文件(避免文件锁定/残留,适配系统公共目录)【内部私有,不对外暴露】 @@ -754,34 +645,20 @@ public class BackgroundSourceUtils { return; } if (file.exists()) { - // 先设置文件为可写(避免系统公共目录下文件只读导致删除失败) - file.setWritable(true, false); - boolean deleteSuccess = file.delete(); - LogUtils.d(TAG, "【文件管理】清理" + fileDesc + ":" + (deleteSuccess ? "成功" : "失败") + ",路径:" + file.getAbsolutePath()); - // 若删除失败,标记为退出时删除(兼容文件锁定场景) - if (!deleteSuccess) { - file.deleteOnExit(); - LogUtils.w(TAG, "【文件管理】" + fileDesc + "删除失败,标记为退出时自动删除"); - } + file.delete(); + LogUtils.w(TAG, "【文件管理】" + fileDesc + "已删除"); } } /** * 新增:清理压缩图目录下的旧文件(避免残留,初始化时调用) */ - private void clearOldCompressFiles() { - if (fBackgroundCompressDir.exists()) { - File[] files = fBackgroundCompressDir.listFiles(); - if (files != null && files.length > 0) { - for (int i = 0; i < files.length; i++) { - File file = files[i]; - if (file.isFile() && file.getName().startsWith("ScaledCompress_")) { - clearOldFile(file, "压缩图目录旧文件(BackgroundCrops)"); - } - } - } - } - LogUtils.d(TAG, "【文件管理】压缩图目录旧文件清理完成(BackgroundCrops)"); + void clearCropTempFiles() { + for(File file : fCropCacheDir.listFiles()){ + clearOldFile(file, "旧裁剪缓存文件(" + file.getAbsolutePath() + ")"); + } + mCropSourceFile = null; + mCropResultFile = null; } /** @@ -790,63 +667,38 @@ public class BackgroundSourceUtils { * @param dir 要验证的目录 * @return true=实际可写,false=实际不可写 */ - private boolean isDirActuallyWritable(File dir) { - if (dir == null || !dir.exists() || !dir.isDirectory()) { - return false; - } - // 创建临时空文件(随机文件名,避免冲突) - File testFile = new File(dir, "test_write_" + System.currentTimeMillis() + ".tmp"); - try { - boolean createSuccess = testFile.createNewFile(); - if (createSuccess) { - boolean canWrite = testFile.canWrite(); - boolean canRead = testFile.canRead(); - testFile.delete(); // 删除临时文件,不占用空间 - LogUtils.d(TAG, "【权限校验】目录实际写入校验(/Pictures/PowerBell下):" + dir.getAbsolutePath() + ",创建成功=" + createSuccess + ",可写=" + canWrite + ",可读=" + canRead + ",结果=" + (canWrite ? "通过" : "失败")); - return canWrite; - } else { - LogUtils.d(TAG, "【权限校验】目录实际写入校验失败:" + dir.getAbsolutePath() + ",创建临时文件失败(Permission denied)"); - return false; - } - } catch (IOException e) { - LogUtils.e(TAG, "【权限校验】目录实际写入校验异常:" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e); - return false; - } - } +// private boolean isDirActuallyWritable(File dir) { +// if (dir == null || !dir.exists() || !dir.isDirectory()) { +// return false; +// } +// // 创建临时空文件(随机文件名,避免冲突) +// File testFile = new File(dir, "test_write_" + System.currentTimeMillis() + ".tmp"); +// try { +// boolean createSuccess = testFile.createNewFile(); +// if (createSuccess) { +// boolean canWrite = testFile.canWrite(); +// boolean canRead = testFile.canRead(); +// testFile.delete(); // 删除临时文件,不占用空间 +// LogUtils.d(TAG, "【权限校验】目录实际写入校验(/Pictures/PowerBell下):" + dir.getAbsolutePath() + ",创建成功=" + createSuccess + ",可写=" + canWrite + ",可读=" + canRead + ",结果=" + (canWrite ? "通过" : "失败")); +// return canWrite; +// } else { +// LogUtils.d(TAG, "【权限校验】目录实际写入校验失败:" + dir.getAbsolutePath() + ",创建临时文件失败(Permission denied)"); +// return false; +// } +// } catch (IOException e) { +// LogUtils.e(TAG, "【权限校验】目录实际写入校验异常:" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e); +// return false; +// } +// } /** * 对外接口:清理指定旧文件(适配BackgroundSettingsActivity调用,支持/Pictures/PowerBell目录) * @param file 要清理的文件 * @param fileDesc 文件描述(用于日志打印) */ - public void clearOldFileByExternal(File file, String fileDesc) { - clearOldFile(file, fileDesc); // 调用内部private方法,复用逻辑 - } - - /** - * 清理裁剪相关临时文件(对外提供,Activity退出时调用,适配/Pictures/PowerBell/cache目录) - * 【恢复函数】:兼容原代码中 mBgSourceUtils.clearCropTempFiles() 调用 - */ - public void clearCropTempFiles() { - // 清理核心裁剪文件(复用内部clearOldFile方法,逻辑统一) - clearOldFile(cropTempFile, "裁剪临时文件(/Pictures/PowerBell/cache)"); - //clearOldFile(cropResultFile, "裁剪结果文件(/Pictures/PowerBell/BackgroundSource)"); - - // 清理裁剪缓存目录下的所有临时文件(避免残留) - if (fPictureCacheDir.exists()) { - File[] files = fPictureCacheDir.listFiles(); - if (files != null && files.length > 0) { - for (int i = 0; i < files.length; i++) { - File file = files[i]; - if (file.isFile()) { - // 仅清理文件(不删除目录),确保后续裁剪可正常创建文件 - clearOldFile(file, "裁剪缓存目录残留文件"); - } - } - } - } - LogUtils.d(TAG, "【文件管理】裁剪相关临时文件清理完成(/Pictures/PowerBell下)"); - } +// public void clearOldFileByExternal(File file, String fileDesc) { +// clearOldFile(file, fileDesc); // 调用内部private方法,复用逻辑 +// } /** * 适配原调用:mBgSourceUtils.copyFile(new File(""), parentDir) @@ -864,7 +716,7 @@ public class BackgroundSourceUtils { } // 若target是文件,取其父目录;若本身是目录,直接创建(实例化时已创建,此处二次确认) File targetDir = target.isFile() ? target.getParentFile() : target; - createDirWithPermission(targetDir, "空源文件场景-目录创建(/Pictures/PowerBell下)", false); + createDirWithPermission(targetDir, "空源文件场景-目录创建(/Pictures/PowerBell下)"); LogUtils.d(TAG, "【文件管理】空源文件场景:目录创建完成,路径=" + targetDir.getAbsolutePath()); return true; } @@ -888,7 +740,7 @@ public class BackgroundSourceUtils { String cachePath = mContext.getCacheDir().getAbsolutePath(); if (!TextUtils.isEmpty(publicPicturePath)) { - if (dirPath.contains(publicPicturePath + File.separator + "PowerBell" + File.separator + COMPRESS_BASE_DIR_NAME)) { + if (dirPath.contains(publicPicturePath + File.separator + "PowerBell" + File.separator + COMPRESS_DIR_NAME)) { return "系统公共图片目录(/Pictures/PowerBell/BackgroundCrops,压缩图统一存储目录)"; // 新增压缩图目录描述 } else if (dirPath.contains(publicPicturePath + File.separator + "PowerBell")) { return "系统公共图片目录(/Pictures/PowerBell,图片存储/裁剪目录)"; @@ -1073,9 +925,9 @@ public class BackgroundSourceUtils { } // 清理旧的压缩文件(避免文件残留) - if (compressFile.exists()) { - clearOldFileByExternal(compressFile, "旧压缩文件(BackgroundCrops)"); - } +// if (compressFile.exists()) { +// clearOldFileByExternal(compressFile, "旧压缩文件(BackgroundCrops)"); +// } compressFile.createNewFile(); // 写入压缩图(质量80,平衡清晰度和内存)