20251202_022447_396
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Mon Dec 01 16:33:23 GMT 2025
|
#Mon Dec 01 18:24:07 GMT 2025
|
||||||
stageCount=13
|
stageCount=13
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.11
|
baseVersion=15.11
|
||||||
publishVersion=15.11.12
|
publishVersion=15.11.12
|
||||||
buildCount=98
|
buildCount=102
|
||||||
baseBetaVersion=15.11.13
|
baseBetaVersion=15.11.13
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,8 @@ public class BackgroundSourceUtils {
|
|||||||
public static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider"; // 多包名兼容
|
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";
|
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";
|
||||||
|
|
||||||
// 1. 静态实例加volatile,禁止指令重排,保证可见性(双重校验锁单例核心)
|
// 1. 静态实例加volatile,禁止指令重排,保证可见性(双重校验锁单例核心)
|
||||||
private static volatile BackgroundSourceUtils sInstance;
|
private static volatile BackgroundSourceUtils sInstance;
|
||||||
@@ -47,7 +49,8 @@ public class BackgroundSourceUtils {
|
|||||||
// 图片操作目录(系统公共目录:/storage/emulated/0/Pictures/PowerBell/)
|
// 图片操作目录(系统公共目录:/storage/emulated/0/Pictures/PowerBell/)
|
||||||
private File fPictureBaseDir; // 图片基础目录
|
private File fPictureBaseDir; // 图片基础目录
|
||||||
private File fPictureCacheDir; // 裁剪缓存目录(基础目录下/cache)
|
private File fPictureCacheDir; // 裁剪缓存目录(基础目录下/cache)
|
||||||
private File fBackgroundSourceDir; // 图片存储目录(基础目录下,存储正式/预览图)
|
private File fBackgroundSourceDir; // 图片存储目录(基础目录下,存储正式/预览原图)
|
||||||
|
private File fBackgroundCompressDir; // 新增:压缩图统一存储目录(基础目录下/BackgroundCrops)
|
||||||
// JSON配置目录(原应用外置存储目录,不改变)
|
// JSON配置目录(原应用外置存储目录,不改变)
|
||||||
private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundSourceUtils)
|
private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundSourceUtils)
|
||||||
private File fModelDir; // 模型文件目录(存储JSON配置)
|
private File fModelDir; // 模型文件目录(存储JSON配置)
|
||||||
@@ -63,9 +66,8 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
// 上下文用Application Context,避免Activity内存泄漏
|
// 上下文用Application Context,避免Activity内存泄漏
|
||||||
this.mContext = context.getApplicationContext();
|
this.mContext = context.getApplicationContext();
|
||||||
// 初始化目录(分图片目录+JSON目录)
|
// 【核心调整1】实例化初期优先初始化所有必要目录(确保实例化完成时目录100%就绪)
|
||||||
initPictureDirs(); // 初始化图片操作目录(系统公共目录)
|
initNecessaryDirs();
|
||||||
initJsonDirs(); // 初始化JSON配置目录(应用外置存储)
|
|
||||||
// 初始化所有文件(裁剪临时文件/结果文件等)
|
// 初始化所有文件(裁剪临时文件/结果文件等)
|
||||||
initAllFiles();
|
initAllFiles();
|
||||||
// 加载配置(确保正式/预览Bean是两份独立实例)
|
// 加载配置(确保正式/预览Bean是两份独立实例)
|
||||||
@@ -85,26 +87,50 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化图片操作目录(核心:系统公共图片目录 /Pictures/PowerBell/)
|
* 【核心新增】统一初始化所有必要目录(实例化初期调用,确保目录优先创建)
|
||||||
|
* 整合图片目录+JSON目录,集中管理目录创建逻辑,保证实例化完成时所有目录就绪
|
||||||
|
*/
|
||||||
|
private void initNecessaryDirs() {
|
||||||
|
LogUtils.d(TAG, "【实例化初期-目录初始化】开始创建所有必要目录...");
|
||||||
|
// 1. 初始化图片操作目录(系统公共目录 /Pictures/PowerBell/)
|
||||||
|
initPictureDirs();
|
||||||
|
// 2. 初始化JSON配置目录(应用外置存储)
|
||||||
|
initJsonDirs();
|
||||||
|
LogUtils.d(TAG, "【实例化初期-目录初始化】所有必要目录创建完成!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化图片操作目录(核心:系统公共图片目录 /Pictures/PowerBell/,新增压缩图目录)
|
||||||
|
* 【调整强化】新增目录创建后二次校验,失败则降级到备选目录,确保目录可用
|
||||||
*/
|
*/
|
||||||
private void initPictureDirs() {
|
private void initPictureDirs() {
|
||||||
// 1. 图片基础目录:/storage/emulated/0/Pictures/PowerBell
|
// 1. 图片基础目录:/storage/emulated/0/Pictures/PowerBell
|
||||||
fPictureBaseDir = new File(PICTURE_BASE_DIR);
|
fPictureBaseDir = new File(PICTURE_BASE_DIR);
|
||||||
// 2. 图片存储目录:基础目录下(存储正式/预览图片)
|
// 2. 图片存储目录:基础目录下(存储正式/预览原图)
|
||||||
fBackgroundSourceDir = new File(fPictureBaseDir, "BackgroundSource");
|
fBackgroundSourceDir = new File(fPictureBaseDir, "BackgroundSource");
|
||||||
// 3. 裁剪缓存目录:基础目录下/cache(所有裁剪操作在此目录)
|
// 3. 裁剪缓存目录:基础目录下/cache(所有裁剪操作在此目录)
|
||||||
fPictureCacheDir = new File(fPictureBaseDir, CROP_TEMP_DIR_NAME);
|
fPictureCacheDir = new File(fPictureBaseDir, CROP_TEMP_DIR_NAME);
|
||||||
|
// 4. 新增:压缩图统一存储目录(基础目录下/BackgroundCrops,所有压缩图放这里)
|
||||||
|
fBackgroundCompressDir = new File(fPictureBaseDir, COMPRESS_BASE_DIR_NAME);
|
||||||
|
|
||||||
// 4. 递归创建目录(复用FileUtils间接创建,简化逻辑)
|
// 5. 强制创建所有图片目录(带二次校验+降级兜底)
|
||||||
createDirWithPermission(fPictureBaseDir, "图片基础目录(/Pictures/PowerBell)");
|
createDirWithPermission(fPictureBaseDir, "图片基础目录(/Pictures/PowerBell)", true);
|
||||||
createDirWithPermission(fBackgroundSourceDir, "图片存储目录(基础目录下)");
|
createDirWithPermission(fBackgroundSourceDir, "图片存储目录(基础目录下)", true);
|
||||||
createDirWithPermission(fPictureCacheDir, "裁剪缓存目录(基础目录/cache)");
|
createDirWithPermission(fPictureCacheDir, "裁剪缓存目录(基础目录/cache)", true);
|
||||||
|
createDirWithPermission(fBackgroundCompressDir, "压缩图统一存储目录(基础目录/BackgroundCrops)", true);
|
||||||
|
|
||||||
LogUtils.d(TAG, "【图片目录初始化】完成:基础目录=" + fPictureBaseDir.getAbsolutePath() + ",裁剪缓存目录=" + fPictureCacheDir.getAbsolutePath());
|
// 6. 目录创建后最终校验(确保所有目录已就绪)
|
||||||
|
validatePictureDirs();
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "【图片目录初始化】完成:" +
|
||||||
|
"基础目录=" + fPictureBaseDir.getAbsolutePath() +
|
||||||
|
",裁剪缓存目录=" + fPictureCacheDir.getAbsolutePath() +
|
||||||
|
",压缩图目录=" + fBackgroundCompressDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化JSON配置目录(保留原逻辑:应用外置存储)
|
* 初始化JSON配置目录(保留原逻辑:应用外置存储)
|
||||||
|
* 【调整强化】新增目录创建后二次校验,失败则降级到应用内部缓存目录
|
||||||
*/
|
*/
|
||||||
private void initJsonDirs() {
|
private void initJsonDirs() {
|
||||||
// 1. 工具类根目录(应用外置存储)
|
// 1. 工具类根目录(应用外置存储)
|
||||||
@@ -115,7 +141,8 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
// 2. 模型文件目录(存储JSON配置)
|
// 2. 模型文件目录(存储JSON配置)
|
||||||
fModelDir = new File(fUtilsDir, "ModelDir");
|
fModelDir = new File(fUtilsDir, "ModelDir");
|
||||||
createDirWithPermission(fModelDir, "JSON配置目录(应用外置存储)");
|
// 强制创建JSON目录(带二次校验+降级兜底)
|
||||||
|
createDirWithPermission(fModelDir, "JSON配置目录(应用外置存储)", true);
|
||||||
|
|
||||||
// 3. 初始化JSON文件对象(两份独立文件,对应两份Bean实例)
|
// 3. 初始化JSON文件对象(两份独立文件,对应两份Bean实例)
|
||||||
currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json");
|
currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json");
|
||||||
@@ -124,6 +151,149 @@ public class BackgroundSourceUtils {
|
|||||||
LogUtils.d(TAG, "【JSON目录初始化】完成:目录=" + fModelDir.getAbsolutePath() + ",正式JSON=" + currentBackgroundBeanFile.getName() + ",预览JSON=" + previewBackgroundBeanFile.getName());
|
LogUtils.d(TAG, "【JSON目录初始化】完成:目录=" + fModelDir.getAbsolutePath() + ",正式JSON=" + currentBackgroundBeanFile.getName() + ",预览JSON=" + previewBackgroundBeanFile.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【核心强化】创建目录并设置权限(适配系统公共目录/Pictures/PowerBell,确保实例化时目录就绪)
|
||||||
|
* @param dir 要创建的目录
|
||||||
|
* @param dirDesc 目录描述(用于日志打印)
|
||||||
|
* @param needFallback 是否需要降级兜底(实例化初期必须为true,确保目录可用)
|
||||||
|
*/
|
||||||
|
private void createDirWithPermission(File dir, String dirDesc, boolean needFallback) {
|
||||||
|
if (dir == null) {
|
||||||
|
LogUtils.e(TAG, "【文件管理】创建目录失败:目录对象为null(描述:" + dirDesc + ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一步:主动检测并创建目录(递归创建所有父目录)
|
||||||
|
boolean isCreated = true;
|
||||||
|
if (!dir.exists()) {
|
||||||
|
LogUtils.d(TAG, "【文件管理】" + dirDesc + "不存在,开始创建:" + dir.getAbsolutePath());
|
||||||
|
isCreated = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【新增】图片目录创建后最终校验(确保实例化时所有图片目录已就绪)
|
||||||
|
*/
|
||||||
|
private void validatePictureDirs() {
|
||||||
|
LogUtils.d(TAG, "【图片目录校验】开始校验所有图片目录...");
|
||||||
|
boolean allReady = true;
|
||||||
|
if (!fPictureBaseDir.exists() || !fPictureBaseDir.isDirectory()) {
|
||||||
|
LogUtils.e(TAG, "【图片目录校验】图片基础目录未就绪:" + fPictureBaseDir.getAbsolutePath());
|
||||||
|
allReady = false;
|
||||||
|
}
|
||||||
|
if (!fBackgroundSourceDir.exists() || !fBackgroundSourceDir.isDirectory()) {
|
||||||
|
LogUtils.e(TAG, "【图片目录校验】图片存储目录未就绪:" + fBackgroundSourceDir.getAbsolutePath());
|
||||||
|
allReady = false;
|
||||||
|
}
|
||||||
|
if (!fPictureCacheDir.exists() || !fPictureCacheDir.isDirectory()) {
|
||||||
|
LogUtils.e(TAG, "【图片目录校验】裁剪缓存目录未就绪:" + fPictureCacheDir.getAbsolutePath());
|
||||||
|
allReady = false;
|
||||||
|
}
|
||||||
|
if (!fBackgroundCompressDir.exists() || !fBackgroundCompressDir.isDirectory()) {
|
||||||
|
LogUtils.e(TAG, "【图片目录校验】压缩图目录未就绪:" + fBackgroundCompressDir.getAbsolutePath());
|
||||||
|
allReady = false;
|
||||||
|
}
|
||||||
|
if (allReady) {
|
||||||
|
LogUtils.d(TAG, "【图片目录校验】所有图片目录均已就绪!");
|
||||||
|
} else {
|
||||||
|
LogUtils.e(TAG, "【图片目录校验】部分目录未就绪,可能影响后续功能!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化所有文件(裁剪文件→图片缓存目录,结果文件→图片存储目录)
|
* 初始化所有文件(裁剪文件→图片缓存目录,结果文件→图片存储目录)
|
||||||
*/
|
*/
|
||||||
@@ -136,71 +306,149 @@ public class BackgroundSourceUtils {
|
|||||||
// 3. 初始化时清理旧文件(复用FileUtils简化清理逻辑)
|
// 3. 初始化时清理旧文件(复用FileUtils简化清理逻辑)
|
||||||
clearOldFile(cropTempFile, "旧裁剪临时文件(/Pictures/PowerBell/cache)");
|
clearOldFile(cropTempFile, "旧裁剪临时文件(/Pictures/PowerBell/cache)");
|
||||||
clearOldFile(cropResultFile, "旧裁剪结果文件(/Pictures/PowerBell/BackgroundSource)");
|
clearOldFile(cropResultFile, "旧裁剪结果文件(/Pictures/PowerBell/BackgroundSource)");
|
||||||
|
// 新增:清理压缩图目录下的旧文件(避免残留)
|
||||||
|
clearOldCompressFiles();
|
||||||
|
|
||||||
LogUtils.d(TAG, "【文件初始化】完成:裁剪临时文件=" + cropTempFile.getAbsolutePath() + ",裁剪结果文件=" + cropResultFile.getAbsolutePath());
|
LogUtils.d(TAG, "【文件初始化】完成:裁剪临时文件=" + cropTempFile.getAbsolutePath() + ",裁剪结果文件=" + cropResultFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 核心函数:为系统裁剪应用创建可读写的FileProvider路径(适配Android14+ MIUI)
|
* 核心优化函数:带原图参数的裁剪路径创建(优先使用原图,不复制)
|
||||||
* 裁剪文件统一放入 /Pictures/PowerBell/cache/,确保系统裁剪工具可读写
|
* 替代原逻辑中"复制原图到BackgroundSource再裁剪"的流程
|
||||||
*/
|
*/
|
||||||
public File createCropFileProviderPath() {
|
public File createCropFileProviderPath(File originalImageFile) {
|
||||||
LogUtils.d(TAG, "【裁剪路径】createCropFileProviderPath 触发,创建系统裁剪可读写路径");
|
final String TAG = "BackgroundSourceUtils";
|
||||||
|
Log.d(TAG, "【裁剪优化】createCropFileProviderPath(原图) 触发,优先使用原图路径");
|
||||||
|
|
||||||
// 优先使用图片基础目录下的cache目录(核心:系统公共目录,权限更友好)
|
// 核心逻辑1:直接使用裁剪前的原图(不复制),仅校验合法性和权限
|
||||||
if (fPictureCacheDir != null && fPictureCacheDir.exists() && isDirActuallyWritable(fPictureCacheDir)) {
|
if (originalImageFile != null && originalImageFile.exists()
|
||||||
try {
|
&& originalImageFile.isFile() && originalImageFile.length() > 0) {
|
||||||
// 先清理旧文件,避免锁定
|
// 校验原图目录是否可写(系统裁剪工具需读写权限)
|
||||||
clearOldFile(cropTempFile, "裁剪临时文件(重新初始化)");
|
if (isDirectoryWritable(originalImageFile.getParentFile())) {
|
||||||
// 创建新的裁剪临时文件
|
// 强制开放原图权限(解决跨进程访问问题)
|
||||||
cropTempFile.createNewFile();
|
setFileReadWritePermission(originalImageFile);
|
||||||
// 强制设置权限(系统裁剪应用必需:允许所有用户读写)
|
Log.d(TAG, "【裁剪优化】直接使用原图启动裁剪(无复制):" + originalImageFile.getAbsolutePath());
|
||||||
setFilePermissions(cropTempFile);
|
return originalImageFile; // 直接返回原图,不做任何复制
|
||||||
LogUtils.d(TAG, "【裁剪路径】创建成功(/Pictures/PowerBell/cache):" + cropTempFile.getAbsolutePath());
|
} else {
|
||||||
return cropTempFile;
|
Log.w(TAG, "【裁剪优化】原图目录不可写,切换到临时文件兜底");
|
||||||
} catch (IOException e) {
|
|
||||||
LogUtils.e(TAG, "【裁剪路径】cache目录创建文件失败:" + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LogUtils.w(TAG, "【裁剪路径】cache目录不可用,切换到图片存储目录兜底");
|
Log.w(TAG, "【裁剪优化】原图无效(空/不存在/0大小),切换到临时文件兜底");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兜底:使用图片存储目录(极端情况)
|
// 兜底逻辑(仅原图不可用时触发,避免多复制):使用应用缓存目录(不涉及BackgroundSource)
|
||||||
if (isDirActuallyWritable(fBackgroundSourceDir)) {
|
File cacheDir = mContext.getCacheDir(); // 应用内部缓存,无需额外权限
|
||||||
|
if (isDirectoryWritable(cacheDir)) {
|
||||||
try {
|
try {
|
||||||
cropTempFile = new File(fBackgroundSourceDir, CROP_TEMP_FILE_NAME);
|
File cropTempFile = new File(cacheDir, "crop_temp_" + System.currentTimeMillis() + ".jpg");
|
||||||
clearOldFile(cropTempFile, "裁剪临时文件(兜底目录)");
|
// 清理旧临时文件(避免堆积)
|
||||||
|
if (cropTempFile.exists()) {
|
||||||
|
cropTempFile.delete();
|
||||||
|
}
|
||||||
cropTempFile.createNewFile();
|
cropTempFile.createNewFile();
|
||||||
setFilePermissions(cropTempFile);
|
setFileReadWritePermission(cropTempFile);
|
||||||
LogUtils.w(TAG, "【裁剪路径】切换到图片存储目录创建成功:" + cropTempFile.getAbsolutePath());
|
Log.d(TAG, "【裁剪优化】原图不可用,使用缓存临时文件:" + cropTempFile.getAbsolutePath());
|
||||||
return cropTempFile;
|
return cropTempFile;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LogUtils.e(TAG, "【裁剪路径】图片存储目录创建文件失败:" + e.getMessage(), e);
|
Log.e(TAG, "【裁剪优化】创建缓存临时文件失败:" + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 终极兜底:应用内部缓存目录(提示用户权限问题)
|
// 终极兜底:公共图片缓存目录(极端情况)
|
||||||
File cacheDir = mContext.getCacheDir();
|
File publicCacheDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Cache");
|
||||||
if (isDirActuallyWritable(cacheDir)) {
|
if (isDirectoryWritable(publicCacheDir)) {
|
||||||
try {
|
try {
|
||||||
cropTempFile = new File(cacheDir, CROP_TEMP_FILE_NAME);
|
if (!publicCacheDir.exists()) {
|
||||||
clearOldFile(cropTempFile, "裁剪临时文件(终极兜底)");
|
publicCacheDir.mkdirs();
|
||||||
|
}
|
||||||
|
File cropTempFile = new File(publicCacheDir, "crop_temp_" + System.currentTimeMillis() + ".jpg");
|
||||||
|
if (cropTempFile.exists()) {
|
||||||
|
cropTempFile.delete();
|
||||||
|
}
|
||||||
cropTempFile.createNewFile();
|
cropTempFile.createNewFile();
|
||||||
setFilePermissions(cropTempFile);
|
setFileReadWritePermission(cropTempFile);
|
||||||
LogUtils.w(TAG, "【裁剪路径】系统公共目录失败,兜底到应用缓存(MIUI可能裁剪失败):" + cropTempFile.getAbsolutePath());
|
Log.w(TAG, "【裁剪优化】应用缓存不可用,使用公共图片缓存:" + cropTempFile.getAbsolutePath());
|
||||||
ToastUtils.show("存储权限受限,建议授予「所有文件访问权限」以确保裁剪正常");
|
|
||||||
return cropTempFile;
|
return cropTempFile;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LogUtils.e(TAG, "【裁剪路径】终极兜底目录创建失败:" + e.getMessage(), e);
|
Log.e(TAG, "【裁剪优化】创建公共临时文件失败:" + e.getMessage(), e);
|
||||||
ToastUtils.show("裁剪路径创建失败,请重启应用并授予存储权限");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 极端情况:所有目录均失败
|
Log.e(TAG, "【裁剪优化】所有裁剪路径创建失败,裁剪功能不可用");
|
||||||
LogUtils.e(TAG, "【裁剪路径】所有目录均无法创建裁剪文件,裁剪功能不可用");
|
|
||||||
return null;
|
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); // 无原图时走兜底逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:校验目录是否可写(新增,保障裁剪权限)
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载背景图片配置数据(核心:确保current/preview是两份独立的BackgroundBean实例)
|
* 加载背景图片配置数据(核心:确保current/preview是两份独立的BackgroundBean实例)
|
||||||
*/
|
*/
|
||||||
@@ -211,6 +459,9 @@ public class BackgroundSourceUtils {
|
|||||||
currentBackgroundBean = new BackgroundBean(); // 正式Bean独立实例初始化
|
currentBackgroundBean = new BackgroundBean(); // 正式Bean独立实例初始化
|
||||||
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
|
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
|
||||||
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在,创建独立实例并保存到JSON");
|
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在,创建独立实例并保存到JSON");
|
||||||
|
} else {
|
||||||
|
// 修复:加载旧配置时,若压缩图路径不在BackgroundCrops,自动迁移路径(兼容历史数据)
|
||||||
|
migrateCompressPathToNewDir(currentBackgroundBean, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 加载预览Bean(独立实例:从previewBackgroundBean.json加载,不存在则新建,与正式Bean完全分离)
|
// 2. 加载预览Bean(独立实例:从previewBackgroundBean.json加载,不存在则新建,与正式Bean完全分离)
|
||||||
@@ -219,6 +470,9 @@ public class BackgroundSourceUtils {
|
|||||||
previewBackgroundBean = new BackgroundBean(); // 预览Bean独立实例初始化
|
previewBackgroundBean = new BackgroundBean(); // 预览Bean独立实例初始化
|
||||||
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
|
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
|
||||||
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在,创建独立实例并保存到JSON");
|
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在,创建独立实例并保存到JSON");
|
||||||
|
} else {
|
||||||
|
// 修复:加载旧配置时,若压缩图路径不在BackgroundCrops,自动迁移路径(兼容历史数据)
|
||||||
|
migrateCompressPathToNewDir(previewBackgroundBean, false);
|
||||||
}
|
}
|
||||||
LogUtils.d(TAG, "【配置管理】两份Bean实例初始化完成:正式Bean=" + currentBackgroundBean.hashCode() + ",预览Bean=" + previewBackgroundBean.hashCode() + "(hash不同证明实例独立)");
|
LogUtils.d(TAG, "【配置管理】两份Bean实例初始化完成:正式Bean=" + currentBackgroundBean.hashCode() + ",预览Bean=" + previewBackgroundBean.hashCode() + "(hash不同证明实例独立)");
|
||||||
}
|
}
|
||||||
@@ -261,7 +515,7 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取预览背景压缩图片路径(同步修复:移除 loadSettings(),强化非空校验)
|
* 获取预览背景压缩图片路径(同步修复:移除 loadSettings(),强化非空校验,统一指向BackgroundCrops目录)
|
||||||
*/
|
*/
|
||||||
public String getPreviewBackgroundScaledCompressFilePath() {
|
public String getPreviewBackgroundScaledCompressFilePath() {
|
||||||
String compressFileName = previewBackgroundBean.getBackgroundScaledCompressFileName();
|
String compressFileName = previewBackgroundBean.getBackgroundScaledCompressFileName();
|
||||||
@@ -269,8 +523,24 @@ public class BackgroundSourceUtils {
|
|||||||
LogUtils.e(TAG, "【路径管理】预览压缩背景文件名为空,返回空路径");
|
LogUtils.e(TAG, "【路径管理】预览压缩背景文件名为空,返回空路径");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
File file = new File(fBackgroundSourceDir, compressFileName);
|
// 关键:压缩图路径统一指向BackgroundCrops目录(不再用BackgroundSource)
|
||||||
LogUtils.d(TAG, "【路径管理】预览压缩背景路径:" + file.getAbsolutePath());
|
File file = new File(fBackgroundCompressDir, compressFileName);
|
||||||
|
LogUtils.d(TAG, "【路径管理】预览压缩背景路径(BackgroundCrops目录):" + file.getAbsolutePath());
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增:获取正式背景压缩图片路径(统一指向BackgroundCrops目录,对外提供调用)
|
||||||
|
*/
|
||||||
|
public String getCurrentBackgroundScaledCompressFilePath() {
|
||||||
|
String compressFileName = currentBackgroundBean.getBackgroundScaledCompressFileName();
|
||||||
|
if (TextUtils.isEmpty(compressFileName)) {
|
||||||
|
LogUtils.e(TAG, "【路径管理】正式压缩背景文件名为空,返回空路径");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 关键:压缩图路径统一指向BackgroundCrops目录
|
||||||
|
File file = new File(fBackgroundCompressDir, compressFileName);
|
||||||
|
LogUtils.d(TAG, "【路径管理】正式压缩背景路径(BackgroundCrops目录):" + file.getAbsolutePath());
|
||||||
return file.getAbsolutePath();
|
return file.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +560,13 @@ public class BackgroundSourceUtils {
|
|||||||
return fBackgroundSourceDir.getAbsolutePath();
|
return fBackgroundSourceDir.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增:获取压缩图统一存储目录路径(对外提供:/Pictures/PowerBell/BackgroundCrops/)
|
||||||
|
*/
|
||||||
|
public String getBackgroundCompressDirPath() {
|
||||||
|
return fBackgroundCompressDir.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
public File getCropTempFile() {
|
public File getCropTempFile() {
|
||||||
return cropTempFile;
|
return cropTempFile;
|
||||||
}
|
}
|
||||||
@@ -304,130 +581,101 @@ public class BackgroundSourceUtils {
|
|||||||
|
|
||||||
// ------------------------------ 核心业务方法(复用FileUtils简化文件操作)------------------------------
|
// ------------------------------ 核心业务方法(复用FileUtils简化文件操作)------------------------------
|
||||||
/**
|
/**
|
||||||
* 保存图片到预览Bean(图片存储到/Pictures/PowerBell/BackgroundSource,JSON仍存应用外置存储)
|
* 优化函数:仅裁剪结果图可保存到BackgroundSource(避免启动裁剪时误复制原图)
|
||||||
* @param sourceFile 源图片文件(非空,必须存在)
|
* 说明:启动裁剪时不调用此方法,仅在裁剪完成后保存结果图时调用
|
||||||
* @param fileInfo 图片附加信息(如Uri字符串,仅作备注)
|
|
||||||
* @return 更新后的预览Bean
|
|
||||||
*/
|
*/
|
||||||
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
|
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
|
||||||
// 强化校验:源文件必须存在、是文件、大小>0(避免无效文件复制)
|
final String TAG = "BackgroundSourceUtils";
|
||||||
if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile() || sourceFile.length() <= 0) {
|
// 强化校验1:仅允许裁剪结果图传入(通过文件路径判断,避免原图误传入)
|
||||||
LogUtils.e(TAG, "【文件管理】源文件无效:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null") + ",大小:" + (sourceFile != null ? sourceFile.length() : 0) + "bytes");
|
if (sourceFile == null || !sourceFile.exists() || sourceFile.length() <= 0) {
|
||||||
ToastUtils.show("源图片文件无效");
|
Log.e(TAG, "【保存优化】源文件无效,拒绝保存:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null"));
|
||||||
return previewBackgroundBean;
|
return previewBackgroundBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保图片存储目录存在(/Pictures/PowerBell/BackgroundSource)
|
// 强化校验2:排除原图路径(避免启动裁剪时传入原图复制)
|
||||||
|
String originalImageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(); // 原图存储目录(如相册/拍照目录)
|
||||||
|
if (sourceFile.getAbsolutePath().contains(originalImageDir)) {
|
||||||
|
Log.w(TAG, "【保存优化】禁止复制原图到BackgroundSource,跳过保存");
|
||||||
|
return previewBackgroundBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保BackgroundSource目录存在(实例化时已创建,此处二次确认)
|
||||||
if (!fBackgroundSourceDir.exists()) {
|
if (!fBackgroundSourceDir.exists()) {
|
||||||
createDirWithPermission(fBackgroundSourceDir, "图片存储目录(saveFileToPreviewBean)");
|
if (!fBackgroundSourceDir.mkdirs()) {
|
||||||
}
|
Log.e(TAG, "【保存优化】BackgroundSource目录创建失败");
|
||||||
|
|
||||||
// 生成唯一文件名(直接复用FileUtils工具方法,无需重复实现)
|
|
||||||
String uniqueFileName = FileUtils.createUniqueFileName(sourceFile);
|
|
||||||
File previewBackgroundFile = new File(fBackgroundSourceDir, uniqueFileName);
|
|
||||||
|
|
||||||
// 核心:用FileUtils.copyFile替代自定义流复制,精简代码且保证高效
|
|
||||||
boolean copySuccess = FileUtils.copyFile(sourceFile, previewBackgroundFile);
|
|
||||||
if (!copySuccess) {
|
|
||||||
LogUtils.e(TAG, "【文件管理】图片复制到预览目录失败:" + sourceFile.getAbsolutePath() + " → " + previewBackgroundFile.getAbsolutePath());
|
|
||||||
ToastUtils.show("预览图片保存失败");
|
|
||||||
return previewBackgroundBean;
|
return previewBackgroundBean;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 预览Bean赋值(仍用独立实例,不影响正式Bean)
|
// 生成唯一文件名(避免覆盖)
|
||||||
previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); // 唯一文件名(非空)
|
String uniqueFileName = "bg_" + System.currentTimeMillis() + "_" + sourceFile.getName();
|
||||||
previewBackgroundBean.setBackgroundFilePath(previewBackgroundFile.getAbsolutePath()); // 新增:设置预览原图完整路径
|
File targetFile = new File(fBackgroundSourceDir, uniqueFileName);
|
||||||
previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_" + previewBackgroundFile.getName());
|
|
||||||
previewBackgroundBean.setBackgroundScaledCompressFilePath(fBackgroundSourceDir.getAbsolutePath() + File.separator + "ScaledCompress_" + previewBackgroundFile.getName()); // 新增:设置预览压缩图完整路径
|
// 执行复制(仅裁剪结果图会走到这一步)
|
||||||
|
if (FileUtils.copyFile(sourceFile, targetFile)) {
|
||||||
|
Log.d(TAG, "【保存优化】裁剪结果图保存成功:" + targetFile.getAbsolutePath());
|
||||||
|
// 更新预览Bean(原有逻辑保留)
|
||||||
|
previewBackgroundBean.setBackgroundFileName(uniqueFileName);
|
||||||
|
previewBackgroundBean.setBackgroundFilePath(targetFile.getAbsolutePath());
|
||||||
previewBackgroundBean.setBackgroundFileInfo(fileInfo);
|
previewBackgroundBean.setBackgroundFileInfo(fileInfo);
|
||||||
previewBackgroundBean.setIsUseBackgroundFile(true);
|
previewBackgroundBean.setIsUseBackgroundFile(true);
|
||||||
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(true); // 重命名字段:设置启用压缩图
|
// 保存Bean到本地(原有逻辑保留)
|
||||||
previewBackgroundBean.setBackgroundWidth(100);
|
saveSettings();
|
||||||
previewBackgroundBean.setBackgroundHeight(100);
|
} else {
|
||||||
|
Log.e(TAG, "【保存优化】裁剪结果图复制失败:" + sourceFile.getAbsolutePath() + " → " + targetFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
// 强制保存预览Bean到对应JSON(不影响正式Bean的JSON)
|
|
||||||
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
|
|
||||||
LogUtils.d(TAG, "【文件管理】预览Bean强制保存到JSON:" + previewBackgroundBeanFile.getAbsolutePath());
|
|
||||||
|
|
||||||
LogUtils.d(TAG, "【文件管理】预览图片保存成功(/Pictures/PowerBell):" + previewBackgroundFile.getAbsolutePath() + ",大小:" + previewBackgroundFile.length() + "bytes");
|
|
||||||
ToastUtils.show("预览图片加载成功");
|
|
||||||
return previewBackgroundBean;
|
return previewBackgroundBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交预览背景到正式背景(预览Bean → 正式Bean:深拷贝,新建正式Bean实例+逐字段拷贝)
|
* 提交预览背景到正式背景(预览Bean → 正式Bean:深拷贝,新建正式Bean实例+逐字段拷贝)
|
||||||
* 核心:深拷贝后,修改正式Bean不会影响预览Bean,两份实例完全独立
|
* 核心:深拷贝后,修改正式Bean不会影响预览Bean,两份实例完全独立,压缩图路径统一指向BackgroundCrops
|
||||||
*/
|
*/
|
||||||
public void commitPreviewSourceToCurrent() {
|
public void commitPreviewSourceToCurrent() {
|
||||||
// 深拷贝第一步:新建正式Bean独立实例(彻底脱离预览Bean的引用)
|
// 深拷贝第一步:新建正式Bean独立实例(彻底脱离预览Bean的引用)
|
||||||
currentBackgroundBean = new BackgroundBean();
|
currentBackgroundBean = new BackgroundBean();
|
||||||
// 深拷贝第二步:逐字段拷贝预览Bean的所有值(确保字段无遗漏)
|
// 深拷贝第二步:逐字段拷贝预览Bean的所有值(压缩图路径同步指向BackgroundCrops)
|
||||||
currentBackgroundBean.setBackgroundFileName(previewBackgroundBean.getBackgroundFileName());
|
currentBackgroundBean.setBackgroundFileName(previewBackgroundBean.getBackgroundFileName());
|
||||||
currentBackgroundBean.setBackgroundFilePath(previewBackgroundBean.getBackgroundFilePath()); // 新增字段:拷贝原图完整路径
|
currentBackgroundBean.setBackgroundFilePath(previewBackgroundBean.getBackgroundFilePath()); // 原图路径(BackgroundSource)
|
||||||
currentBackgroundBean.setBackgroundFileInfo(previewBackgroundBean.getBackgroundFileInfo());
|
currentBackgroundBean.setBackgroundFileInfo(previewBackgroundBean.getBackgroundFileInfo());
|
||||||
currentBackgroundBean.setIsUseBackgroundFile(previewBackgroundBean.isUseBackgroundFile());
|
currentBackgroundBean.setIsUseBackgroundFile(previewBackgroundBean.isUseBackgroundFile());
|
||||||
currentBackgroundBean.setBackgroundScaledCompressFileName(previewBackgroundBean.getBackgroundScaledCompressFileName());
|
currentBackgroundBean.setBackgroundScaledCompressFileName(previewBackgroundBean.getBackgroundScaledCompressFileName());
|
||||||
currentBackgroundBean.setBackgroundScaledCompressFilePath(previewBackgroundBean.getBackgroundScaledCompressFilePath()); // 新增字段:拷贝压缩图完整路径
|
currentBackgroundBean.setBackgroundScaledCompressFilePath(previewBackgroundBean.getBackgroundScaledCompressFilePath()); // 压缩图路径(BackgroundCrops)
|
||||||
currentBackgroundBean.setIsUseBackgroundScaledCompressFile(previewBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
|
currentBackgroundBean.setIsUseBackgroundScaledCompressFile(previewBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
|
||||||
currentBackgroundBean.setBackgroundWidth(previewBackgroundBean.getBackgroundWidth());
|
currentBackgroundBean.setBackgroundWidth(previewBackgroundBean.getBackgroundWidth());
|
||||||
currentBackgroundBean.setBackgroundHeight(previewBackgroundBean.getBackgroundHeight());
|
currentBackgroundBean.setBackgroundHeight(previewBackgroundBean.getBackgroundHeight());
|
||||||
currentBackgroundBean.setPixelColor(previewBackgroundBean.getPixelColor());
|
currentBackgroundBean.setPixelColor(previewBackgroundBean.getPixelColor());
|
||||||
|
|
||||||
saveSettings(); // 分别保存:正式Bean→currentJSON,预览Bean→previewJSON(两份独立)
|
saveSettings(); // 分别保存:正式Bean→currentJSON,预览Bean→previewJSON(两份独立)
|
||||||
LogUtils.d(TAG, "【配置管理】预览背景深拷贝到正式Bean:两份实例独立,JSON分别存储");
|
LogUtils.d(TAG, "【配置管理】预览背景深拷贝到正式Bean:两份实例独立,压缩图统一存储到BackgroundCrops");
|
||||||
ToastUtils.show("背景图片应用成功");
|
ToastUtils.show("背景图片应用成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将正式背景同步到预览背景(正式Bean → 预览Bean:深拷贝,新建预览Bean实例+逐字段拷贝)
|
* 将正式背景同步到预览背景(正式Bean → 预览Bean:深拷贝,新建预览Bean实例+逐字段拷贝)
|
||||||
* 核心:深拷贝后,修改预览Bean不会影响正式Bean,两份实例完全独立
|
* 核心:深拷贝后,修改预览Bean不会影响正式Bean,两份实例完全独立,压缩图路径统一指向BackgroundCrops
|
||||||
*/
|
*/
|
||||||
public void setCurrentSourceToPreview() {
|
public void setCurrentSourceToPreview() {
|
||||||
// 深拷贝第一步:新建预览Bean独立实例(彻底脱离正式Bean的引用)
|
// 深拷贝第一步:新建预览Bean独立实例(彻底脱离正式Bean的引用)
|
||||||
previewBackgroundBean = new BackgroundBean();
|
previewBackgroundBean = new BackgroundBean();
|
||||||
// 深拷贝第二步:逐字段拷贝正式Bean的所有值(确保字段无遗漏)
|
// 深拷贝第二步:逐字段拷贝正式Bean的所有值(压缩图路径同步指向BackgroundCrops)
|
||||||
previewBackgroundBean.setBackgroundFileName(currentBackgroundBean.getBackgroundFileName());
|
previewBackgroundBean.setBackgroundFileName(currentBackgroundBean.getBackgroundFileName());
|
||||||
previewBackgroundBean.setBackgroundFilePath(currentBackgroundBean.getBackgroundFilePath()); // 新增字段:拷贝原图完整路径
|
previewBackgroundBean.setBackgroundFilePath(currentBackgroundBean.getBackgroundFilePath()); // 原图路径(BackgroundSource)
|
||||||
previewBackgroundBean.setBackgroundFileInfo(currentBackgroundBean.getBackgroundFileInfo());
|
previewBackgroundBean.setBackgroundFileInfo(currentBackgroundBean.getBackgroundFileInfo());
|
||||||
previewBackgroundBean.setIsUseBackgroundFile(currentBackgroundBean.isUseBackgroundFile());
|
previewBackgroundBean.setIsUseBackgroundFile(currentBackgroundBean.isUseBackgroundFile());
|
||||||
previewBackgroundBean.setBackgroundScaledCompressFileName(currentBackgroundBean.getBackgroundScaledCompressFileName());
|
previewBackgroundBean.setBackgroundScaledCompressFileName(currentBackgroundBean.getBackgroundScaledCompressFileName());
|
||||||
previewBackgroundBean.setBackgroundScaledCompressFilePath(currentBackgroundBean.getBackgroundScaledCompressFilePath()); // 新增字段:拷贝压缩图完整路径
|
previewBackgroundBean.setBackgroundScaledCompressFilePath(currentBackgroundBean.getBackgroundScaledCompressFilePath()); // 压缩图路径(BackgroundCrops)
|
||||||
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(currentBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
|
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(currentBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
|
||||||
previewBackgroundBean.setBackgroundWidth(currentBackgroundBean.getBackgroundWidth());
|
previewBackgroundBean.setBackgroundWidth(currentBackgroundBean.getBackgroundWidth());
|
||||||
previewBackgroundBean.setBackgroundHeight(currentBackgroundBean.getBackgroundHeight());
|
previewBackgroundBean.setBackgroundHeight(currentBackgroundBean.getBackgroundHeight());
|
||||||
previewBackgroundBean.setPixelColor(currentBackgroundBean.getPixelColor());
|
previewBackgroundBean.setPixelColor(currentBackgroundBean.getPixelColor());
|
||||||
|
|
||||||
saveSettings(); // 分别保存:正式Bean→currentJSON,预览Bean→previewJSON(两份独立)
|
saveSettings(); // 分别保存:正式Bean→currentJSON,预览Bean→previewJSON(两份独立)
|
||||||
LogUtils.d(TAG, "【配置管理】正式背景深拷贝到预览Bean:两份实例独立,JSON分别存储");
|
LogUtils.d(TAG, "【配置管理】正式背景深拷贝到预览Bean:两份实例独立,压缩图统一存储到BackgroundCrops");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------ 必需保留的工具方法(与业务/权限强相关,无法复用FileUtils)------------------------------
|
// ------------------------------ 必需保留的工具方法(与业务/权限强相关,无法复用FileUtils)------------------------------
|
||||||
/**
|
|
||||||
* 工具方法:创建目录并设置权限(适配系统公共目录/Pictures/PowerBell,确保可读写)
|
|
||||||
* @param dir 要创建的目录
|
|
||||||
* @param dirDesc 目录描述(用于日志打印)
|
|
||||||
*/
|
|
||||||
private void createDirWithPermission(File dir, String dirDesc) {
|
|
||||||
if (dir == null) {
|
|
||||||
LogUtils.e(TAG, "【文件管理】创建目录失败:目录对象为null(描述:" + dirDesc + ")");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!dir.exists()) {
|
|
||||||
boolean mkdirsSuccess = dir.mkdirs();
|
|
||||||
LogUtils.d(TAG, "【文件管理】" + dirDesc + "创建结果:" + (mkdirsSuccess ? "成功" : "失败") + ",路径:" + dir.getAbsolutePath());
|
|
||||||
if (!mkdirsSuccess) {
|
|
||||||
LogUtils.w(TAG, "【文件管理】" + dirDesc + "创建失败,尝试创建父目录");
|
|
||||||
File parentDir = dir.getParentFile();
|
|
||||||
if (parentDir != null && !parentDir.exists()) {
|
|
||||||
parentDir.mkdirs();
|
|
||||||
dir.mkdir();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 强制设置目录权限(递归设置,确保系统公共目录所有层级可读写)
|
|
||||||
setDirPermissionsRecursively(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具方法:递归设置目录及子目录/文件的读写权限(适配系统公共目录/Pictures/PowerBell)
|
* 工具方法:递归设置目录及子目录/文件的读写权限(适配系统公共目录/Pictures/PowerBell)
|
||||||
* @param dir 要设置权限的目录
|
* @param dir 要设置权限的目录
|
||||||
@@ -458,8 +706,10 @@ public class BackgroundSourceUtils {
|
|||||||
file.setReadable(true, false);
|
file.setReadable(true, false);
|
||||||
file.setWritable(true, false);
|
file.setWritable(true, false);
|
||||||
file.setExecutable(false, false);
|
file.setExecutable(false, false);
|
||||||
// 裁剪/预览相关文件单独打印日志
|
// 裁剪/压缩/预览相关文件单独打印日志
|
||||||
if (file.getName().contains(CROP_TEMP_FILE_NAME) || file.getName().contains(CROP_RESULT_FILE_NAME) || file.getName().startsWith("ScaledCompress_")) {
|
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());
|
LogUtils.d(TAG, "【权限管理】关键文件权限设置:文件名=" + file.getName() + ",可写=" + file.canWrite());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,6 +766,24 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增:清理压缩图目录下的旧文件(避免残留,初始化时调用)
|
||||||
|
*/
|
||||||
|
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)");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具方法:验证目录实际写入能力(解决Android14+ canWrite()假阳性问题,适配/Pictures/PowerBell)
|
* 工具方法:验证目录实际写入能力(解决Android14+ canWrite()假阳性问题,适配/Pictures/PowerBell)
|
||||||
* 原理:通过创建临时空文件并删除,验证目录是否真的可写
|
* 原理:通过创建临时空文件并删除,验证目录是否真的可写
|
||||||
@@ -594,9 +862,9 @@ public class BackgroundSourceUtils {
|
|||||||
LogUtils.e(TAG, "【文件管理】目录创建失败:目标目录对象为null");
|
LogUtils.e(TAG, "【文件管理】目录创建失败:目标目录对象为null");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 若target是文件,取其父目录;若本身是目录,直接创建(适配/Pictures/PowerBell目录)
|
// 若target是文件,取其父目录;若本身是目录,直接创建(实例化时已创建,此处二次确认)
|
||||||
File targetDir = target.isFile() ? target.getParentFile() : target;
|
File targetDir = target.isFile() ? target.getParentFile() : target;
|
||||||
createDirWithPermission(targetDir, "空源文件场景-目录创建(/Pictures/PowerBell下)");
|
createDirWithPermission(targetDir, "空源文件场景-目录创建(/Pictures/PowerBell下)", false);
|
||||||
LogUtils.d(TAG, "【文件管理】空源文件场景:目录创建完成,路径=" + targetDir.getAbsolutePath());
|
LogUtils.d(TAG, "【文件管理】空源文件场景:目录创建完成,路径=" + targetDir.getAbsolutePath());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -619,8 +887,12 @@ public class BackgroundSourceUtils {
|
|||||||
String externalFilesPath = mContext.getExternalFilesDir(null) != null ? mContext.getExternalFilesDir(null).getAbsolutePath() : "";
|
String externalFilesPath = mContext.getExternalFilesDir(null) != null ? mContext.getExternalFilesDir(null).getAbsolutePath() : "";
|
||||||
String cachePath = mContext.getCacheDir().getAbsolutePath();
|
String cachePath = mContext.getCacheDir().getAbsolutePath();
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(publicPicturePath) && dirPath.contains(publicPicturePath + File.separator + "PowerBell")) {
|
if (!TextUtils.isEmpty(publicPicturePath)) {
|
||||||
|
if (dirPath.contains(publicPicturePath + File.separator + "PowerBell" + File.separator + COMPRESS_BASE_DIR_NAME)) {
|
||||||
|
return "系统公共图片目录(/Pictures/PowerBell/BackgroundCrops,压缩图统一存储目录)"; // 新增压缩图目录描述
|
||||||
|
} else if (dirPath.contains(publicPicturePath + File.separator + "PowerBell")) {
|
||||||
return "系统公共图片目录(/Pictures/PowerBell,图片存储/裁剪目录)";
|
return "系统公共图片目录(/Pictures/PowerBell,图片存储/裁剪目录)";
|
||||||
|
}
|
||||||
} else if (!TextUtils.isEmpty(externalFilesPath) && dirPath.contains(externalFilesPath)) {
|
} else if (!TextUtils.isEmpty(externalFilesPath) && dirPath.contains(externalFilesPath)) {
|
||||||
return "应用私有外部目录(getExternalFilesDir(),JSON配置目录)";
|
return "应用私有外部目录(getExternalFilesDir(),JSON配置目录)";
|
||||||
} else if (dirPath.contains(cachePath)) {
|
} else if (dirPath.contains(cachePath)) {
|
||||||
@@ -628,9 +900,58 @@ public class BackgroundSourceUtils {
|
|||||||
} else {
|
} else {
|
||||||
return "外部存储目录(非应用私有,权限受限)";
|
return "外部存储目录(非应用私有,权限受限)";
|
||||||
}
|
}
|
||||||
|
return "未知目录";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增:迁移旧压缩图路径到新目录(BackgroundCrops),兼容历史数据
|
||||||
|
* @param bean 要迁移的BackgroundBean(正式/预览)
|
||||||
|
* @param isCurrentBean 是否是正式Bean(用于日志区分)
|
||||||
|
*/
|
||||||
|
private void migrateCompressPathToNewDir(BackgroundBean bean, boolean isCurrentBean) {
|
||||||
|
String oldCompressPath = bean.getBackgroundScaledCompressFilePath();
|
||||||
|
String beanType = isCurrentBean ? "正式Bean" : "预览Bean";
|
||||||
|
|
||||||
|
// 校验:旧路径非空,且不在BackgroundCrops目录下,才需要迁移
|
||||||
|
if (TextUtils.isEmpty(oldCompressPath) || oldCompressPath.contains(fBackgroundCompressDir.getAbsolutePath())) {
|
||||||
|
LogUtils.d(TAG, "【路径迁移】" + beanType + "无需迁移:旧路径为空或已在BackgroundCrops目录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File oldCompressFile = new File(oldCompressPath);
|
||||||
|
if (!oldCompressFile.exists() || !oldCompressFile.isFile() || oldCompressFile.length() <= 0) {
|
||||||
|
LogUtils.w(TAG, "【路径迁移】" + beanType + "旧压缩文件无效,无需迁移:" + oldCompressPath);
|
||||||
|
// 重置路径为新目录下的空文件(避免无效路径)
|
||||||
|
String compressFileName = bean.getBackgroundScaledCompressFileName();
|
||||||
|
if (!TextUtils.isEmpty(compressFileName)) {
|
||||||
|
File newCompressFile = new File(fBackgroundCompressDir, compressFileName);
|
||||||
|
bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath());
|
||||||
|
saveSettings();
|
||||||
|
LogUtils.d(TAG, "【路径迁移】" + beanType + "重置压缩路径到BackgroundCrops:" + newCompressFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移逻辑:复制旧文件到新目录,更新Bean路径,删除旧文件
|
||||||
|
String compressFileName = bean.getBackgroundScaledCompressFileName();
|
||||||
|
if (TextUtils.isEmpty(compressFileName)) {
|
||||||
|
compressFileName = "ScaledCompress_" + System.currentTimeMillis() + ".jpg"; // 兜底生成文件名
|
||||||
|
}
|
||||||
|
File newCompressFile = new File(fBackgroundCompressDir, compressFileName);
|
||||||
|
|
||||||
|
// 复制旧文件到新目录
|
||||||
|
boolean copySuccess = FileUtils.copyFile(oldCompressFile, newCompressFile);
|
||||||
|
if (copySuccess) {
|
||||||
|
// 更新Bean路径为新目录路径
|
||||||
|
bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath());
|
||||||
|
saveSettings();
|
||||||
|
// 删除旧文件(清理残留)
|
||||||
|
clearOldFile(oldCompressFile, beanType + "旧压缩文件(迁移后清理)");
|
||||||
|
LogUtils.d(TAG, "【路径迁移】" + beanType + "压缩路径迁移成功:" + oldCompressPath + " → " + newCompressFile.getAbsolutePath());
|
||||||
|
} else {
|
||||||
|
LogUtils.e(TAG, "【路径迁移】" + beanType + "压缩文件复制失败,迁移终止:" + oldCompressPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ======================================== 核心实现:获取图片旋转角度 ========================================
|
// ======================================== 核心实现:获取图片旋转角度 ========================================
|
||||||
/**
|
/**
|
||||||
@@ -692,19 +1013,19 @@ public class BackgroundSourceUtils {
|
|||||||
|
|
||||||
// ======================================== 图片处理核心方法(压缩/裁剪/保存) ========================================
|
// ======================================== 图片处理核心方法(压缩/裁剪/保存) ========================================
|
||||||
/**
|
/**
|
||||||
* 压缩图片并保存(核心修复:路径非空校验+兜底路径,Java7 手动管理流)
|
* 压缩图片并保存(核心修复:路径非空校验+兜底路径,统一存储到BackgroundCrops目录)
|
||||||
*/
|
*/
|
||||||
public void compressQualityToRecivedPicture(Bitmap bitmap) {
|
public void compressQualityToRecivedPicture(Bitmap bitmap) {
|
||||||
// 兼容裁剪等旧调用:从工具类获取默认压缩路径,转发至重载函数
|
// 兼容裁剪等旧调用:从工具类获取默认压缩路径(统一指向BackgroundCrops),转发至重载函数
|
||||||
String defaultCompressPath = getPreviewBackgroundScaledCompressFilePath();
|
String defaultCompressPath = getPreviewBackgroundScaledCompressFilePath();
|
||||||
compressQualityToRecivedPicture(bitmap, defaultCompressPath);
|
compressQualityToRecivedPicture(bitmap, defaultCompressPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重载方法:指定路径压缩图片并保存(修复:压缩后同步路径到预览Bean)
|
* 重载方法:指定路径压缩图片并保存(修复:压缩后同步路径到预览Bean,统一存储到BackgroundCrops)
|
||||||
* 适配场景:裁剪后生成压缩图,强制绑定路径到预览Bean,避免路径错位
|
* 适配场景:裁剪后生成压缩图,强制绑定路径到预览Bean,避免路径错位
|
||||||
* @param bitmap 待压缩的Bitmap(裁剪后的缩放图)
|
* @param bitmap 待压缩的Bitmap(裁剪后的缩放图)
|
||||||
* @param targetCompressPath 强制指定的压缩目标路径(从预览Bean获取/生成)
|
* @param targetCompressPath 强制指定的压缩目标路径(从预览Bean获取/生成,默认指向BackgroundCrops)
|
||||||
*/
|
*/
|
||||||
public void compressQualityToRecivedPicture(Bitmap bitmap, String targetCompressPath) {
|
public void compressQualityToRecivedPicture(Bitmap bitmap, String targetCompressPath) {
|
||||||
LogUtils.d(TAG, "【压缩启动】开始压缩图片(指定路径),Bitmap状态:" + (bitmap != null && !bitmap.isRecycled()));
|
LogUtils.d(TAG, "【压缩启动】开始压缩图片(指定路径),Bitmap状态:" + (bitmap != null && !bitmap.isRecycled()));
|
||||||
@@ -718,22 +1039,27 @@ public class BackgroundSourceUtils {
|
|||||||
FileOutputStream fos = null;
|
FileOutputStream fos = null;
|
||||||
try {
|
try {
|
||||||
String scaledCompressFilePath = targetCompressPath;
|
String scaledCompressFilePath = targetCompressPath;
|
||||||
// 兜底:若传入路径为空,生成临时压缩路径(兼容异常场景)
|
// 兜底:若传入路径为空,生成BackgroundCrops目录下的临时压缩路径(兼容异常场景)
|
||||||
if (TextUtils.isEmpty(scaledCompressFilePath)) {
|
if (TextUtils.isEmpty(scaledCompressFilePath)) {
|
||||||
LogUtils.e(TAG, "【压缩异常】指定路径为空,使用临时兜底路径");
|
LogUtils.e(TAG, "【压缩异常】指定路径为空,使用BackgroundCrops临时兜底路径");
|
||||||
File tempDir = new File(App.getTempDirPath(), "PreviewCompress"); // 多包名环境下临时目录隔离
|
// 强制在BackgroundCrops目录下生成临时路径(不再用其他目录)
|
||||||
if (!tempDir.exists()) {
|
String tempCompressName = "preview_compress_" + System.currentTimeMillis() + ".jpg";
|
||||||
tempDir.mkdirs();
|
scaledCompressFilePath = new File(fBackgroundCompressDir, tempCompressName).getAbsolutePath();
|
||||||
FileUtils.copyFile(new File(""), tempDir); // 复用原有目录创建逻辑
|
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);
|
||||||
}
|
}
|
||||||
scaledCompressFilePath = new File(tempDir, "preview_compress_" + System.currentTimeMillis() + ".jpg").getAbsolutePath();
|
|
||||||
LogUtils.d(TAG, "【压缩兜底】临时路径:" + scaledCompressFilePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File compressFile = new File(scaledCompressFilePath);
|
File compressFile = new File(scaledCompressFilePath);
|
||||||
LogUtils.d(TAG, "【压缩配置】目标路径:" + scaledCompressFilePath + ",Bitmap原始大小:" + bitmap.getByteCount() / 1024 + "KB");
|
LogUtils.d(TAG, "【压缩配置】目标路径(BackgroundCrops):" + scaledCompressFilePath + ",Bitmap原始大小:" + bitmap.getByteCount() / 1024 + "KB");
|
||||||
|
|
||||||
// 确保父目录存在(兼容Android 10+分区存储,多包名路径适配)
|
// 确保压缩图目录存在(实例化时已创建,此处二次确认)
|
||||||
File parentDir = compressFile.getParentFile();
|
File parentDir = compressFile.getParentFile();
|
||||||
if (parentDir == null) {
|
if (parentDir == null) {
|
||||||
LogUtils.e(TAG, "【压缩异常】父目录为空,无法创建文件");
|
LogUtils.e(TAG, "【压缩异常】父目录为空,无法创建文件");
|
||||||
@@ -743,12 +1069,12 @@ public class BackgroundSourceUtils {
|
|||||||
if (!parentDir.exists()) {
|
if (!parentDir.exists()) {
|
||||||
parentDir.mkdirs();
|
parentDir.mkdirs();
|
||||||
FileUtils.copyFile(new File(""), parentDir);
|
FileUtils.copyFile(new File(""), parentDir);
|
||||||
LogUtils.d(TAG, "【压缩准备】目录已创建:" + parentDir.getAbsolutePath());
|
LogUtils.d(TAG, "【压缩准备】BackgroundCrops目录已创建:" + parentDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理旧的压缩文件(避免文件残留)
|
// 清理旧的压缩文件(避免文件残留)
|
||||||
if (compressFile.exists()) {
|
if (compressFile.exists()) {
|
||||||
clearOldFileByExternal(compressFile, "旧压缩文件");
|
clearOldFileByExternal(compressFile, "旧压缩文件(BackgroundCrops)");
|
||||||
}
|
}
|
||||||
compressFile.createNewFile();
|
compressFile.createNewFile();
|
||||||
|
|
||||||
@@ -768,7 +1094,7 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogUtils.d(TAG, "【压缩结果】" + (compressSuccess ? "成功" : "失败") + ",大小:" + compressFile.length() / 1024 + "KB");
|
LogUtils.d(TAG, "【压缩结果】" + (compressSuccess ? "成功" : "失败") + ",大小:" + compressFile.length() / 1024 + "KB,路径:" + scaledCompressFilePath);
|
||||||
|
|
||||||
// 关键修复:压缩成功后,强制同步路径到预览Bean(双重保障,避免时序错位)
|
// 关键修复:压缩成功后,强制同步路径到预览Bean(双重保障,避免时序错位)
|
||||||
if (compressSuccess) {
|
if (compressSuccess) {
|
||||||
@@ -776,7 +1102,7 @@ public class BackgroundSourceUtils {
|
|||||||
if (previewBean != null) {
|
if (previewBean != null) {
|
||||||
previewBean.setBackgroundScaledCompressFilePath(scaledCompressFilePath);
|
previewBean.setBackgroundScaledCompressFilePath(scaledCompressFilePath);
|
||||||
saveSettings(); // 持久化配置,多包名环境下配置隔离
|
saveSettings(); // 持久化配置,多包名环境下配置隔离
|
||||||
LogUtils.d(TAG, "【压缩路径同步】已绑定到预览Bean:" + scaledCompressFilePath);
|
LogUtils.d(TAG, "【压缩路径同步】已绑定到预览Bean(BackgroundCrops):" + scaledCompressFilePath);
|
||||||
} else {
|
} else {
|
||||||
LogUtils.e(TAG, "【压缩路径同步失败】预览Bean为空");
|
LogUtils.e(TAG, "【压缩路径同步失败】预览Bean为空");
|
||||||
}
|
}
|
||||||
@@ -809,4 +1135,3 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user