调试信息准备

This commit is contained in:
2025-12-11 19:34:47 +08:00
parent f240d9c057
commit 10ddca4f73

View File

@@ -7,12 +7,10 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.BuildConfig; import cc.winboll.studio.powerbell.BuildConfig;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.model.BackgroundBean; import cc.winboll.studio.powerbell.model.BackgroundBean;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
@@ -32,54 +30,44 @@ public class BackgroundSourceUtils {
public static final String TAG = "BackgroundSourceUtils"; public static final String TAG = "BackgroundSourceUtils";
// 裁剪相关常量(统一定义,避免硬编码) // 裁剪相关常量(统一定义,避免硬编码)
private static final String CROP_CACHE_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_TEMP_FILE_NAME = "SourceCropTemp.jpg";
private static final String CROP_RESULT_FILE_NAME = "SourceCropped.jpg"; // 裁剪输出结果文件 private static final String CROP_RESULT_FILE_NAME = "SourceCropped.jpg";
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 SOURCE_DIR_NAME = "BackgroundSource"; private static final String SOURCE_DIR_NAME = "BackgroundSource";
private static final String COMPRESS_DIR_NAME = "BackgroundCompress"; private static final String COMPRESS_DIR_NAME = "BackgroundCompress";
// 1. 静态实例加volatile禁止指令重排保证可见性双重校验锁单例核心 // 单例相关
private static volatile BackgroundSourceUtils sInstance; private static volatile BackgroundSourceUtils sInstance;
private Context mContext; private Context mContext;
private File currentBackgroundBeanFile; private File currentBackgroundBeanFile;
private BackgroundBean currentBackgroundBean; // 正式Bean独立实例 private BackgroundBean currentBackgroundBean;
private File previewBackgroundBeanFile; private File previewBackgroundBeanFile;
private BackgroundBean previewBackgroundBean; // 预览Bean独立实例与正式Bean完全分离 private BackgroundBean previewBackgroundBean;
// 2. 统一文件目录分两类图片目录→系统公共目录JSON目录→应用外置存储 // 目录文件相关
// 图片操作目录(系统公共目录:/storage/emulated/0/Pictures/PowerBell/ private File fPictureBaseDir;
private File fPictureBaseDir; // 图片基础目录 private File fCropCacheDir;
private File fCropCacheDir; // 裁剪缓存目录(基础目录下/cache private File fBackgroundSourceDir;
private File fBackgroundSourceDir; // 图片存储目录(基础目录下,存储正式/预览原图) private File fBackgroundCompressDir;
private File fBackgroundCompressDir; // 新增:压缩图统一存储目录(基础目录下/BackgroundCrops private File fUtilsDir;
// JSON配置目录原应用外置存储目录不改变 private File fModelDir;
private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundSourceUtils private File mCropSourceFile;
private File fModelDir; // 模型文件目录存储JSON配置 private File mCropResultFile;
// 裁剪文件统一放入图片基础目录下的cache
private File mCropSourceFile; // 裁剪临时文件fCropCacheDir下
private File mCropResultFile; // 裁剪临时文件fCropCacheDir下
// 3. 私有构造器(加防反射逻辑+初始化所有目录/文件) // 双重校验锁单例
private BackgroundSourceUtils(Context context) { private BackgroundSourceUtils(Context context) {
// 防反射破坏:若已有实例,抛异常阻止重复创建
if (sInstance != null) { if (sInstance != null) {
throw new RuntimeException("BackgroundSourceUtils 是单例类,禁止重复创建!"); throw new RuntimeException("BackgroundSourceUtils 是单例类,禁止重复创建!");
} }
// 上下文用Application Context避免Activity内存泄漏
this.mContext = context.getApplicationContext(); this.mContext = context.getApplicationContext();
// 【核心调整1】实例化初期优先初始化所有必要目录确保实例化完成时目录100%就绪)
initNecessaryDirs(); initNecessaryDirs();
// 初始化所有文件(裁剪临时文件/结果文件等)
initAllFiles(); initAllFiles();
// 加载配置(确保正式/预览Bean是两份独立实例
loadSettings(); loadSettings();
} }
// 4. 双重校验锁单例线程安全高效支持多线程并发调用Java7语法兼容
public static BackgroundSourceUtils getInstance(Context context) { public static BackgroundSourceUtils getInstance(Context context) {
if (sInstance == null) { if (sInstance == null) {
synchronized (BackgroundSourceUtils.class) { synchronized (BackgroundSourceUtils.class) {
@@ -92,161 +80,109 @@ public class BackgroundSourceUtils {
} }
/** /**
* 【核心新增】统一初始化所有必要目录(实例化初期调用,确保目录优先创建) * 统一初始化所有必要目录
* 整合图片目录+JSON目录集中管理目录创建逻辑保证实例化完成时所有目录就绪
*/ */
private void initNecessaryDirs() { private void initNecessaryDirs() {
LogUtils.d(TAG, "实例化初期-目录初始化】开始创建所有必要目录..."); LogUtils.d(TAG, "【目录初始化】开始创建所有必要目录");
// 1. 初始化图片操作目录(系统公共目录 /Pictures/PowerBell/
initPictureDirs(); initPictureDirs();
// 2. 初始化JSON配置目录应用外置存储
initJsonDirs(); initJsonDirs();
LogUtils.d(TAG, "实例化初期-目录初始化】所有必要目录创建完成"); LogUtils.d(TAG, "【目录初始化】所有必要目录创建完成");
} }
/** /**
* 初始化图片操作目录(核心:系统公共图片目录 /Pictures/PowerBell/,新增压缩图目录) * 初始化图片操作目录
* 【调整强化】新增目录创建后二次校验,失败则降级到备选目录,确保目录可用
*/ */
private void initPictureDirs() { private void initPictureDirs() {
// 1. 图片基础目录:/storage/emulated/0/Pictures/PowerBell
fPictureBaseDir = new File(PICTURE_BASE_DIR); fPictureBaseDir = new File(PICTURE_BASE_DIR);
// 2. 图片存储目录:基础目录下(存储正式/预览原图)
fBackgroundSourceDir = new File(fPictureBaseDir, SOURCE_DIR_NAME); fBackgroundSourceDir = new File(fPictureBaseDir, SOURCE_DIR_NAME);
// 3. 裁剪缓存目录:基础目录下/cache所有裁剪操作在此目录
fCropCacheDir = new File(fPictureBaseDir, CROP_CACHE_DIR_NAME); fCropCacheDir = new File(fPictureBaseDir, CROP_CACHE_DIR_NAME);
// 4. 新增:压缩图统一存储目录(基础目录下/BackgroundCrops所有压缩图放这里
fBackgroundCompressDir = new File(fPictureBaseDir, COMPRESS_DIR_NAME); fBackgroundCompressDir = new File(fPictureBaseDir, COMPRESS_DIR_NAME);
// 5. 强制创建所有图片目录(带二次校验+降级兜底) createDirWithPermission(fPictureBaseDir, "图片基础目录");
createDirWithPermission(fPictureBaseDir, "图片基础目录(" + PICTURE_BASE_DIR + ""); createDirWithPermission(fBackgroundSourceDir, "图片存储目录");
createDirWithPermission(fBackgroundSourceDir, "图片存储目录(基础目录下/" + SOURCE_DIR_NAME + ""); createDirWithPermission(fCropCacheDir, "裁剪缓存目录");
createDirWithPermission(fCropCacheDir, "裁剪缓存目录(基础目录/" + CROP_CACHE_DIR_NAME + ""); createDirWithPermission(fBackgroundCompressDir, "压缩图存储目录");
createDirWithPermission(fBackgroundCompressDir, "裁剪压缩图存储目录(基础目录/" + COMPRESS_DIR_NAME + "");
// 6. 目录创建后最终校验(确保所有目录已就绪)
validatePictureDirs(); validatePictureDirs();
LogUtils.d(TAG, "【图片目录初始化】完成:" +
"基础目录=" + fPictureBaseDir.getAbsolutePath() +
"图片存储目录=" + fBackgroundSourceDir.getAbsolutePath() +
",裁剪缓存目录=" + fCropCacheDir.getAbsolutePath() +
",裁剪压缩图存储目录=" + fBackgroundCompressDir.getAbsolutePath());
} }
/** /**
* 初始化JSON配置目录(保留原逻辑:应用外置存储) * 初始化JSON配置目录
* 【调整强化】新增目录创建后二次校验,失败则降级到应用内部缓存目录
*/ */
private void initJsonDirs() { private void initJsonDirs() {
// 1. 工具类根目录(应用外置存储)
fUtilsDir = mContext.getExternalFilesDir(TAG); fUtilsDir = mContext.getExternalFilesDir(TAG);
if (fUtilsDir == null) { if (fUtilsDir == null) {
LogUtils.e(TAG, "【JSON目录】应用外置存储不可用,切换到应用内部缓存目录"); LogUtils.e(TAG, "应用外置存储不可用,切换到应用内部缓存目录");
fUtilsDir = mContext.getDataDir(); fUtilsDir = mContext.getDataDir();
} }
// 2. 模型文件目录存储JSON配置
fModelDir = new File(fUtilsDir, "ModelDir"); fModelDir = new File(fUtilsDir, "ModelDir");
// 强制创建JSON目录带二次校验+降级兜底) createDirWithPermission(fModelDir, "JSON配置目录");
createDirWithPermission(fModelDir, "JSON配置目录应用外置存储");
// 3. 初始化JSON文件对象两份独立文件对应两份Bean实例
currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json"); currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json");
previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json"); previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json");
LogUtils.d(TAG, "【JSON目录初始化】完成目录=" + fModelDir.getAbsolutePath() + "正式JSON=" + currentBackgroundBeanFile.getName() + "预览JSON=" + previewBackgroundBeanFile.getName());
} }
/** /**
* 【核心强化】创建目录并设置权限(适配系统公共目录/Pictures/PowerBell确保实例化时目录就绪 * 创建目录并校验
* @param dir 要创建的目录
* @param dirDesc 目录描述(用于日志打印)
*/ */
private void createDirWithPermission(File dir, String dirDesc) { private void createDirWithPermission(File dir, String dirDesc) {
if (dir == null) { if (dir == null) {
LogUtils.e(TAG, "【文件管理】创建目录失败目录对象为null描述" + dirDesc + ""); LogUtils.e(TAG, dirDesc + "创建失败目录对象为null");
return; return;
} }
// 第一步:主动检测并创建目录(递归创建所有父目录)
if (!dir.exists()) { if (!dir.exists()) {
LogUtils.d(TAG, "【文件管理】" + dirDesc + "不存在,开始创建:" + dir.getAbsolutePath()); LogUtils.d(TAG, dirDesc + "不存在,开始创建:" + dir.getAbsolutePath());
dir.mkdirs(); // 递归创建所有父目录 dir.mkdirs();
} else {
LogUtils.d(TAG, "【文件管理】" + dirDesc + "已存在:" + dir.getAbsolutePath());
} }
} }
/** /**
* 【新增】图片目录创建后最终校验(确保实例化时所有图片目录就绪 * 校验图片目录是否就绪
*/ */
private void validatePictureDirs() { private void validatePictureDirs() {
LogUtils.d(TAG, "【图片目录校验】开始校验所有图片目录..."); boolean allReady = fPictureBaseDir.exists() && fBackgroundSourceDir.exists()
boolean allReady = true; && fCropCacheDir.exists() && fBackgroundCompressDir.exists();
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 (!fCropCacheDir.exists() || !fCropCacheDir.isDirectory()) {
LogUtils.e(TAG, "【图片目录校验】裁剪缓存目录未就绪:" + fCropCacheDir.getAbsolutePath());
allReady = false;
}
if (!fBackgroundCompressDir.exists() || !fBackgroundCompressDir.isDirectory()) {
LogUtils.e(TAG, "【图片目录校验】压缩图目录未就绪:" + fBackgroundCompressDir.getAbsolutePath());
allReady = false;
}
if (allReady) { if (allReady) {
LogUtils.d(TAG, "【图片目录校验】所有图片目录均已就绪"); LogUtils.d(TAG, "所有图片目录均已就绪");
} else { } else {
LogUtils.e(TAG, "【图片目录校验】部分目录未就绪,可能影响后续功能"); LogUtils.e(TAG, "部分图片目录未就绪,可能影响后续功能");
} }
} }
/** /**
* 初始化所有文件(裁剪文件→图片缓存目录,结果文件→图片存储目录) * 初始化所有文件
*/ */
private void initAllFiles() { private void initAllFiles() {
// 1. 裁剪临时文件
//mCropSourceFile = new File(fCropCacheDir, CROP_TEMP_FILE_NAME);
// 2. 裁剪结果文件
//cropResultFile = new File(fCropCacheDir, CROP_RESULT_FILE_NAME);
// 新增:清理压缩图目录下的旧文件(避免残留)
clearCropTempFiles(); clearCropTempFiles();
LogUtils.d(TAG, "文件初始化完成"); LogUtils.d(TAG, "文件初始化完成");
} }
// 【核心实现】定义 getFileProviderUri 方法:将 File 转为 ContentUri适配 FileProvider /**
* 将File转为ContentUri
*/
public Uri getFileProviderUri(File file) { public Uri getFileProviderUri(File file) {
Log.d("BackgroundSourceUtils", "getFileProviderUri: 生成FileProvider Uri文件路径:" + file.getAbsolutePath()); LogUtils.d(TAG, "getFileProviderUri调用】文件路径:" + file.getAbsolutePath());
Uri contentUri = null; Uri contentUri = null;
try { try {
// 适配 Android 7.0+:使用 FileProvider 生成 ContentUri避免 FileUriExposedException
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
contentUri = FileProvider.getUriForFile( contentUri = FileProvider.getUriForFile(mContext, FILE_PROVIDER_AUTHORITY, file);
mContext, LogUtils.d(TAG, "7.0+ 生成ContentUri" + contentUri.toString());
FILE_PROVIDER_AUTHORITY, // 与清单文件中一致
file
);
Log.d("BackgroundSourceUtils", "getFileProviderUri: 7.0+ 生成ContentUri" + contentUri.toString());
} else { } else {
// 适配 Android 7.0 以下:直接使用 File.toURI()(兼容旧版本)
contentUri = Uri.fromFile(file); contentUri = Uri.fromFile(file);
Log.d("BackgroundSourceUtils", "getFileProviderUri: 7.0以下 生成FileUri" + contentUri.toString()); LogUtils.d(TAG, "7.0以下 生成FileUri" + contentUri.toString());
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// 捕获异常(如文件路径无效、授权不匹配等) LogUtils.e(TAG, "生成Uri失败" + e.getMessage(), e);
Log.e("BackgroundSourceUtils", "getFileProviderUri: 生成Uri失败异常" + e.getMessage(), e);
contentUri = null; contentUri = null;
} }
return contentUri; return contentUri;
} }
/**
* 检查背景是否为空并创建空白背景Bean
*/
boolean checkEmptyBackgroundAndCreateBlankBackgroundBean(BackgroundBean checkBackgroundBean) { boolean checkEmptyBackgroundAndCreateBlankBackgroundBean(BackgroundBean checkBackgroundBean) {
LogUtils.d(TAG, "【checkEmptyBackgroundAndCreateBlankBackgroundBean调用】开始检查背景Bean");
File fCheckBackgroundFile = new File(checkBackgroundBean.getBackgroundFilePath()); File fCheckBackgroundFile = new File(checkBackgroundBean.getBackgroundFilePath());
if (!fCheckBackgroundFile.exists()) { if (!fCheckBackgroundFile.exists()) {
String newCropFileName = "blank10x10"; String newCropFileName = "blank10x10";
@@ -261,40 +197,36 @@ public class BackgroundSourceUtils {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
} }
// 加载图片数据模型数据
loadSettings(); loadSettings();
// 修改预览数据模型
previewBackgroundBean.setIsUseBackgroundFile(true); previewBackgroundBean.setIsUseBackgroundFile(true);
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(false); previewBackgroundBean.setIsUseBackgroundScaledCompressFile(false);
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName()); previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());
previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath()); previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath());
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName()); previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath()); previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
// 保存数据模型数据更改
saveSettings(); saveSettings();
LogUtils.d(TAG, "背景Bean为空已创建空白背景并更新配置");
return true; return true;
} }
LogUtils.d(TAG, "背景Bean文件存在无需创建空白背景");
return false; return false;
} }
/* /**
* 创建预览数据剪裁环境 * 创建并更新预览剪裁环境
*/ */
public boolean createAndUpdatePreviewEnvironmentForCropping(BackgroundBean oldPreviewBackgroundBean) { public boolean createAndUpdatePreviewEnvironmentForCropping(BackgroundBean oldPreviewBackgroundBean) {
LogUtils.d(TAG, "【createAndUpdatePreviewEnvironmentForCropping调用】开始初始化预览剪裁环境");
InputStream is = null; InputStream is = null;
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
clearCropTempFiles(); clearCropTempFiles();
if (checkEmptyBackgroundAndCreateBlankBackgroundBean(oldPreviewBackgroundBean)) { if (checkEmptyBackgroundAndCreateBlankBackgroundBean(oldPreviewBackgroundBean)) {
LogUtils.d(TAG, "空白背景创建成功,直接返回");
return true; return true;
} }
Uri uri = UriUtil.getUriForFile(mContext, oldPreviewBackgroundBean.getBackgroundFilePath()); Uri uri = UriUtil.getUriForFile(mContext, oldPreviewBackgroundBean.getBackgroundFilePath());
//String szType = mContext.getContentResolver().getType(uri);
// 2. 截取MIME类型后缀如从image/jpeg中提取jpeg【核心新增逻辑】
String fileSuffix = FileUtils.getFileSuffix(mContext, uri); String fileSuffix = FileUtils.getFileSuffix(mContext, uri);
String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis(); String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis();
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix); mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
@@ -310,103 +242,80 @@ public class BackgroundSourceUtils {
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundFilePath()), mCropSourceFile); FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
} else { } else {
mCropSourceFile.createNewFile(); mCropSourceFile.createNewFile();
// 1. 打开Uri输入流兼容content:///file:// 等多种Uri格式
is = mContext.getContentResolver().openInputStream(uri); is = mContext.getContentResolver().openInputStream(uri);
if (is == null) { if (is == null) {
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败Uri" + uri.toString()); LogUtils.e(TAG, "ContentResolver打开Uri失败" + uri.toString());
return false; return false;
} }
// 2. 初始化选图临时文件输出流Java7 手动创建流不依赖try-with-resources
fos = new FileOutputStream(mCropSourceFile); fos = new FileOutputStream(mCropSourceFile);
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区平衡读写性能与内存占用 byte[] buffer = new byte[1024 * 8];
int readLen; // 每次读取的字节长度 int readLen;
// 3. 流复制Java7 标准while循环避免Java8+语法)
while ((readLen = is.read(buffer)) != -1) { while ((readLen = is.read(buffer)) != -1) {
fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充 fos.write(buffer, 0, readLen);
} }
// 4. 强制同步写入磁盘解决Android 10+ 异步写入导致的文件无效问题)
fos.flush(); fos.flush();
if (fos != null) {
try { try {
fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存 fos.getFD().sync();
} catch (IOException e) { } catch (IOException e) {
LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,flush()兜底:" + e.getMessage()); LogUtils.w(TAG, "文件同步到磁盘失败flush兜底" + e.getMessage());
fos.flush(); fos.flush();
} }
} }
}
// 加载图片数据模型数据
loadSettings(); loadSettings();
// 修改预览数据模型
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName()); previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());
previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath()); previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath());
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName()); previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath()); previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
// 保存数据模型数据更改
saveSettings(); saveSettings();
// 6. 解析成功日志(打印文件信息,便于问题排查) LogUtils.d(TAG, "预览剪裁环境初始化成功");
LogUtils.d(TAG, "【选图解析】Uri解析成功");
LogUtils.d(TAG, "→ 原Uri" + uri.toString()); LogUtils.d(TAG, "→ 原Uri" + uri.toString());
LogUtils.d(TAG, "图片剪裁数据源:" + mCropSourceFile.getAbsolutePath()); LogUtils.d(TAG, "→ 剪裁数据源:" + mCropSourceFile.getAbsolutePath());
LogUtils.d(TAG, "图片剪裁数据源文件大小" + mCropSourceFile.length() + " bytes"); LogUtils.d(TAG, "剪裁结果文件" + mCropResultFile.getAbsolutePath());
LogUtils.d(TAG, "→ 剪裁结果数据文件:" + mCropResultFile.getAbsolutePath());
LogUtils.d(TAG, "→ 剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes");
return true; return true;
} catch (Exception e) { } catch (Exception e) {
// 捕获所有异常IO异常/空指针等),避免崩溃 LogUtils.e(TAG, "预览剪裁环境初始化异常:" + e.getMessage(), e);
LogUtils.e(TAG, "【选图解析】流复制异常:" + e.getMessage(), e);
// 异常时清理无效文件,防止残留
clearCropTempFiles(); clearCropTempFiles();
return false; return false;
} finally { } finally {
// 7. 手动关闭流资源Java7 标准写法,避免内存泄漏)
if (is != null) { if (is != null) {
try { try {
is.close(); is.close();
} catch (IOException e) { } catch (IOException e) {
LogUtils.e(TAG, "【选图解析】输入流关闭失败:" + e.getMessage()); LogUtils.e(TAG, "输入流关闭失败:" + e.getMessage());
} }
} }
if (fos != null) { if (fos != null) {
try { try {
fos.close(); fos.close();
} catch (IOException e) { } catch (IOException e) {
LogUtils.e(TAG, "【选图解析】输出流关闭失败:" + e.getMessage()); LogUtils.e(TAG, "输出流关闭失败:" + e.getMessage());
} }
} }
} }
} }
/** /**
* 加载背景图片配置数据核心确保current/preview是两份独立的BackgroundBean实例 * 加载背景配置
*/ */
public void loadSettings() { public void loadSettings() {
// 1. 加载正式Bean独立实例从currentBackgroundBean.json加载不存在则新建
currentBackgroundBean = BackgroundBean.loadBeanFromFile(currentBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class); currentBackgroundBean = BackgroundBean.loadBeanFromFile(currentBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
if (currentBackgroundBean == null) { if (currentBackgroundBean == null) {
currentBackgroundBean = new BackgroundBean(); // 正式Bean独立实例初始化 currentBackgroundBean = new BackgroundBean();
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在创建独立实例并保存到JSON"); LogUtils.d(TAG, "正式背景Bean不存在创建新实例");
} }
// 2. 加载预览Bean独立实例从previewBackgroundBean.json加载不存在则新建与正式Bean完全分离
previewBackgroundBean = BackgroundBean.loadBeanFromFile(previewBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class); previewBackgroundBean = BackgroundBean.loadBeanFromFile(previewBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
if (previewBackgroundBean == null) { if (previewBackgroundBean == null) {
previewBackgroundBean = new BackgroundBean(); // 预览Bean独立实例初始化 previewBackgroundBean = new BackgroundBean();
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在创建独立实例并保存到JSON"); LogUtils.d(TAG, "预览背景Bean不存在创建新实例");
} }
} }
// ------------------------------ 对外提供的核心方法(路径已适配新目录)------------------------------ // ------------------------------ 对外提供的核心方法 ------------------------------
public BackgroundBean getCurrentBackgroundBean() { public BackgroundBean getCurrentBackgroundBean() {
return currentBackgroundBean; return currentBackgroundBean;
} }
@@ -415,87 +324,43 @@ public class BackgroundSourceUtils {
return previewBackgroundBean; return previewBackgroundBean;
} }
/**
* 获取正式背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
*/
// public String getCurrentBackgroundFilePath() {
// String fileName = currentBackgroundBean.getBackgroundFileName();
// if (TextUtils.isEmpty(fileName)) {
// LogUtils.e(TAG, "【路径管理】正式背景文件名为空,返回空路径");
// return "";
// }
// File file = new File(fBackgroundSourceDir, fileName);
// LogUtils.d(TAG, "【路径管理】正式背景路径:" + file.getAbsolutePath());
// return file.getAbsolutePath();
// }
//
// /**
// * 获取预览背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
// */
// public String getPreviewBackgroundFilePath() {
// String fileName = previewBackgroundBean.getBackgroundFileName();
// if (TextUtils.isEmpty(fileName)) {
// LogUtils.e(TAG, "【路径管理】预览背景文件名为空,返回空路径");
// return "";
// }
// File file = new File(fBackgroundSourceDir, fileName);
// LogUtils.d(TAG, "【路径管理】预览背景路径:" + file.getAbsolutePath());
// return file.getAbsolutePath();
// }
/**
* 获取预览背景压缩图片路径(同步修复:移除 loadSettings()强化非空校验统一指向BackgroundCrops目录
*/
public String getPreviewBackgroundScaledCompressFilePath() { public String getPreviewBackgroundScaledCompressFilePath() {
String compressFileName = previewBackgroundBean.getBackgroundScaledCompressFileName(); String compressFileName = previewBackgroundBean.getBackgroundScaledCompressFileName();
if (TextUtils.isEmpty(compressFileName)) { if (TextUtils.isEmpty(compressFileName)) {
LogUtils.e(TAG, "【路径管理】预览压缩背景文件名为空,返回空路径"); LogUtils.e(TAG, "预览压缩背景文件名为空");
return ""; return "";
} }
// 关键压缩图路径统一指向BackgroundCrops目录不再用BackgroundSource
File file = new File(fBackgroundCompressDir, compressFileName); File file = new File(fBackgroundCompressDir, compressFileName);
LogUtils.d(TAG, "【路径管理】预览压缩背景路径BackgroundCrops目录" + file.getAbsolutePath());
return file.getAbsolutePath(); return file.getAbsolutePath();
} }
/**
* 新增获取正式背景压缩图片路径统一指向BackgroundCrops目录对外提供调用
*/
public String getCurrentBackgroundScaledCompressFilePath() { public String getCurrentBackgroundScaledCompressFilePath() {
String compressFileName = currentBackgroundBean.getBackgroundScaledCompressFileName(); String compressFileName = currentBackgroundBean.getBackgroundScaledCompressFileName();
if (TextUtils.isEmpty(compressFileName)) { if (TextUtils.isEmpty(compressFileName)) {
LogUtils.e(TAG, "【路径管理】正式压缩背景文件名为空,返回空路径"); LogUtils.e(TAG, "正式压缩背景文件名为空");
return ""; return "";
} }
// 关键压缩图路径统一指向BackgroundCrops目录
File file = new File(fBackgroundCompressDir, compressFileName); File file = new File(fBackgroundCompressDir, compressFileName);
LogUtils.d(TAG, "【路径管理】正式压缩背景路径BackgroundCrops目录" + file.getAbsolutePath());
return file.getAbsolutePath(); return file.getAbsolutePath();
} }
/** /**
* 保存配置核心将两份独立Bean实例分别写入各自的JSON文件 * 保存配置
*/ */
public void saveSettings() { public void saveSettings() {
if (currentBackgroundBean != null && previewBackgroundBean != null) { if (currentBackgroundBean != null && previewBackgroundBean != null) {
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); // 正式Bean→正式JSON BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); // 预览Bean→预览JSON BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "【配置管理】两份配置保存成功正式JSON=" + currentBackgroundBeanFile.getAbsolutePath() + "预览JSON=" + previewBackgroundBeanFile.getAbsolutePath()); LogUtils.d(TAG, "两份背景配置保存成功");
return; } else {
LogUtils.e(TAG, "配置保存失败current/preview Bean存在空值");
} }
LogUtils.d(TAG, "【配置管理】两份配置保存失败。currentBackgroundBean 与 previewBackgroundBean 有空值。");
} }
/**
* 获取图片基础目录路径(对外提供:/Pictures/PowerBell/
*/
public String getBackgroundSourceDirPath() { public String getBackgroundSourceDirPath() {
return fBackgroundSourceDir.getAbsolutePath(); return fBackgroundSourceDir.getAbsolutePath();
} }
/**
* 新增:获取压缩图统一存储目录路径(对外提供:/Pictures/PowerBell/BackgroundCrops/
*/
public String getBackgroundCompressDirPath() { public String getBackgroundCompressDirPath() {
return fBackgroundCompressDir.getAbsolutePath(); return fBackgroundCompressDir.getAbsolutePath();
} }
@@ -508,75 +373,60 @@ public class BackgroundSourceUtils {
return FILE_PROVIDER_AUTHORITY; return FILE_PROVIDER_AUTHORITY;
} }
// ------------------------------ 核心业务方法复用FileUtils简化文件操作------------------------------ // ------------------------------ 核心业务方法 ------------------------------
/** /**
* 优化函数仅裁剪结果图可保存到BackgroundSource避免启动裁剪时误复制原图 * 保存裁剪结果图到预览Bean
* 说明:启动裁剪时不调用此方法,仅在裁剪完成后保存结果图时调用
*/ */
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) { public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
final String TAG = "BackgroundSourceUtils"; LogUtils.d(TAG, "【saveFileToPreviewBean调用】源文件路径" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null"));
// 强化校验1仅允许裁剪结果图传入通过文件路径判断避免原图误传入
if (sourceFile == null || !sourceFile.exists() || sourceFile.length() <= 0) { if (sourceFile == null || !sourceFile.exists() || sourceFile.length() <= 0) {
Log.e(TAG, "【保存优化】源文件无效,拒绝保存" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null")); LogUtils.e(TAG, "源文件无效,拒绝保存");
return previewBackgroundBean; return previewBackgroundBean;
} }
// 强化校验2排除原图路径避免启动裁剪时传入原图复制 String originalImageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath();
String originalImageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(); // 原图存储目录(如相册/拍照目录)
if (sourceFile.getAbsolutePath().contains(originalImageDir)) { if (sourceFile.getAbsolutePath().contains(originalImageDir)) {
Log.w(TAG, "【保存优化】禁止复制原图到BackgroundSource,跳过保存"); LogUtils.w(TAG, "禁止复制原图,跳过保存");
return previewBackgroundBean; return previewBackgroundBean;
} }
// 确保BackgroundSource目录存在(实例化时已创建,此处二次确认) if (!fBackgroundSourceDir.exists() && !fBackgroundSourceDir.mkdirs()) {
if (!fBackgroundSourceDir.exists()) { LogUtils.e(TAG, "BackgroundSource目录创建失败");
if (!fBackgroundSourceDir.mkdirs()) {
Log.e(TAG, "【保存优化】BackgroundSource目录创建失败");
return previewBackgroundBean; return previewBackgroundBean;
} }
}
// 生成唯一文件名(避免覆盖)
String uniqueFileName = "bg_" + System.currentTimeMillis() + "_" + sourceFile.getName(); String uniqueFileName = "bg_" + System.currentTimeMillis() + "_" + sourceFile.getName();
File targetFile = new File(fBackgroundSourceDir, uniqueFileName); File targetFile = new File(fBackgroundSourceDir, uniqueFileName);
// 执行复制(仅裁剪结果图会走到这一步)
if (FileUtils.copyFile(sourceFile, targetFile)) { if (FileUtils.copyFile(sourceFile, targetFile)) {
Log.d(TAG, "【保存优化】裁剪结果图保存成功:" + targetFile.getAbsolutePath()); LogUtils.d(TAG, "裁剪结果图保存成功:" + targetFile.getAbsolutePath());
// 更新预览Bean原有逻辑保留
previewBackgroundBean.setBackgroundFileName(uniqueFileName); previewBackgroundBean.setBackgroundFileName(uniqueFileName);
previewBackgroundBean.setBackgroundFilePath(targetFile.getAbsolutePath()); previewBackgroundBean.setBackgroundFilePath(targetFile.getAbsolutePath());
previewBackgroundBean.setBackgroundFileInfo(fileInfo); previewBackgroundBean.setBackgroundFileInfo(fileInfo);
previewBackgroundBean.setIsUseBackgroundFile(true); previewBackgroundBean.setIsUseBackgroundFile(true);
// 保存Bean到本地原有逻辑保留
saveSettings(); saveSettings();
} else { } else {
Log.e(TAG, "【保存优化】裁剪结果图复制失败" + sourceFile.getAbsolutePath() + "" + targetFile.getAbsolutePath()); LogUtils.e(TAG, "裁剪结果图复制失败");
} }
return previewBackgroundBean; return previewBackgroundBean;
} }
/** /**
* 提交预览背景到正式背景预览Bean → 正式Bean深拷贝新建正式Bean实例+逐字段拷贝) * 提交预览背景到正式背景
* 核心深拷贝后修改正式Bean不会影响预览Bean两份实例完全独立压缩图路径统一指向BackgroundCrops
*/ */
public void commitPreviewSourceToCurrent() { public void commitPreviewSourceToCurrent() {
// 深拷贝第一步新建正式Bean独立实例彻底脱离预览Bean的引用 LogUtils.d(TAG, "【commitPreviewSourceToCurrent调用】开始深拷贝预览Bean到正式Bean");
currentBackgroundBean = new BackgroundBean(); currentBackgroundBean = new BackgroundBean();
// 深拷贝第二步逐字段拷贝预览Bean的所有值压缩图路径同步指向BackgroundCrops
currentBackgroundBean.setBackgroundFileName(previewBackgroundBean.getBackgroundFileName()); currentBackgroundBean.setBackgroundFileName(previewBackgroundBean.getBackgroundFileName());
currentBackgroundBean.setBackgroundFilePath(previewBackgroundBean.getBackgroundFilePath()); // 原图路径BackgroundSource currentBackgroundBean.setBackgroundFilePath(previewBackgroundBean.getBackgroundFilePath());
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()); // 压缩图路径BackgroundCrops currentBackgroundBean.setBackgroundScaledCompressFilePath(previewBackgroundBean.getBackgroundScaledCompressFilePath());
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());
// 拷贝一份缓存图片文件到正式背景文件夹
String previewFileName = previewBackgroundBean.getBackgroundFileName(); String previewFileName = previewBackgroundBean.getBackgroundFileName();
String previewCropFileName = previewBackgroundBean.getBackgroundScaledCompressFileName(); String previewCropFileName = previewBackgroundBean.getBackgroundScaledCompressFileName();
File previewFile = new File(previewBackgroundBean.getBackgroundFilePath()); File previewFile = new File(previewBackgroundBean.getBackgroundFilePath());
@@ -585,42 +435,37 @@ public class BackgroundSourceUtils {
File currentCropFile = new File(fBackgroundCompressDir, previewCropFileName); File currentCropFile = new File(fBackgroundCompressDir, previewCropFileName);
FileUtils.copyFile(previewFile, currentFile); FileUtils.copyFile(previewFile, currentFile);
FileUtils.copyFile(previewCropFile, currentCropFile); FileUtils.copyFile(previewCropFile, currentCropFile);
// 更新当前背景文件路径 currentBackgroundBean.setBackgroundFilePath(currentFile.getAbsolutePath());
currentBackgroundBean.setBackgroundFilePath(currentFile.getAbsolutePath()); // 原图路径BackgroundSource currentBackgroundBean.setBackgroundScaledCompressFilePath(currentCropFile.getAbsolutePath());
currentBackgroundBean.setBackgroundScaledCompressFilePath(currentCropFile.getAbsolutePath()); // 压缩图路径BackgroundCrops
saveSettings(); // 分别保存正式Bean→currentJSON预览Bean→previewJSON两份独立 saveSettings();
LogUtils.d(TAG, "【配置管理】预览背景深拷贝到正式Bean两份实例独立压缩图统一存储到BackgroundCrops"); LogUtils.d(TAG, "预览背景提交到正式背景成功,两份实例完全独立");
ToastUtils.show("背景图片应用成功"); ToastUtils.show("背景图片应用成功");
} }
/** /**
* 将正式背景同步到预览背景正式Bean → 预览Bean深拷贝新建预览Bean实例+逐字段拷贝) * 将正式背景同步到预览背景
* 核心深拷贝后修改预览Bean不会影响正式Bean两份实例完全独立压缩图路径统一指向BackgroundCrops
*/ */
public void setCurrentSourceToPreview() { public void setCurrentSourceToPreview() {
LogUtils.d(TAG, "正在初始化预览数据,setCurrentSourceToPreview()"); LogUtils.d(TAG, "setCurrentSourceToPreview调用】开始深拷贝正式Bean到预览Bean");
// 深拷贝第一步新建预览Bean独立实例彻底脱离正式Bean的引用
previewBackgroundBean = new BackgroundBean(); previewBackgroundBean = new BackgroundBean();
// 深拷贝第二步逐字段拷贝正式Bean的所有值压缩图路径同步指向BackgroundCrops
previewBackgroundBean.setBackgroundFileName(currentBackgroundBean.getBackgroundFileName()); previewBackgroundBean.setBackgroundFileName(currentBackgroundBean.getBackgroundFileName());
previewBackgroundBean.setBackgroundFilePath(currentBackgroundBean.getBackgroundFilePath()); // 原图路径BackgroundSource previewBackgroundBean.setBackgroundFilePath(currentBackgroundBean.getBackgroundFilePath());
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()); // 压缩图路径BackgroundCrops previewBackgroundBean.setBackgroundScaledCompressFilePath(currentBackgroundBean.getBackgroundScaledCompressFilePath());
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(); saveSettings();
LogUtils.d(TAG, "正式背景同步到预览背景成功");
} }
/** /**
* 工具方法:清理旧文件(避免文件锁定/残留,适配系统公共目录)【内部私有,不对外暴露】 * 清理单个旧文件
* @param file 要清理的文件
* @param fileDesc 文件描述(用于日志打印)
*/ */
private void clearOldFile(File file, String fileDesc) { private void clearOldFile(File file, String fileDesc) {
if (file == null) { if (file == null) {
@@ -628,50 +473,45 @@ public class BackgroundSourceUtils {
} }
if (file.exists()) { if (file.exists()) {
file.delete(); file.delete();
LogUtils.w(TAG, "【文件管理】" + fileDesc + "已删除"); LogUtils.d(TAG, fileDesc + "已删除");
} }
} }
/** /**
* 新增:清理压缩图目录下的旧文件(避免残留,初始化时调用) * 清理裁剪临时文件
*/ */
void clearCropTempFiles() { void clearCropTempFiles() {
for (File file : fCropCacheDir.listFiles()) { File[] files = fCropCacheDir.listFiles();
clearOldFile(file, "旧裁剪缓存文件(" + file.getAbsolutePath() + ")"); if (files == null) {
return;
}
for (File file : files) {
clearOldFile(file, "旧裁剪缓存文件:" + file.getAbsolutePath());
} }
mCropSourceFile = null; mCropSourceFile = null;
mCropResultFile = null; mCropResultFile = null;
} }
/** /**
* 适配原调用mBgSourceUtils.copyFile(new File(""), parentDir) * 复制文件
* 核心复用FileUtils支持「空源文件→仅创建目标目录」和「正常文件复制」两种场景
* @param source 源文件(可为空/空文件,为空时仅创建目标目录)
* @param target 目标文件/目录若源文件为空target视为目录并创建
* @return true=复制/创建成功false=失败
*/ */
public boolean copyFile(File source, File target) { public boolean copyFile(File source, File target) {
// 场景1源文件为空适配 new File("") 调用)→ 仅创建目标目录 LogUtils.d(TAG, "【copyFile调用】源文件" + (source != null ? source.getAbsolutePath() : "null") + " 目标:" + (target != null ? target.getAbsolutePath() : "null"));
if (source == null || (source.exists() && source.length() <= 0) || TextUtils.isEmpty(source.getPath())) { if (source == null || TextUtils.isEmpty(source.getPath()) || (source.exists() && source.length() <= 0)) {
if (target == null) { if (target == null) {
LogUtils.e(TAG, "【文件管理】目录创建失败:目标目录对象为null"); LogUtils.e(TAG, "目录创建失败目标对象为null");
return false; return false;
} }
// 若target是文件取其父目录若本身是目录直接创建实例化时已创建此处二次确认
File targetDir = target.isFile() ? target.getParentFile() : target; File targetDir = target.isFile() ? target.getParentFile() : target;
createDirWithPermission(targetDir, "空源文件场景-目录创建/Pictures/PowerBell下"); createDirWithPermission(targetDir, "空源文件场景-目录创建");
LogUtils.d(TAG, "【文件管理】空源文件场景目录创建完成,路径=" + targetDir.getAbsolutePath()); LogUtils.d(TAG, "空源文件场景目录创建完成");
return true; return true;
} }
// 场景2正常文件复制源文件非空且存在→ 复用FileUtils.copyFile确保高效兼容
return FileUtils.copyFile(source, target); return FileUtils.copyFile(source, target);
} }
/** /**
* 工具方法:获取目录类型描述(用于日志调试,明确目录类型,适配新目录结构) * 获取目录类型描述
* @param dir 目标目录
* @return 目录类型描述
*/ */
public String getDirTypeDesc(File dir) { public String getDirTypeDesc(File dir) {
if (dir == null) { if (dir == null) {
@@ -684,7 +524,7 @@ public class BackgroundSourceUtils {
if (!TextUtils.isEmpty(publicPicturePath)) { if (!TextUtils.isEmpty(publicPicturePath)) {
if (dirPath.contains(publicPicturePath + File.separator + "PowerBell" + File.separator + COMPRESS_DIR_NAME)) { if (dirPath.contains(publicPicturePath + File.separator + "PowerBell" + File.separator + COMPRESS_DIR_NAME)) {
return "系统公共图片目录(/Pictures/PowerBell/BackgroundCrops压缩图统一存储目录"; // 新增压缩图目录描述 return "系统公共图片目录(/Pictures/PowerBell/BackgroundCompress压缩图统一存储目录";
} else if (dirPath.contains(publicPicturePath + File.separator + "PowerBell")) { } else if (dirPath.contains(publicPicturePath + File.separator + "PowerBell")) {
return "系统公共图片目录(/Pictures/PowerBell图片存储/裁剪目录)"; return "系统公共图片目录(/Pictures/PowerBell图片存储/裁剪目录)";
} }
@@ -699,86 +539,68 @@ public class BackgroundSourceUtils {
} }
/** /**
* 新增:迁移旧压缩图路径到新目录BackgroundCrops兼容历史数据 * 迁移旧压缩图路径到新目录
* @param bean 要迁移的BackgroundBean正式/预览)
* @param isCurrentBean 是否是正式Bean用于日志区分
*/ */
private void migrateCompressPathToNewDir(BackgroundBean bean, boolean isCurrentBean) { private void migrateCompressPathToNewDir(BackgroundBean bean, boolean isCurrentBean) {
LogUtils.d(TAG, "【migrateCompressPathToNewDir调用】开始迁移" + (isCurrentBean ? "正式" : "预览") + "Bean压缩路径");
String oldCompressPath = bean.getBackgroundScaledCompressFilePath(); String oldCompressPath = bean.getBackgroundScaledCompressFilePath();
String beanType = isCurrentBean ? "正式Bean" : "预览Bean"; String beanType = isCurrentBean ? "正式Bean" : "预览Bean";
// 校验旧路径非空且不在BackgroundCrops目录下才需要迁移
if (TextUtils.isEmpty(oldCompressPath) || oldCompressPath.contains(fBackgroundCompressDir.getAbsolutePath())) { if (TextUtils.isEmpty(oldCompressPath) || oldCompressPath.contains(fBackgroundCompressDir.getAbsolutePath())) {
LogUtils.d(TAG, "【路径迁移】" + beanType + "无需迁移:旧路径为空或已在BackgroundCrops目录"); LogUtils.d(TAG, beanType + "无需迁移:旧路径为空或已在目标目录");
return; return;
} }
File oldCompressFile = new File(oldCompressPath); File oldCompressFile = new File(oldCompressPath);
if (!oldCompressFile.exists() || !oldCompressFile.isFile() || oldCompressFile.length() <= 0) { if (!oldCompressFile.exists() || !oldCompressFile.isFile() || oldCompressFile.length() <= 0) {
LogUtils.w(TAG, "【路径迁移】" + beanType + "旧压缩文件无效,无需迁移:" + oldCompressPath); LogUtils.w(TAG, beanType + "旧压缩文件无效,无需迁移:" + oldCompressPath);
// 重置路径为新目录下的空文件(避免无效路径)
String compressFileName = bean.getBackgroundScaledCompressFileName(); String compressFileName = bean.getBackgroundScaledCompressFileName();
if (!TextUtils.isEmpty(compressFileName)) { if (!TextUtils.isEmpty(compressFileName)) {
File newCompressFile = new File(fBackgroundCompressDir, compressFileName); File newCompressFile = new File(fBackgroundCompressDir, compressFileName);
bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath()); bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath());
saveSettings(); saveSettings();
LogUtils.d(TAG, "【路径迁移】" + beanType + "重置压缩路径到BackgroundCrops" + newCompressFile.getAbsolutePath()); LogUtils.d(TAG, beanType + "压缩路径已重置到目标目录");
} }
return; return;
} }
// 迁移逻辑复制旧文件到新目录更新Bean路径删除旧文件
String compressFileName = bean.getBackgroundScaledCompressFileName(); String compressFileName = bean.getBackgroundScaledCompressFileName();
if (TextUtils.isEmpty(compressFileName)) { if (TextUtils.isEmpty(compressFileName)) {
compressFileName = "ScaledCompress_" + System.currentTimeMillis() + ".jpg"; // 兜底生成文件名 compressFileName = "ScaledCompress_" + System.currentTimeMillis() + ".jpg";
} }
File newCompressFile = new File(fBackgroundCompressDir, compressFileName); File newCompressFile = new File(fBackgroundCompressDir, compressFileName);
// 复制旧文件到新目录
boolean copySuccess = FileUtils.copyFile(oldCompressFile, newCompressFile); boolean copySuccess = FileUtils.copyFile(oldCompressFile, newCompressFile);
if (copySuccess) { if (copySuccess) {
// 更新Bean路径为新目录路径
bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath()); bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath());
saveSettings(); saveSettings();
// 删除旧文件(清理残留)
clearOldFile(oldCompressFile, beanType + "旧压缩文件(迁移后清理)"); clearOldFile(oldCompressFile, beanType + "旧压缩文件(迁移后清理)");
LogUtils.d(TAG, "【路径迁移】" + beanType + "压缩路径迁移成功:" + oldCompressPath + "" + newCompressFile.getAbsolutePath()); LogUtils.d(TAG, beanType + "压缩路径迁移成功:" + oldCompressPath + "" + newCompressFile.getAbsolutePath());
} else { } else {
LogUtils.e(TAG, "【路径迁移】" + beanType + "压缩文件复制失败,迁移终止" + oldCompressPath); LogUtils.e(TAG, beanType + "压缩文件复制失败,迁移终止");
} }
} }
// ======================================== 核心实现:获取图片旋转角度 ========================================
/** /**
* 取图片EXIF信息获取旋转角度适配JPEG/PNG等主流格式 * 取图片旋转角度
* @param imagePath 图片绝对路径(支持本地文件路径,兼容多包名临时目录)
* @return 旋转角度0/90/180/270无旋转返回0
*/ */
public int getImageRotateAngle(String imagePath) { public int getImageRotateAngle(String imagePath) {
// 1. 入参校验(避免空指针/无效路径) LogUtils.d(TAG, "【getImageRotateAngle调用】图片路径" + imagePath);
if (TextUtils.isEmpty(imagePath)) { if (TextUtils.isEmpty(imagePath)) {
Log.e(TAG, "getImageRotateAngle: 图片路径为空"); LogUtils.e(TAG, "图片路径为空");
return 0; return 0;
} }
File imageFile = new File(imagePath); File imageFile = new File(imagePath);
if (!imageFile.exists() || !imageFile.isFile() || imageFile.length() <= 0) { if (!imageFile.exists() || !imageFile.isFile() || imageFile.length() <= 0) {
Log.e(TAG, "getImageRotateAngle: 图片文件无效,路径" + imagePath); LogUtils.e(TAG, "图片文件无效:" + imagePath);
return 0; return 0;
} }
InputStream inputStream = null; InputStream inputStream = null;
try { try {
// 2. 读取图片EXIF信息优先用流读取避免文件占用
inputStream = new FileInputStream(imageFile); inputStream = new FileInputStream(imageFile);
ExifInterface exifInterface = new ExifInterface(inputStream); ExifInterface exifInterface = new ExifInterface(inputStream);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
// 3. 获取旋转角度标签兼容不同设备的EXIF字段
int orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL
);
// 4. 解析旋转角度标准EXIF角度映射
switch (orientation) { switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90: case ExifInterface.ORIENTATION_ROTATE_90:
return 90; return 90;
@@ -786,103 +608,83 @@ public class BackgroundSourceUtils {
return 180; return 180;
case ExifInterface.ORIENTATION_ROTATE_270: case ExifInterface.ORIENTATION_ROTATE_270:
return 270; return 270;
default: // 正常/翻转等其他情况均视为0度 default:
return 0; return 0;
} }
} catch (IOException e) { } catch (IOException e) {
// 兼容异常场景如图片无EXIF信息、格式不支持如WebP LogUtils.w(TAG, "读取EXIF异常" + e.getMessage());
Log.w(TAG, "getImageRotateAngle: 读取EXIF异常路径" + imagePath + ",错误:" + e.getMessage());
return 0; return 0;
} finally { } finally {
// 5. 关闭流资源(避免内存泄漏/文件占用)
if (inputStream != null) { if (inputStream != null) {
try { try {
inputStream.close(); inputStream.close();
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "getImageRotateAngle: 流关闭失败,错误" + e.getMessage()); LogUtils.e(TAG, "流关闭失败:" + e.getMessage());
} }
} }
} }
} }
// ======================================== 图片处理核心方法(压缩/裁剪/保存) ========================================
/** /**
* 压缩图片并保存(核心修复:路径非空校验+兜底路径统一存储到BackgroundCrops目录 * 压缩图片并保存(默认路径
*/ */
public void compressQualityToRecivedPicture(Bitmap bitmap) { public void compressQualityToRecivedPicture(Bitmap bitmap) {
// 兼容裁剪等旧调用从工具类获取默认压缩路径统一指向BackgroundCrops转发至重载函数 LogUtils.d(TAG, "【compressQualityToRecivedPicture调用】使用默认路径压缩图片");
String defaultCompressPath = getPreviewBackgroundScaledCompressFilePath(); String defaultCompressPath = getPreviewBackgroundScaledCompressFilePath();
compressQualityToRecivedPicture(bitmap, defaultCompressPath); compressQualityToRecivedPicture(bitmap, defaultCompressPath);
} }
/** /**
* 重载方法指定路径压缩图片并保存修复压缩后同步路径到预览Bean统一存储到BackgroundCrops * 压缩图片并保存(指定路径
* 适配场景裁剪后生成压缩图强制绑定路径到预览Bean避免路径错位
* @param bitmap 待压缩的Bitmap裁剪后的缩放图
* @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, "compressQualityToRecivedPicture调用】指定路径压缩图片目标路径" + targetCompressPath);
if (bitmap == null || bitmap.isRecycled()) { if (bitmap == null || bitmap.isRecycled()) {
ToastUtils.show("压缩失败:图片为空"); ToastUtils.show("压缩失败:图片为空");
LogUtils.e(TAG, "【压缩失败】Bitmap为空或已回收"); LogUtils.e(TAG, "Bitmap为空或已回收");
return; return;
} }
OutputStream outStream = null; OutputStream outStream = null;
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
LogUtils.d(TAG, "【压缩配置】目标路径BackgroundCrops" + targetCompressPath + "Bitmap原始大小" + bitmap.getByteCount() / 1024 + "KB"); LogUtils.d(TAG, "Bitmap原始大小" + bitmap.getByteCount() / 1024 + "KB");
File targetCompressFile = new File(targetCompressPath); File targetCompressFile = new File(targetCompressPath);
if (targetCompressFile.exists()) { if (targetCompressFile.exists()) {
targetCompressFile.delete(); targetCompressFile.delete();
} }
targetCompressFile.createNewFile(); targetCompressFile.createNewFile();
// 写入压缩图质量80平衡清晰度和内存
fos = new FileOutputStream(targetCompressFile); fos = new FileOutputStream(targetCompressFile);
outStream = new BufferedOutputStream(fos); outStream = new BufferedOutputStream(fos);
boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream); boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream);
outStream.flush(); outStream.flush();
// 强制同步到磁盘(避免异步写入导致控件读取不到文件)
if (fos != null) {
try { try {
fos.getFD().sync(); fos.getFD().sync();
LogUtils.d(TAG, "【压缩保存】已强制同步到磁盘"); LogUtils.d(TAG, "图片已强制同步到磁盘");
} catch (IOException e) { } catch (IOException e) {
LogUtils.w(TAG, "【压缩保存】sync()失败flush()兜底:" + e.getMessage()); LogUtils.w(TAG, "sync失败flush兜底" + e.getMessage());
outStream.flush(); outStream.flush();
} }
}
LogUtils.d(TAG, "【压缩结果】" + (compressSuccess ? "成功" : "失败") + ",大小:" + targetCompressFile.length() / 1024 + "KB,路径:" + targetCompressFile); LogUtils.d(TAG, "图片压缩" + (compressSuccess ? "成功" : "失败") + ",大小:" + targetCompressFile.length() / 1024 + "KB");
ToastUtils.show(compressSuccess ? "图片压缩成功" : "图片压缩失败");
// 关键修复压缩成功后强制同步路径到预览Bean双重保障避免时序错位
if (compressSuccess) {
ToastUtils.show("图片压缩成功");
} else {
ToastUtils.show("图片压缩失败");
}
} catch (IOException e) { } catch (IOException e) {
LogUtils.e(TAG, "【压缩异常】IO错误" + e.getMessage(), e); LogUtils.e(TAG, "图片压缩IO异常" + e.getMessage(), e);
ToastUtils.show("图片压缩失败"); ToastUtils.show("图片压缩失败");
} finally { } finally {
// 资源回收(避免内存泄漏)
if (outStream != null) { if (outStream != null) {
try { try {
outStream.close(); outStream.close();
} catch (IOException e) { } catch (IOException e) {
LogUtils.e(TAG, "【流关闭失败】BufferedOutputStream" + e.getMessage()); LogUtils.e(TAG, "BufferedOutputStream关闭失败" + e.getMessage());
} }
} }
if (fos != null) { if (fos != null) {
try { try {
fos.close(); fos.close();
} catch (IOException e) { } catch (IOException e) {
LogUtils.e(TAG, "【流关闭失败】FileOutputStream" + e.getMessage()); LogUtils.e(TAG, "FileOutputStream关闭失败" + e.getMessage());
} }
} }
if (bitmap != null && !bitmap.isRecycled()) { if (bitmap != null && !bitmap.isRecycled()) {
@@ -891,3 +693,4 @@ public class BackgroundSourceUtils {
} }
} }
} }