文件管理类规范划分

This commit is contained in:
2025-12-01 08:20:45 +08:00
parent 6951f642a1
commit f7ef8f6b19
4 changed files with 1225 additions and 1866 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Nov 30 23:19:16 GMT 2025
#Mon Dec 01 00:19:18 GMT 2025
stageCount=13
libraryProject=
baseVersion=15.11
publishVersion=15.11.12
buildCount=33
buildCount=36
baseBetaVersion=15.11.13

View File

@@ -40,7 +40,7 @@ public class BackgroundPicturePreviewDialog extends Dialog {
initEnv();
mContext = context;
mBackgroundPictureUtils = ((BackgroundSettingsActivity)context).mBackgroundSourceUtils;
mBackgroundPictureUtils = BackgroundSourceUtils.getInstance(mContext);
ImageView imageView = findViewById(R.id.dialogbackgroundpicturepreviewImageView1);
copyAndViewRecivePicture(imageView);
@@ -95,7 +95,7 @@ public class BackgroundPicturePreviewDialog extends Dialog {
File fSrcImage = new File(szSrcImage);
//mszPreReceivedFileName = DateUtils.getDateNowString() + "-" + fSrcImage.getName();
File mfPreReceivedPhoto = new File(activity.mBackgroundSourceUtils.getBackgroundSourceDirPath(), mszPreReceivedFileName);
File mfPreReceivedPhoto = new File(BackgroundSourceUtils.getInstance(mContext).getBackgroundSourceDirPath(), mszPreReceivedFileName);
// 复制源图片到剪裁文件
try {
FileUtils.copyFileUsingFileChannels(fSrcImage, mfPreReceivedPhoto);

View File

@@ -1,19 +1,33 @@
package cc.winboll.studio.powerbell.utils;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import cc.winboll.studio.powerbell.BuildConfig;
import cc.winboll.studio.powerbell.model.BackgroundBean;
import java.io.File;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.io.File;
import java.io.IOException;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 12:07:20
* @Describe 背景图片工具集(修复单例模式,线程安全+数据流转正常)
* @Describe 背景图片工具集(全量文件管理+裁剪FileProvider路径适配,线程安全+数据流转正常)
* 核心能力:
* 1. 统一管理所有文件路径(背景图/裁剪临时文件/压缩图)
* 2. 为系统裁剪应用创建可读写的FileProvider路径适配Android14+ MIUI
* 3. 替代BackgroundSettingsActivity的所有文件操作逻辑
*/
public class BackgroundSourceUtils {
public static final String TAG = "BackgroundPictureUtils";
// 裁剪相关常量(统一定义,避免硬编码)
private static final String CROP_TEMP_DIR_NAME = "CropTemp"; // 裁剪临时目录FileProvider适配
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_FALLBACK_DIR_NAME = "CropFallback"; // 裁剪兜底目录
private static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider"; // 多包名兼容
// 1. 静态实例加volatile禁止指令重排保证可见性双重校验锁单例核心
private static volatile BackgroundSourceUtils sInstance;
@@ -22,13 +36,17 @@ public class BackgroundSourceUtils {
private BackgroundBean currentBackgroundBean;
private File previewBackgroundBeanFile;
private BackgroundBean previewBackgroundBean;
// 应用外部存储文件夹路径(按功能划分目录,避免路径混乱)
private File fUtilsDir;
private File fModelDir;
// 背景图片源文件目录(存储正式/预览图片)
private File fBackgroundSourceDir;
// 2. 私有构造器(加防反射逻辑+初始化目录
// 2. 统一文件目录全量文件管理替代Activity中的目录变量
private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundPictureUtils
private File fModelDir; // 模型文件目录存储BackgroundBean的JSON文件
private File fBackgroundSourceDir; // 背景图片源目录(存储正式/预览图片)
private File fCropTempDir; // 裁剪临时目录FileProvider适配路径系统裁剪应用可读写
private File fCropFallbackDir; // 裁剪兜底目录(应用私有外部目录失败时使用)
private File cropTempFile; // 裁剪临时文件(系统裁剪应用写入目标)
private File cropResultFile; // 裁剪结果文件(裁剪后保存的最终文件)
// 3. 私有构造器(加防反射逻辑+初始化所有目录/文件)
private BackgroundSourceUtils(Context context) {
// 防反射破坏:若已有实例,抛异常阻止重复创建
if (sInstance != null) {
@@ -36,13 +54,15 @@ public class BackgroundSourceUtils {
}
// 上下文用Application Context避免Activity内存泄漏
this.mContext = context.getApplicationContext();
// 初始化目录(按功能划分,确保目录存在
initDirs();
// 初始化所有目录(文件管理核心:统一创建+权限设置
initAllDirs();
// 初始化所有文件(裁剪临时文件/结果文件等)
initAllFiles();
// 加载配置(正式/预览Bean
loadSettings();
}
// 3. 双重校验锁单例(线程安全,高效,支持多线程并发调用)
// 4. 双重校验锁单例(线程安全,高效,支持多线程并发调用)
public static BackgroundSourceUtils getInstance(Context context) {
// 第一重校验:避免每次调用都加锁(提升效率)
if (sInstance == null) {
@@ -58,33 +78,110 @@ public class BackgroundSourceUtils {
}
/**
* 初始化所有目录(确保目录存在,避免文件操作失败
* 初始化所有文件目录(统一管理替代Activity中的目录初始化逻辑
* 包含:工具类根目录、模型目录、背景图目录、裁剪临时目录、裁剪兜底目录
*/
private void initDirs() {
// 工具类根目录(外部存储:/Android/data/包名/files/BackgroundPictureUtils
private void initAllDirs() {
// 1. 工具类根目录(应用外部存储:/Android/data/包名/files/BackgroundPictureUtils
fUtilsDir = mContext.getExternalFilesDir(TAG);
if (fUtilsDir == null) {
LogUtils.e(TAG, "外部存储不可用,无法初始化目录");
return;
}
// 模型文件目录存储BackgroundBean的JSON文件
fModelDir = new File(fUtilsDir, "ModelDir");
// 背景图片源目录(存储正式/预览图片文件)
fBackgroundSourceDir = new File(fUtilsDir, "BackgroundSource");
// 递归创建所有目录(确保目录存在)
if (!fModelDir.exists()) {
fModelDir.mkdirs();
LogUtils.d(TAG, "创建模型文件目录:" + fModelDir.getAbsolutePath());
}
if (!fBackgroundSourceDir.exists()) {
fBackgroundSourceDir.mkdirs();
LogUtils.d(TAG, "创建背景图片目录:" + fBackgroundSourceDir.getAbsolutePath());
LogUtils.e(TAG, "【文件管理】应用外部存储不可用,切换到应用内部缓存目录");
fUtilsDir = mContext.getCacheDir(); // 极端兜底:应用内部缓存目录
}
// 初始化Bean文件对象存储JSON配置
// 2. 子目录初始化(按功能划分,确保目录存在并设置权限
fModelDir = new File(fUtilsDir, "ModelDir"); // 模型文件目录JSON配置
fBackgroundSourceDir = new File(fUtilsDir, "BackgroundSource"); // 背景图片目录
fCropTempDir = new File(fUtilsDir, CROP_TEMP_DIR_NAME); // 裁剪临时目录FileProvider适配路径
fCropFallbackDir = new File(fUtilsDir, CROP_FALLBACK_DIR_NAME); // 裁剪兜底目录
// 3. 递归创建所有目录确保目录存在Android14+ 必需)
createDirWithPermission(fModelDir, "模型文件目录");
createDirWithPermission(fBackgroundSourceDir, "背景图片目录");
createDirWithPermission(fCropTempDir, "裁剪临时目录FileProvider适配");
createDirWithPermission(fCropFallbackDir, "裁剪兜底目录");
// 4. 初始化Bean文件对象存储JSON配置
currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json");
previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json");
LogUtils.d(TAG, "【文件管理】所有目录初始化完成:根目录=" + fUtilsDir.getAbsolutePath());
}
/**
* 初始化所有文件统一管理替代Activity中的文件初始化逻辑
* 包含:裁剪临时文件、裁剪结果文件
*/
private void initAllFiles() {
// 1. 裁剪临时文件(系统裁剪应用写入路径,优先裁剪临时目录)
cropTempFile = new File(fCropTempDir, CROP_TEMP_FILE_NAME);
// 2. 裁剪结果文件(裁剪后保存的最终文件,存入背景图片目录)
cropResultFile = new File(fBackgroundSourceDir, CROP_RESULT_FILE_NAME);
// 3. 初始化时清理旧文件(避免文件锁定/权限残留)
clearOldFile(cropTempFile, "旧裁剪临时文件");
clearOldFile(cropResultFile, "旧裁剪结果文件");
LogUtils.d(TAG, "【文件管理】所有文件初始化完成:裁剪临时文件=" + cropTempFile.getAbsolutePath() + ",裁剪结果文件=" + cropResultFile.getAbsolutePath());
}
/**
* 核心函数为系统裁剪应用创建可读写的FileProvider路径满足需求
* 功能确保裁剪临时目录fCropTempDir可读写返回裁剪临时文件系统裁剪应用写入目标
* 适配Android14+、MIUI解决Permission denied问题确保系统裁剪应用能正常写入
* @return 裁剪临时文件File系统裁剪应用可读写路径已适配FileProvider
*/
public File createCropFileProviderPath() {
LogUtils.d(TAG, "【裁剪路径】createCropFileProviderPath 触发,创建系统裁剪可读写路径");
// 1. 优先尝试裁剪临时目录FileProvider适配路径
if (isDirActuallyWritable(fCropTempDir)) {
try {
// 重新初始化裁剪临时文件(先删后建,避免文件锁定)
clearOldFile(cropTempFile, "裁剪临时文件(重新初始化)");
cropTempFile.createNewFile();
// 强制设置文件权限(系统裁剪应用必需:允许所有用户读写)
setFilePermissions(cropTempFile);
LogUtils.d(TAG, "【裁剪路径】系统裁剪可读写路径创建成功(裁剪临时目录):" + cropTempFile.getAbsolutePath());
return cropTempFile;
} catch (IOException e) {
LogUtils.e(TAG, "【裁剪路径】裁剪临时目录创建文件失败:" + e.getMessage(), e);
}
}
// 2. 兜底1裁剪临时目录失败切换到裁剪兜底目录
if (isDirActuallyWritable(fCropFallbackDir)) {
try {
cropTempFile = new File(fCropFallbackDir, CROP_TEMP_FILE_NAME);
clearOldFile(cropTempFile, "裁剪临时文件(兜底目录)");
cropTempFile.createNewFile();
setFilePermissions(cropTempFile);
LogUtils.w(TAG, "【裁剪路径】裁剪临时目录失败,切换到兜底目录创建成功:" + cropTempFile.getAbsolutePath());
return cropTempFile;
} catch (IOException e) {
LogUtils.e(TAG, "【裁剪路径】裁剪兜底目录创建文件失败:" + e.getMessage(), e);
}
}
// 3. 终极兜底:应用内部缓存目录(确保不崩溃)
File cacheDir = mContext.getCacheDir();
if (isDirActuallyWritable(cacheDir)) {
try {
cropTempFile = new File(cacheDir, CROP_TEMP_FILE_NAME);
clearOldFile(cropTempFile, "裁剪临时文件(终极兜底)");
cropTempFile.createNewFile();
setFilePermissions(cropTempFile);
LogUtils.w(TAG, "【裁剪路径】应用外部目录全部失败,终极兜底到缓存目录:" + cropTempFile.getAbsolutePath());
return cropTempFile;
} catch (IOException e) {
LogUtils.e(TAG, "【裁剪路径】终极兜底目录创建文件失败:" + e.getMessage(), e);
ToastUtils.show("裁剪路径创建失败,请重启应用");
}
}
// 极端情况所有目录均失败返回null上层需处理
LogUtils.e(TAG, "【裁剪路径】所有目录均无法创建裁剪文件,系统裁剪功能不可用");
return null;
}
/**
@@ -97,7 +194,7 @@ public class BackgroundSourceUtils {
if (currentBackgroundBean == null) {
currentBackgroundBean = new BackgroundBean();
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
LogUtils.d(TAG, "正式背景Bean不存在创建默认Bean");
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在创建默认Bean");
}
// 加载预览Bean
@@ -105,7 +202,7 @@ public class BackgroundSourceUtils {
if (previewBackgroundBean == null) {
previewBackgroundBean = new BackgroundBean();
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "预览背景Bean不存在创建默认Bean");
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在创建默认Bean");
}
}
@@ -129,7 +226,7 @@ public class BackgroundSourceUtils {
public String getCurrentBackgroundFilePath() {
loadSettings(); // 加载最新配置,避免数据滞后
File file = new File(fBackgroundSourceDir, currentBackgroundBean.getBackgroundFileName());
LogUtils.d(TAG, "正式背景路径:" + file.getAbsolutePath());
LogUtils.d(TAG, "【路径管理】正式背景路径:" + file.getAbsolutePath());
return file.getAbsolutePath();
}
@@ -139,7 +236,7 @@ public class BackgroundSourceUtils {
public String getPreviewBackgroundFilePath() {
loadSettings(); // 加载最新配置,避免数据滞后
File file = new File(fBackgroundSourceDir, previewBackgroundBean.getBackgroundFileName());
LogUtils.d(TAG, "预览背景路径:" + file.getAbsolutePath());
LogUtils.d(TAG, "【路径管理】预览背景路径:" + file.getAbsolutePath());
return file.getAbsolutePath();
}
@@ -149,7 +246,7 @@ public class BackgroundSourceUtils {
public String getPreviewBackgroundScaledCompressFilePath() {
loadSettings(); // 加载最新配置,避免数据滞后
File file = new File(fBackgroundSourceDir, previewBackgroundBean.getBackgroundScaledCompressFileName());
LogUtils.d(TAG, "预览压缩背景路径:" + file.getAbsolutePath());
LogUtils.d(TAG, "【路径管理】预览压缩背景路径:" + file.getAbsolutePath());
return file.getAbsolutePath();
}
@@ -159,7 +256,7 @@ public class BackgroundSourceUtils {
public void saveSettings() {
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "配置保存成功正式Bean=" + currentBackgroundBeanFile.getAbsolutePath() + "预览Bean=" + previewBackgroundBeanFile.getAbsolutePath());
LogUtils.d(TAG, "【配置管理】配置保存成功正式Bean=" + currentBackgroundBeanFile.getAbsolutePath() + "预览Bean=" + previewBackgroundBeanFile.getAbsolutePath());
}
/**
@@ -169,6 +266,27 @@ public class BackgroundSourceUtils {
return fBackgroundSourceDir.getAbsolutePath();
}
/**
* 获取裁剪临时文件对外提供Activity中用于传递给系统裁剪应用
*/
public File getCropTempFile() {
return cropTempFile;
}
/**
* 获取裁剪结果文件对外提供Activity中用于获取裁剪后的图片
*/
public File getCropResultFile() {
return cropResultFile;
}
/**
* 获取FileProvider授权Authority多包名兼容对外提供给Activity
*/
public String getFileProviderAuthority() {
return FILE_PROVIDER_AUTHORITY;
}
/**
* 保存图片到预览Bean核心修复解决文件名覆盖+路径无效问题)
* @param sourceFile 源图片文件(非空,必须存在)
@@ -178,15 +296,14 @@ public class BackgroundSourceUtils {
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
// 校验源文件合法性
if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile()) {
LogUtils.e(TAG, "源文件无效:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null"));
LogUtils.e(TAG, "【文件管理】源文件无效:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null"));
ToastUtils.show("源图片文件无效");
return previewBackgroundBean;
}
// 确保背景目录存在(防止首次使用时目录未创建)
if (!fBackgroundSourceDir.exists()) {
fBackgroundSourceDir.mkdirs();
LogUtils.d(TAG, "背景目录不存在,自动创建:" + fBackgroundSourceDir.getAbsolutePath());
createDirWithPermission(fBackgroundSourceDir, "背景图片目录saveFileToPreviewBean");
}
// 生成唯一文件名(基于源文件后缀,避免重复)
@@ -196,7 +313,7 @@ public class BackgroundSourceUtils {
// 复制源文件到预览目录(确保图片实际保存成功)
boolean copySuccess = FileUtils.copyFile(sourceFile, previewBackgroundFile);
if (!copySuccess) {
LogUtils.e(TAG, "图片复制到预览目录失败:" + sourceFile.getAbsolutePath() + "" + previewBackgroundFile.getAbsolutePath());
LogUtils.e(TAG, "【文件管理】图片复制到预览目录失败:" + sourceFile.getAbsolutePath() + "" + previewBackgroundFile.getAbsolutePath());
ToastUtils.show("预览图片保存失败");
return previewBackgroundBean;
}
@@ -206,12 +323,13 @@ public class BackgroundSourceUtils {
previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); // 正确赋值:唯一文件名
previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_" + previewBackgroundFile.getName()); // 压缩文件名(前缀标识)
previewBackgroundBean.setBackgroundFileInfo(fileInfo); // 正确赋值附加信息Uri
previewBackgroundBean.setIsUseBackgroundFile(true); // 标记使用背景图确保BackgroundView加载
previewBackgroundBean.setIsUseBackgroundFile(true); // 标记使用背景图
previewBackgroundBean.setIsUseScaledCompress(true); // 启用压缩图,提升预览加载速度
previewBackgroundBean.setBackgroundWidth(100); // 默认宽高比1:1可根据需求调整
previewBackgroundBean.setBackgroundHeight(100);
saveSettings(); // 持久化保存预览Bean
LogUtils.d(TAG, "预览图片保存成功:" + previewBackgroundFile.getAbsolutePath());
LogUtils.d(TAG, "【文件管理】预览图片保存成功:" + previewBackgroundFile.getAbsolutePath());
ToastUtils.show("预览图片加载成功");
return previewBackgroundBean;
}
@@ -232,7 +350,7 @@ public class BackgroundSourceUtils {
currentBackgroundBean.setPixelColor(previewBackgroundBean.getPixelColor());
saveSettings(); // 持久化保存正式Bean
LogUtils.d(TAG, "预览背景提交成功:正式背景更新为预览背景");
LogUtils.d(TAG, "【配置管理】预览背景提交成功:正式背景更新为预览背景");
ToastUtils.show("背景图片应用成功");
}
@@ -252,7 +370,238 @@ public class BackgroundSourceUtils {
previewBackgroundBean.setPixelColor(currentBackgroundBean.getPixelColor());
saveSettings(); // 持久化保存预览Bean
LogUtils.d(TAG, "正式背景同步到预览:预览背景更新为当前正式背景");
LogUtils.d(TAG, "【配置管理】正式背景同步到预览:预览背景更新为当前正式背景");
}
/**
* 工具方法创建目录并设置权限确保目录可读写适配Android14+
* @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);
}
/**
* 工具方法:递归设置目录及子目录/文件的读写权限适配Android全版本
* @param dir 要设置权限的目录
*/
private void setDirPermissionsRecursively(File dir) {
if (dir == null || !dir.exists()) {
String dirPath = (dir != null) ? dir.getAbsolutePath() : "null";
LogUtils.d(TAG, "【权限管理】目录无效,无需设置权限:" + dirPath);
return;
}
try {
// 设置目录权限(允许所有用户读写,系统裁剪应用必需)
dir.setReadable(true, false);
dir.setWritable(true, false);
dir.setExecutable(false, false);
LogUtils.d(TAG, "【权限管理】目录权限设置完成:路径=" + dir.getAbsolutePath() + ",可写=" + dir.canWrite() + ",可读=" + dir.canRead());
// 递归处理子目录和文件
File[] files = dir.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
if (file.isDirectory()) {
setDirPermissionsRecursively(file);
} else {
// 设置文件权限(与目录一致)
file.setReadable(true, false);
file.setWritable(true, false);
file.setExecutable(false, false);
// 裁剪相关文件单独打印日志
if (file.getName().contains(CROP_TEMP_FILE_NAME) || file.getName().contains(CROP_RESULT_FILE_NAME)) {
LogUtils.d(TAG, "【权限管理】裁剪文件权限设置:文件名=" + file.getName() + ",可写=" + file.canWrite());
}
}
}
}
} catch (SecurityException e) {
LogUtils.e(TAG, "【权限管理】设置目录权限失败(系统禁止):" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e);
} catch (Exception e) {
LogUtils.e(TAG, "【权限管理】设置目录权限异常:" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e);
}
}
/**
* 工具方法:设置单个文件权限(确保系统裁剪应用可读写)
* 【关键调整】修改为public适配BackgroundSettingsActivity的外部调用
* @param file 要设置权限的文件
*/
public void setFilePermissions(File file) {
if (file == null || !file.exists()) {
LogUtils.d(TAG, "【权限管理】文件无效,无需设置权限:" + (file != null ? file.getAbsolutePath() : "null"));
return;
}
try {
// 核心:允许所有用户读写(系统裁剪应用非本应用进程,需开放权限)
file.setReadable(true, false);
file.setWritable(true, false);
file.setExecutable(false, false);
LogUtils.d(TAG, "【权限管理】文件权限设置完成:路径=" + file.getAbsolutePath() + ",可写=" + file.canWrite() + ",可读=" + file.canRead());
} catch (Exception e) {
LogUtils.e(TAG, "【权限管理】设置文件权限失败:" + file.getAbsolutePath() + ",错误:" + e.getMessage(), e);
}
}
/**
* 工具方法:清理旧文件(避免文件锁定/残留)
* @param file 要清理的文件
* @param fileDesc 文件描述(用于日志打印)
*/
/**
* 工具方法:清理旧文件(避免文件锁定/残留)【内部私有,不对外暴露】
* @param file 要清理的文件
* @param fileDesc 文件描述(用于日志打印)
*/
private void clearOldFile(File file, String fileDesc) {
if (file == null) {
return;
}
if (file.exists()) {
boolean deleteSuccess = file.delete();
LogUtils.d(TAG, "【文件管理】清理" + fileDesc + "" + (deleteSuccess ? "成功" : "失败") + ",路径:" + file.getAbsolutePath());
// 若删除失败,标记为退出时删除(兼容文件锁定场景)
if (!deleteSuccess) {
file.deleteOnExit();
LogUtils.w(TAG, "【文件管理】" + fileDesc + "删除失败,标记为退出时自动删除");
}
}
}
/**
* 工具方法验证目录实际写入能力解决Android14+ canWrite()假阳性问题)
* 原理:通过创建临时空文件并删除,验证目录是否真的可写
* @param dir 要验证的目录
* @return true=实际可写false=实际不可写
*/
private boolean isDirActuallyWritable(File dir) {
if (dir == null || !dir.exists() || !dir.isDirectory()) {
return false;
}
// 创建临时空文件(随机文件名,避免冲突)
File testFile = new File(dir, "test_write_" + System.currentTimeMillis() + ".tmp");
try {
boolean createSuccess = testFile.createNewFile();
if (createSuccess) {
boolean canWrite = testFile.canWrite();
testFile.delete(); // 删除临时文件,不占用空间
LogUtils.d(TAG, "【权限校验】目录实际写入校验:" + dir.getAbsolutePath() + ",结果=" + (canWrite ? "通过" : "失败"));
return canWrite;
} else {
LogUtils.d(TAG, "【权限校验】目录实际写入校验失败:" + dir.getAbsolutePath() + ",创建临时文件失败");
return false;
}
} catch (IOException e) {
LogUtils.e(TAG, "【权限校验】目录实际写入校验异常:" + dir.getAbsolutePath() + ",错误:" + e.getMessage(), e);
return false;
}
}
/**
* 工具方法复制文件适配大文件避免OOM
* 【关键优化】兼容源文件为空的场景适配Activity中mBgSourceUtils.copyFile(new File(""), parentDir)调用)
* @param source 源文件(可为空,为空时仅创建目标目录)
* @param target 目标文件/目录若源文件为空target视为目录并创建
* @return true=复制/创建成功false=失败
*/
public boolean copyFile(File source, File target) {
// 场景1源文件为空 → 仅创建目标目录适配Activity的目录创建调用
if (source == null || (source.exists() && source.length() <= 0)) {
if (target == null) {
LogUtils.e(TAG, "【文件管理】目录创建失败目标目录对象为null");
return false;
}
// 若target是文件取其父目录若本身是目录直接创建
File targetDir = target.isFile() ? target.getParentFile() : target;
createDirWithPermission(targetDir, "空源文件场景-目录创建");
LogUtils.d(TAG, "【文件管理】空源文件场景:目录创建完成,路径=" + targetDir.getAbsolutePath());
return true;
}
// 场景2正常文件复制源文件非空且存在
if (!source.exists() || target == null) {
LogUtils.e(TAG, "【文件管理】文件复制失败:源文件无效或目标文件为空");
return false;
}
// 确保目标目录存在
File targetDir = target.getParentFile();
if (!targetDir.exists()) {
createDirWithPermission(targetDir, "文件复制目标目录");
}
// 调用FileUtils复制若项目中已有该方法可直接复用
return FileUtils.copyFile(source, target);
}
/**
* 工具方法清理裁剪相关临时文件对外提供Activity退出时调用
*/
public void clearCropTempFiles() {
clearOldFile(cropTempFile, "裁剪临时文件");
clearOldFile(cropResultFile, "裁剪结果文件");
// 清理裁剪目录下的其他临时文件
if (fCropTempDir.exists()) {
File[] files = fCropTempDir.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
if (file.isFile()) {
file.delete();
}
}
}
}
LogUtils.d(TAG, "【文件管理】裁剪相关临时文件清理完成");
}
/**
* 对外接口清理指定旧文件适配BackgroundSettingsActivity调用
* @param file 要清理的文件
* @param fileDesc 文件描述(用于日志打印)
*/
public void clearOldFileByExternal(File file, String fileDesc) {
clearOldFile(file, fileDesc); // 调用内部private方法复用逻辑
}
/**
* 工具方法:获取目录类型描述(用于日志调试,明确目录类型)
* @param dir 目标目录
* @return 目录类型描述
*/
public String getDirTypeDesc(File dir) {
if (dir == null) {
return "未知目录null";
}
String dirPath = dir.getAbsolutePath();
String externalFilesPath = mContext.getExternalFilesDir(null) != null ? mContext.getExternalFilesDir(null).getAbsolutePath() : "";
String cachePath = mContext.getCacheDir().getAbsolutePath();
if (!TextUtils.isEmpty(externalFilesPath) && dirPath.contains(externalFilesPath)) {
return "应用私有外部目录getExternalFilesDir(),系统裁剪可读写)";
} else if (dirPath.contains(cachePath)) {
return "应用内部缓存目录getCacheDir(),兜底目录)";
} else {
return "外部存储目录(非应用私有,权限受限)";
}
}
}