20251201_085930_533
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 00:19:18 GMT 2025
|
#Mon Dec 01 00:57:13 GMT 2025
|
||||||
stageCount=13
|
stageCount=13
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.11
|
baseVersion=15.11
|
||||||
publishVersion=15.11.12
|
publishVersion=15.11.12
|
||||||
buildCount=36
|
buildCount=41
|
||||||
baseBetaVersion=15.11.13
|
baseBetaVersion=15.11.13
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
||||||
|
|
||||||
@@ -672,41 +673,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理选图回调(调用工具类同步预览,精简文件操作)
|
|
||||||
*/
|
|
||||||
private void handleSelectPictureResult(int resultCode, Intent data) {
|
|
||||||
if (resultCode != RESULT_OK || data == null) {
|
|
||||||
handleOperationCancelOrFail();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri selectedImage = data.getData();
|
|
||||||
if (selectedImage == null) {
|
|
||||||
ToastUtils.show("选择的图片Uri为空");
|
|
||||||
mBgSourceUtils.clearCropTempFiles();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LogUtils.d(TAG, "【选图回调】选择图片Uri : " + selectedImage.toString());
|
|
||||||
|
|
||||||
// 授予持久化权限
|
|
||||||
grantPersistableUriPermission(selectedImage);
|
|
||||||
|
|
||||||
// 解析Uri为文件(调用工具类复制文件,兜底流复制)
|
|
||||||
File selectedFile = parseUriToFile(selectedImage);
|
|
||||||
if (selectedFile == null || !selectedFile.exists()) {
|
|
||||||
ToastUtils.show("选择的图片文件无效");
|
|
||||||
mBgSourceUtils.clearCropTempFiles();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同步到预览并启动裁剪(调用工具类保存预览)
|
|
||||||
mBgSourceUtils.saveFileToPreviewBean(selectedFile, selectedImage.toString());
|
|
||||||
bvPreviewBackground.reloadPreviewBackground();
|
|
||||||
startCropImageActivity(false);
|
|
||||||
LogUtils.d(TAG, "【选图完成】选图回调处理结束,已启动裁剪");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理拍照回调(调用工具类同步预览,精简文件操作)
|
* 处理拍照回调(调用工具类同步预览,精简文件操作)
|
||||||
*/
|
*/
|
||||||
@@ -804,48 +770,173 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 辅助函数:解析选图Uri为File(调用工具类复制,精简逻辑)
|
* 辅助函数:解析选图Uri为File(核心修复:适配Android11+ 共享存储私有路径)
|
||||||
|
* 放弃直接路径读取,改用ContentResolver流复制,避免Permission denied
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 辅助函数:解析选图Uri为File(核心修复:Android14+ 共享存储私有路径适配)
|
||||||
|
* 放弃直接路径读取,改用ContentResolver流复制,避免Permission denied
|
||||||
*/
|
*/
|
||||||
private File parseUriToFile(Uri uri) {
|
private File parseUriToFile(Uri uri) {
|
||||||
File targetFile = null;
|
File targetFile = null;
|
||||||
|
// 1. 尝试解析路径(兼容旧版本/普通路径)
|
||||||
String filePath = UriUtil.getFilePathFromUri(this, uri);
|
String filePath = UriUtil.getFilePathFromUri(this, uri);
|
||||||
LogUtils.d(TAG, "【选图解析】Uri解析路径:" + filePath);
|
LogUtils.d(TAG, "【选图解析】Uri解析路径:" + filePath);
|
||||||
|
|
||||||
// 直接解析成功
|
// 2. 路径有效且可读取(兼容Android 10- 或 非隐藏路径)
|
||||||
if (!TextUtils.isEmpty(filePath)) {
|
if (!TextUtils.isEmpty(filePath)) {
|
||||||
targetFile = new File(filePath);
|
File tempFile = new File(filePath);
|
||||||
|
// 双重校验:文件存在 + 实际可读取(避免canRead()假阳性)
|
||||||
|
if (isFileActuallyReadable(tempFile)) {
|
||||||
|
targetFile = tempFile;
|
||||||
|
} else {
|
||||||
|
// 路径存在但无权限 → 流复制兜底(核心修复)
|
||||||
|
targetFile = createTempFileByStreamCopy(uri);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 解析失败:流复制兜底(调用工具类目录创建)
|
// 3. 路径解析失败(ContentProvider Uri)→ 流复制兜底
|
||||||
targetFile = createTempFileByStreamCopy(uri);
|
targetFile = createTempFileByStreamCopy(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 校验目标文件有效性(避免后续逻辑崩溃)
|
||||||
|
if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) {
|
||||||
|
LogUtils.e(TAG, "【选图解析】生成的目标文件无效:" + (targetFile != null ? targetFile.getAbsolutePath() : "null"));
|
||||||
|
ToastUtils.show("图片读取失败,请重新选择");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LogUtils.d(TAG, "【选图解析】Uri解析成功,目标文件:" + targetFile.getAbsolutePath() + ",大小:" + targetFile.length() + "bytes");
|
||||||
return targetFile;
|
return targetFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 辅助函数:通过流复制生成选图临时文件(调用工具类管理目录)
|
* 辅助函数:通过ContentResolver流复制生成临时文件(核心修复:绕开共享存储权限限制)
|
||||||
|
* 直接读取Uri流,不依赖文件路径,适配所有相册Uri(包括私有隐藏路径)
|
||||||
*/
|
*/
|
||||||
private File createTempFileByStreamCopy(Uri uri) {
|
private File createTempFileByStreamCopy(Uri uri) {
|
||||||
// 从工具类获取背景源目录,作为选图临时目录(统一路径)
|
// 1. 初始化临时目录(复用工具类目录,统一路径管理)
|
||||||
File tempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp");
|
File tempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp");
|
||||||
if (!tempDir.exists()) {
|
if (!tempDir.exists()) {
|
||||||
mBgSourceUtils.copyFile(new File(""), tempDir); // 复用工具类目录创建逻辑
|
mBgSourceUtils.copyFile(new File(""), tempDir); // 复用工具类目录创建逻辑
|
||||||
}
|
}
|
||||||
|
|
||||||
File tempFile = new File(tempDir, "selected_temp.jpg");
|
// 2. 生成唯一临时文件名(避免重复)
|
||||||
|
String uniqueFileName = "selected_temp_" + System.currentTimeMillis() + ".jpg";
|
||||||
|
File tempFile = new File(tempDir, uniqueFileName);
|
||||||
|
|
||||||
|
// 3. 流复制(核心:用ContentResolver打开Uri流,绕开路径权限限制)
|
||||||
|
InputStream is = null;
|
||||||
|
FileOutputStream fos = null;
|
||||||
try {
|
try {
|
||||||
|
// 清理旧文件
|
||||||
if (tempFile.exists()) {
|
if (tempFile.exists()) {
|
||||||
mBgSourceUtils.clearOldFileByExternal(tempFile, "旧选图临时文件");
|
mBgSourceUtils.clearOldFileByExternal(tempFile, "旧选图临时文件");
|
||||||
}
|
}
|
||||||
// 流复制(适配ContentProvider Uri)
|
|
||||||
FileUtils.copyStreamToFile(getContentResolver().openInputStream(uri), tempFile);
|
// 打开Uri输入流(关键:Android14+ 仅允许通过ContentResolver读取共享存储私有文件)
|
||||||
LogUtils.d(TAG, "【选图解析】流复制生成临时文件:" + tempFile.getAbsolutePath());
|
is = getContentResolver().openInputStream(uri);
|
||||||
|
if (is == null) {
|
||||||
|
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败:" + uri.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开目标文件输出流
|
||||||
|
fos = new FileOutputStream(tempFile);
|
||||||
|
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,提升复制效率
|
||||||
|
int len;
|
||||||
|
// 循环读取流并写入目标文件
|
||||||
|
while ((len = is.read(buffer)) != -1) {
|
||||||
|
fos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
fos.flush();
|
||||||
|
fos.getFD().sync(); // 确保数据写入磁盘(避免缓冲导致文件损坏)
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "【选图解析】流复制成功:" + tempFile.getAbsolutePath() + ",大小:" + tempFile.length() + "bytes");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.e(TAG, "【选图解析】流复制失败:" + e.getMessage(), e);
|
LogUtils.e(TAG, "【选图解析】流复制失败:" + e.getMessage(), e);
|
||||||
tempFile = null;
|
tempFile = null;
|
||||||
|
ToastUtils.show("图片读取失败,请重新选择");
|
||||||
|
} finally {
|
||||||
|
// 关闭流资源(Java7 手动关闭,避免内存泄漏)
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "【选图解析】输入流关闭失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fos != null) {
|
||||||
|
try {
|
||||||
|
fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "【选图解析】输出流关闭失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助函数:校验文件是否实际可读取(解决Android14+ canRead()假阳性问题)
|
||||||
|
*/
|
||||||
|
private boolean isFileActuallyReadable(File file) {
|
||||||
|
if (file == null || !file.exists() || !file.isFile()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 实际尝试读取文件(避免路径存在但无权限)
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
fis.read(new byte[1]); // 读取1字节验证权限
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.w(TAG, "【选图解析】文件存在但无读取权限:" + file.getAbsolutePath());
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "【选图解析】校验流关闭失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理选图回调(新增:目标文件有效性校验,避免后续逻辑崩溃)
|
||||||
|
*/
|
||||||
|
private void handleSelectPictureResult(int resultCode, Intent data) {
|
||||||
|
if (resultCode != RESULT_OK || data == null) {
|
||||||
|
handleOperationCancelOrFail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri selectedImage = data.getData();
|
||||||
|
if (selectedImage == null) {
|
||||||
|
ToastUtils.show("选择的图片Uri为空");
|
||||||
|
mBgSourceUtils.clearCropTempFiles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogUtils.d(TAG, "【选图回调】选择图片Uri : " + selectedImage.toString());
|
||||||
|
|
||||||
|
// 授予持久化权限(Android4.4+)
|
||||||
|
grantPersistableUriPermission(selectedImage);
|
||||||
|
|
||||||
|
// 解析Uri为文件(核心:使用修复后的流复制方法)
|
||||||
|
File selectedFile = parseUriToFile(selectedImage);
|
||||||
|
if (selectedFile == null || !selectedFile.exists() || selectedFile.length() <= 0) {
|
||||||
|
ToastUtils.show("选择的图片文件无效");
|
||||||
|
mBgSourceUtils.clearCropTempFiles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步到预览并启动裁剪(此时文件已存在,避免传入目录路径)
|
||||||
|
mBgSourceUtils.saveFileToPreviewBean(selectedFile, selectedImage.toString());
|
||||||
|
bvPreviewBackground.reloadPreviewBackground();
|
||||||
|
startCropImageActivity(false);
|
||||||
|
LogUtils.d(TAG, "【选图完成】选图回调处理结束,已启动裁剪");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 辅助函数:从拍照数据中获取Bitmap(精简版,无文件操作)
|
* 辅助函数:从拍照数据中获取Bitmap(精简版,无文件操作)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import cc.winboll.studio.powerbell.model.BackgroundBean;
|
|||||||
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 java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
@@ -21,12 +25,13 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class BackgroundSourceUtils {
|
public class BackgroundSourceUtils {
|
||||||
|
|
||||||
public static final String TAG = "BackgroundPictureUtils";
|
public static final String TAG = "BackgroundSourceUtils";
|
||||||
// 裁剪相关常量(统一定义,避免硬编码)
|
// 裁剪相关常量(统一定义,避免硬编码)
|
||||||
private static final String CROP_TEMP_DIR_NAME = "CropTemp"; // 裁剪临时目录(FileProvider适配)
|
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_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"; // 裁剪输出结果文件
|
||||||
private static final String CROP_FALLBACK_DIR_NAME = "CropFallback"; // 裁剪兜底目录
|
private static final String CROP_FALLBACK_DIR_NAME = "CropFallback"; // 裁剪兜底目录
|
||||||
|
private static final String CROP_INNER_DIR_NAME = "CropInner"; // 优先裁剪目录(BackgroundSource下)
|
||||||
private static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider"; // 多包名兼容
|
private static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider"; // 多包名兼容
|
||||||
|
|
||||||
// 1. 静态实例加volatile,禁止指令重排,保证可见性(双重校验锁单例核心)
|
// 1. 静态实例加volatile,禁止指令重排,保证可见性(双重校验锁单例核心)
|
||||||
@@ -38,12 +43,14 @@ public class BackgroundSourceUtils {
|
|||||||
private BackgroundBean previewBackgroundBean;
|
private BackgroundBean previewBackgroundBean;
|
||||||
|
|
||||||
// 2. 统一文件目录(全量文件管理,替代Activity中的目录变量)
|
// 2. 统一文件目录(全量文件管理,替代Activity中的目录变量)
|
||||||
private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundPictureUtils)
|
private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundSourceUtils)
|
||||||
private File fModelDir; // 模型文件目录(存储BackgroundBean的JSON文件)
|
private File fModelDir; // 模型文件目录(存储BackgroundBean的JSON文件)
|
||||||
private File fBackgroundSourceDir; // 背景图片源目录(存储正式/预览图片)
|
private File fBackgroundSourceDir; // 背景图片源目录(存储正式/预览图片)
|
||||||
private File fCropTempDir; // 裁剪临时目录(FileProvider适配路径,系统裁剪应用可读写)
|
private File fCropTempDir; // 裁剪临时目录(FileProvider适配路径,系统裁剪应用可读写)
|
||||||
private File fCropFallbackDir; // 裁剪兜底目录(应用私有外部目录失败时使用)
|
private File fCropFallbackDir; // 裁剪兜底目录(应用私有外部目录失败时使用)
|
||||||
|
private File fCropInnerDir; // 新增:优先裁剪目录(BackgroundSource下,权限更可控)
|
||||||
private File cropTempFile; // 裁剪临时文件(系统裁剪应用写入目标)
|
private File cropTempFile; // 裁剪临时文件(系统裁剪应用写入目标)
|
||||||
|
private File cropInnerTempFile; // 新增:优先裁剪临时文件(CropInner目录下)
|
||||||
private File cropResultFile; // 裁剪结果文件(裁剪后保存的最终文件)
|
private File cropResultFile; // 裁剪结果文件(裁剪后保存的最终文件)
|
||||||
|
|
||||||
// 3. 私有构造器(加防反射逻辑+初始化所有目录/文件)
|
// 3. 私有构造器(加防反射逻辑+初始化所有目录/文件)
|
||||||
@@ -62,7 +69,7 @@ public class BackgroundSourceUtils {
|
|||||||
loadSettings();
|
loadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 双重校验锁单例(线程安全,高效,支持多线程并发调用)
|
// 4. 双重校验锁单例(线程安全,高效,支持多线程并发调用,Java7语法兼容)
|
||||||
public static BackgroundSourceUtils getInstance(Context context) {
|
public static BackgroundSourceUtils getInstance(Context context) {
|
||||||
// 第一重校验:避免每次调用都加锁(提升效率)
|
// 第一重校验:避免每次调用都加锁(提升效率)
|
||||||
if (sInstance == null) {
|
if (sInstance == null) {
|
||||||
@@ -78,26 +85,28 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化所有文件目录(统一管理,替代Activity中的目录初始化逻辑)
|
* 初始化所有文件目录(修改:优先初始化CropInner目录,确保权限可控)
|
||||||
* 包含:工具类根目录、模型目录、背景图目录、裁剪临时目录、裁剪兜底目录
|
* 包含:工具类根目录、模型目录、背景图目录、裁剪临时目录、裁剪兜底目录、优先裁剪目录
|
||||||
*/
|
*/
|
||||||
private void initAllDirs() {
|
private void initAllDirs() {
|
||||||
// 1. 工具类根目录(应用外部存储:/Android/data/包名/files/BackgroundPictureUtils)
|
// 1. 工具类根目录(应用外部存储:/Android/data/包名/files/BackgroundSourceUtils)
|
||||||
fUtilsDir = mContext.getExternalFilesDir(TAG);
|
fUtilsDir = mContext.getExternalFilesDir(TAG);
|
||||||
if (fUtilsDir == null) {
|
if (fUtilsDir == null) {
|
||||||
LogUtils.e(TAG, "【文件管理】应用外部存储不可用,切换到应用内部缓存目录");
|
LogUtils.e(TAG, "【文件管理】应用外部存储不可用,切换到应用内部缓存目录");
|
||||||
fUtilsDir = mContext.getCacheDir(); // 极端兜底:应用内部缓存目录
|
fUtilsDir = mContext.getCacheDir(); // 极端兜底:应用内部缓存目录
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 子目录初始化(按功能划分,确保目录存在并设置权限)
|
// 2. 子目录初始化(按功能划分,新增优先裁剪目录CropInner)
|
||||||
fModelDir = new File(fUtilsDir, "ModelDir"); // 模型文件目录(JSON配置)
|
fModelDir = new File(fUtilsDir, "ModelDir"); // 模型文件目录(JSON配置)
|
||||||
fBackgroundSourceDir = new File(fUtilsDir, "BackgroundSource"); // 背景图片目录
|
fBackgroundSourceDir = new File(fUtilsDir, "BackgroundSource"); // 背景图片目录
|
||||||
fCropTempDir = new File(fUtilsDir, CROP_TEMP_DIR_NAME); // 裁剪临时目录(FileProvider适配路径)
|
fCropTempDir = new File(fUtilsDir, CROP_TEMP_DIR_NAME); // 裁剪临时目录(FileProvider适配路径)
|
||||||
fCropFallbackDir = new File(fUtilsDir, CROP_FALLBACK_DIR_NAME); // 裁剪兜底目录
|
fCropFallbackDir = new File(fUtilsDir, CROP_FALLBACK_DIR_NAME); // 裁剪兜底目录
|
||||||
|
fCropInnerDir = new File(fBackgroundSourceDir, CROP_INNER_DIR_NAME); // 优先裁剪目录(BackgroundSource下)
|
||||||
|
|
||||||
// 3. 递归创建所有目录(确保目录存在,Android14+ 必需)
|
// 3. 递归创建所有目录(修改:优先创建CropInner目录,确保权限初始化)
|
||||||
createDirWithPermission(fModelDir, "模型文件目录");
|
createDirWithPermission(fModelDir, "模型文件目录");
|
||||||
createDirWithPermission(fBackgroundSourceDir, "背景图片目录");
|
createDirWithPermission(fBackgroundSourceDir, "背景图片目录");
|
||||||
|
createDirWithPermission(fCropInnerDir, "优先裁剪目录(BackgroundSource下)"); // 优先创建
|
||||||
createDirWithPermission(fCropTempDir, "裁剪临时目录(FileProvider适配)");
|
createDirWithPermission(fCropTempDir, "裁剪临时目录(FileProvider适配)");
|
||||||
createDirWithPermission(fCropFallbackDir, "裁剪兜底目录");
|
createDirWithPermission(fCropFallbackDir, "裁剪兜底目录");
|
||||||
|
|
||||||
@@ -105,42 +114,61 @@ public class BackgroundSourceUtils {
|
|||||||
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, "【文件管理】所有目录初始化完成:根目录=" + fUtilsDir.getAbsolutePath());
|
LogUtils.d(TAG, "【文件管理】所有目录初始化完成:根目录=" + fUtilsDir.getAbsolutePath() + ",优先裁剪目录=" + fCropInnerDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化所有文件(统一管理,替代Activity中的文件初始化逻辑)
|
* 初始化所有文件(修改:新增优先裁剪文件初始化)
|
||||||
* 包含:裁剪临时文件、裁剪结果文件
|
* 包含:优先裁剪临时文件、原裁剪临时文件、裁剪结果文件
|
||||||
*/
|
*/
|
||||||
private void initAllFiles() {
|
private void initAllFiles() {
|
||||||
// 1. 裁剪临时文件(系统裁剪应用写入路径,优先裁剪临时目录)
|
// 1. 新增:优先裁剪临时文件(BackgroundSource/CropInner下,FileProvider已配置)
|
||||||
|
cropInnerTempFile = new File(fCropInnerDir, CROP_TEMP_FILE_NAME);
|
||||||
|
// 2. 原裁剪临时文件(兼容旧逻辑)
|
||||||
cropTempFile = new File(fCropTempDir, CROP_TEMP_FILE_NAME);
|
cropTempFile = new File(fCropTempDir, CROP_TEMP_FILE_NAME);
|
||||||
// 2. 裁剪结果文件(裁剪后保存的最终文件,存入背景图片目录)
|
// 3. 裁剪结果文件(裁剪后保存的最终文件,存入背景图片目录)
|
||||||
cropResultFile = new File(fBackgroundSourceDir, CROP_RESULT_FILE_NAME);
|
cropResultFile = new File(fBackgroundSourceDir, CROP_RESULT_FILE_NAME);
|
||||||
|
|
||||||
// 3. 初始化时清理旧文件(避免文件锁定/权限残留)
|
// 4. 初始化时清理旧文件(避免文件锁定/权限残留)
|
||||||
|
clearOldFile(cropInnerTempFile, "旧优先裁剪临时文件"); // 清理优先裁剪文件
|
||||||
clearOldFile(cropTempFile, "旧裁剪临时文件");
|
clearOldFile(cropTempFile, "旧裁剪临时文件");
|
||||||
clearOldFile(cropResultFile, "旧裁剪结果文件");
|
clearOldFile(cropResultFile, "旧裁剪结果文件");
|
||||||
|
|
||||||
LogUtils.d(TAG, "【文件管理】所有文件初始化完成:裁剪临时文件=" + cropTempFile.getAbsolutePath() + ",裁剪结果文件=" + cropResultFile.getAbsolutePath());
|
LogUtils.d(TAG, "【文件管理】所有文件初始化完成:优先裁剪临时文件=" + cropInnerTempFile.getAbsolutePath() + ",裁剪结果文件=" + cropResultFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 核心函数:为系统裁剪应用创建可读写的FileProvider路径(满足需求)
|
* 核心函数:为系统裁剪应用创建可读写的FileProvider路径(修改:优先使用CropInner目录)
|
||||||
* 功能:确保裁剪临时目录(fCropTempDir)可读写,返回裁剪临时文件(系统裁剪应用写入目标)
|
* 适配:Android14+、MIUI,解决Permission denied+裁剪文件为0字节问题
|
||||||
* 适配:Android14+、MIUI,解决Permission denied问题,确保系统裁剪应用能正常写入
|
|
||||||
* @return 裁剪临时文件(File),系统裁剪应用可读写,路径已适配FileProvider
|
* @return 裁剪临时文件(File),系统裁剪应用可读写,路径已适配FileProvider
|
||||||
*/
|
*/
|
||||||
public File createCropFileProviderPath() {
|
public File createCropFileProviderPath() {
|
||||||
LogUtils.d(TAG, "【裁剪路径】createCropFileProviderPath 触发,创建系统裁剪可读写路径");
|
LogUtils.d(TAG, "【裁剪路径】createCropFileProviderPath 触发,创建系统裁剪可读写路径");
|
||||||
|
|
||||||
// 1. 优先尝试裁剪临时目录(FileProvider适配路径)
|
// 1. 优先使用BackgroundSource下的CropInner目录(核心修改:权限可控,FileProvider已配置)
|
||||||
|
if (fCropInnerDir != null && fCropInnerDir.exists() && isDirActuallyWritable(fCropInnerDir)) {
|
||||||
|
try {
|
||||||
|
// 重新初始化优先裁剪临时文件(先删后建,避免文件锁定)
|
||||||
|
clearOldFile(cropInnerTempFile, "优先裁剪临时文件(重新初始化)");
|
||||||
|
cropInnerTempFile.createNewFile();
|
||||||
|
// 强制设置文件权限(系统裁剪应用必需:允许所有用户读写)
|
||||||
|
setFilePermissions(cropInnerTempFile);
|
||||||
|
// 关键:将当前裁剪文件指向优先裁剪文件(上层Activity直接使用)
|
||||||
|
cropTempFile = cropInnerTempFile;
|
||||||
|
LogUtils.d(TAG, "【裁剪路径】系统裁剪可读写路径创建成功(优先裁剪目录):" + cropTempFile.getAbsolutePath());
|
||||||
|
return cropTempFile;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "【裁剪路径】优先裁剪目录创建文件失败:" + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.w(TAG, "【裁剪路径】优先裁剪目录不可用(不存在或无权限),切换到备用目录");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 备用:尝试裁剪临时目录(FileProvider适配路径)
|
||||||
if (isDirActuallyWritable(fCropTempDir)) {
|
if (isDirActuallyWritable(fCropTempDir)) {
|
||||||
try {
|
try {
|
||||||
// 重新初始化裁剪临时文件(先删后建,避免文件锁定)
|
|
||||||
clearOldFile(cropTempFile, "裁剪临时文件(重新初始化)");
|
clearOldFile(cropTempFile, "裁剪临时文件(重新初始化)");
|
||||||
cropTempFile.createNewFile();
|
cropTempFile.createNewFile();
|
||||||
// 强制设置文件权限(系统裁剪应用必需:允许所有用户读写)
|
|
||||||
setFilePermissions(cropTempFile);
|
setFilePermissions(cropTempFile);
|
||||||
LogUtils.d(TAG, "【裁剪路径】系统裁剪可读写路径创建成功(裁剪临时目录):" + cropTempFile.getAbsolutePath());
|
LogUtils.d(TAG, "【裁剪路径】系统裁剪可读写路径创建成功(裁剪临时目录):" + cropTempFile.getAbsolutePath());
|
||||||
return cropTempFile;
|
return cropTempFile;
|
||||||
@@ -149,7 +177,7 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 兜底1:裁剪临时目录失败,切换到裁剪兜底目录
|
// 3. 兜底1:裁剪兜底目录
|
||||||
if (isDirActuallyWritable(fCropFallbackDir)) {
|
if (isDirActuallyWritable(fCropFallbackDir)) {
|
||||||
try {
|
try {
|
||||||
cropTempFile = new File(fCropFallbackDir, CROP_TEMP_FILE_NAME);
|
cropTempFile = new File(fCropFallbackDir, CROP_TEMP_FILE_NAME);
|
||||||
@@ -163,7 +191,7 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 终极兜底:应用内部缓存目录(确保不崩溃)
|
// 4. 终极兜底:应用内部缓存目录(提示用户权限问题)
|
||||||
File cacheDir = mContext.getCacheDir();
|
File cacheDir = mContext.getCacheDir();
|
||||||
if (isDirActuallyWritable(cacheDir)) {
|
if (isDirActuallyWritable(cacheDir)) {
|
||||||
try {
|
try {
|
||||||
@@ -171,11 +199,12 @@ public class BackgroundSourceUtils {
|
|||||||
clearOldFile(cropTempFile, "裁剪临时文件(终极兜底)");
|
clearOldFile(cropTempFile, "裁剪临时文件(终极兜底)");
|
||||||
cropTempFile.createNewFile();
|
cropTempFile.createNewFile();
|
||||||
setFilePermissions(cropTempFile);
|
setFilePermissions(cropTempFile);
|
||||||
LogUtils.w(TAG, "【裁剪路径】应用外部目录全部失败,终极兜底到缓存目录:" + cropTempFile.getAbsolutePath());
|
LogUtils.w(TAG, "【裁剪路径】应用外部目录全部失败,终极兜底到缓存目录(MIUI可能裁剪失败):" + cropTempFile.getAbsolutePath());
|
||||||
|
ToastUtils.show("存储权限受限,建议授予「所有文件访问权限」以确保裁剪正常");
|
||||||
return cropTempFile;
|
return cropTempFile;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LogUtils.e(TAG, "【裁剪路径】终极兜底目录创建文件失败:" + e.getMessage(), e);
|
LogUtils.e(TAG, "【裁剪路径】终极兜底目录创建文件失败:" + e.getMessage(), e);
|
||||||
ToastUtils.show("裁剪路径创建失败,请重启应用");
|
ToastUtils.show("裁剪路径创建失败,请重启应用并授予存储权限");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,15 +317,79 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存图片到预览Bean(核心修复:解决文件名覆盖+路径无效问题)
|
* 新增:流复制文件(核心修复:Android14+ 共享存储权限限制适配)
|
||||||
|
* 不依赖文件路径,直接通过流复制,支持读取相册私有隐藏文件,避免Permission denied
|
||||||
|
* @param source 源文件(可为共享存储私有文件)
|
||||||
|
* @param target 目标文件(应用私有目录,确保可写)
|
||||||
|
* @return true=复制成功,false=失败
|
||||||
|
*/
|
||||||
|
public boolean copyFileByStream(File source, File target) {
|
||||||
|
if (source == null || !source.exists() || !source.isFile() || target == null) {
|
||||||
|
LogUtils.e(TAG, "【文件管理】流复制失败:源文件无效或目标文件为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保目标目录存在
|
||||||
|
File targetDir = target.getParentFile();
|
||||||
|
if (!targetDir.exists()) {
|
||||||
|
createDirWithPermission(targetDir, "流复制目标目录");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream fis = null;
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
// 打开源文件输入流(支持共享存储私有文件)
|
||||||
|
fis = new FileInputStream(source);
|
||||||
|
// 打开目标文件输出流
|
||||||
|
fos = new FileOutputStream(target);
|
||||||
|
|
||||||
|
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,提升复制效率
|
||||||
|
int len;
|
||||||
|
// 循环读取流并写入目标文件(Java7 普通for循环,兼容语法)
|
||||||
|
while ((len = fis.read(buffer)) != -1) {
|
||||||
|
fos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
fos.flush();
|
||||||
|
fos.getFD().sync(); // 强制同步到磁盘,确保文件写入完成(Java7 支持)
|
||||||
|
LogUtils.d(TAG, "【文件管理】流复制成功:" + source.getAbsolutePath() + " → " + target.getAbsolutePath() + ",大小:" + target.length() + "bytes");
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "【文件管理】流复制异常:" + e.getMessage(), e);
|
||||||
|
// 复制失败时删除目标文件(避免残留空文件)
|
||||||
|
if (target.exists()) {
|
||||||
|
clearOldFileByExternal(target, "流复制失败残留文件");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
// 关闭流资源(Java7 手动关闭,避免内存泄漏,不使用try-with-resources)
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "【文件管理】源文件流关闭失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fos != null) {
|
||||||
|
try {
|
||||||
|
fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "【文件管理】目标文件流关闭失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存图片到预览Bean(核心修复:替换路径复制为流复制,避免预览路径错误)
|
||||||
* @param sourceFile 源图片文件(非空,必须存在)
|
* @param sourceFile 源图片文件(非空,必须存在)
|
||||||
* @param fileInfo 图片附加信息(如Uri字符串,仅作备注)
|
* @param fileInfo 图片附加信息(如Uri字符串,仅作备注)
|
||||||
* @return 更新后的预览Bean
|
* @return 更新后的预览Bean
|
||||||
*/
|
*/
|
||||||
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
|
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
|
||||||
// 校验源文件合法性
|
// 强化校验:源文件必须存在、是文件、大小>0
|
||||||
if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile()) {
|
if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile() || sourceFile.length() <= 0) {
|
||||||
LogUtils.e(TAG, "【文件管理】源文件无效:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null"));
|
LogUtils.e(TAG, "【文件管理】源文件无效:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null") + ",大小:" + (sourceFile != null ? sourceFile.length() : 0) + "bytes");
|
||||||
ToastUtils.show("源图片文件无效");
|
ToastUtils.show("源图片文件无效");
|
||||||
return previewBackgroundBean;
|
return previewBackgroundBean;
|
||||||
}
|
}
|
||||||
@@ -310,26 +403,26 @@ public class BackgroundSourceUtils {
|
|||||||
String uniqueFileName = FileUtils.createUniqueFileName(sourceFile);
|
String uniqueFileName = FileUtils.createUniqueFileName(sourceFile);
|
||||||
File previewBackgroundFile = new File(fBackgroundSourceDir, uniqueFileName);
|
File previewBackgroundFile = new File(fBackgroundSourceDir, uniqueFileName);
|
||||||
|
|
||||||
// 复制源文件到预览目录(确保图片实际保存成功)
|
// 核心修改:用流复制替代原FileUtils.copyFile,解决共享存储权限问题
|
||||||
boolean copySuccess = FileUtils.copyFile(sourceFile, previewBackgroundFile);
|
boolean copySuccess = copyFileByStream(sourceFile, previewBackgroundFile);
|
||||||
if (!copySuccess) {
|
if (!copySuccess) {
|
||||||
LogUtils.e(TAG, "【文件管理】图片复制到预览目录失败:" + sourceFile.getAbsolutePath() + " → " + previewBackgroundFile.getAbsolutePath());
|
LogUtils.e(TAG, "【文件管理】图片复制到预览目录失败:" + sourceFile.getAbsolutePath() + " → " + previewBackgroundFile.getAbsolutePath());
|
||||||
ToastUtils.show("预览图片保存失败");
|
ToastUtils.show("预览图片保存失败");
|
||||||
return previewBackgroundBean;
|
return previewBackgroundBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 正确赋值预览Bean(核心修复:不覆盖文件名,将附加信息存入backgroundFileInfo)
|
// 正确赋值预览Bean(确保文件名非空,避免后续路径为空)
|
||||||
previewBackgroundBean = new BackgroundBean();
|
previewBackgroundBean = new BackgroundBean();
|
||||||
previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); // 正确赋值:唯一文件名
|
previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); // 唯一文件名(非空)
|
||||||
previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_" + previewBackgroundFile.getName()); // 压缩文件名(前缀标识)
|
previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_" + previewBackgroundFile.getName()); // 压缩文件名(前缀标识)
|
||||||
previewBackgroundBean.setBackgroundFileInfo(fileInfo); // 正确赋值:附加信息(Uri)
|
previewBackgroundBean.setBackgroundFileInfo(fileInfo); // 附加信息(Uri)
|
||||||
previewBackgroundBean.setIsUseBackgroundFile(true); // 标记使用背景图
|
previewBackgroundBean.setIsUseBackgroundFile(true); // 标记使用背景图
|
||||||
previewBackgroundBean.setIsUseScaledCompress(true); // 启用压缩图,提升预览加载速度
|
previewBackgroundBean.setIsUseScaledCompress(true); // 启用压缩图
|
||||||
previewBackgroundBean.setBackgroundWidth(100); // 默认宽高比1:1(可根据需求调整)
|
previewBackgroundBean.setBackgroundWidth(100); // 默认宽高比1:1
|
||||||
previewBackgroundBean.setBackgroundHeight(100);
|
previewBackgroundBean.setBackgroundHeight(100);
|
||||||
saveSettings(); // 持久化保存预览Bean
|
saveSettings(); // 持久化保存预览Bean
|
||||||
|
|
||||||
LogUtils.d(TAG, "【文件管理】预览图片保存成功:" + previewBackgroundFile.getAbsolutePath());
|
LogUtils.d(TAG, "【文件管理】预览图片保存成功:" + previewBackgroundFile.getAbsolutePath() + ",大小:" + previewBackgroundFile.length() + "bytes");
|
||||||
ToastUtils.show("预览图片加载成功");
|
ToastUtils.show("预览图片加载成功");
|
||||||
return previewBackgroundBean;
|
return previewBackgroundBean;
|
||||||
}
|
}
|
||||||
@@ -417,10 +510,11 @@ public class BackgroundSourceUtils {
|
|||||||
|
|
||||||
LogUtils.d(TAG, "【权限管理】目录权限设置完成:路径=" + dir.getAbsolutePath() + ",可写=" + dir.canWrite() + ",可读=" + dir.canRead());
|
LogUtils.d(TAG, "【权限管理】目录权限设置完成:路径=" + dir.getAbsolutePath() + ",可写=" + dir.canWrite() + ",可读=" + dir.canRead());
|
||||||
|
|
||||||
// 递归处理子目录和文件
|
// 递归处理子目录和文件(Java7 普通for循环,兼容语法)
|
||||||
File[] files = dir.listFiles();
|
File[] files = dir.listFiles();
|
||||||
if (files != null && files.length > 0) {
|
if (files != null && files.length > 0) {
|
||||||
for (File file : files) {
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
File file = files[i];
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
setDirPermissionsRecursively(file);
|
setDirPermissionsRecursively(file);
|
||||||
} else {
|
} else {
|
||||||
@@ -464,16 +558,11 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具方法:清理旧文件(避免文件锁定/残留)
|
|
||||||
* @param file 要清理的文件
|
|
||||||
* @param fileDesc 文件描述(用于日志打印)
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* 工具方法:清理旧文件(避免文件锁定/残留)【内部私有,不对外暴露】
|
* 工具方法:清理旧文件(避免文件锁定/残留)【内部私有,不对外暴露】
|
||||||
* @param file 要清理的文件
|
* @param file 要清理的文件
|
||||||
* @param fileDesc 文件描述(用于日志打印)
|
* @param fileDesc 文件描述(用于日志打印)
|
||||||
*/
|
*/
|
||||||
private void clearOldFile(File file, String fileDesc) {
|
private void clearOldFile(File file, String fileDesc) {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -504,11 +593,12 @@ public class BackgroundSourceUtils {
|
|||||||
boolean createSuccess = testFile.createNewFile();
|
boolean createSuccess = testFile.createNewFile();
|
||||||
if (createSuccess) {
|
if (createSuccess) {
|
||||||
boolean canWrite = testFile.canWrite();
|
boolean canWrite = testFile.canWrite();
|
||||||
|
boolean canRead = testFile.canRead();
|
||||||
testFile.delete(); // 删除临时文件,不占用空间
|
testFile.delete(); // 删除临时文件,不占用空间
|
||||||
LogUtils.d(TAG, "【权限校验】目录实际写入校验:" + dir.getAbsolutePath() + ",结果=" + (canWrite ? "通过" : "失败"));
|
LogUtils.d(TAG, "【权限校验】目录实际写入校验:" + dir.getAbsolutePath() + ",创建成功=" + createSuccess + ",可写=" + canWrite + ",可读=" + canRead + ",结果=" + (canWrite ? "通过" : "失败"));
|
||||||
return canWrite;
|
return canWrite;
|
||||||
} else {
|
} else {
|
||||||
LogUtils.d(TAG, "【权限校验】目录实际写入校验失败:" + dir.getAbsolutePath() + ",创建临时文件失败");
|
LogUtils.d(TAG, "【权限校验】目录实际写入校验失败:" + dir.getAbsolutePath() + ",创建临时文件失败(Permission denied)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -558,11 +648,24 @@ public class BackgroundSourceUtils {
|
|||||||
public void clearCropTempFiles() {
|
public void clearCropTempFiles() {
|
||||||
clearOldFile(cropTempFile, "裁剪临时文件");
|
clearOldFile(cropTempFile, "裁剪临时文件");
|
||||||
clearOldFile(cropResultFile, "裁剪结果文件");
|
clearOldFile(cropResultFile, "裁剪结果文件");
|
||||||
// 清理裁剪目录下的其他临时文件
|
// 清理裁剪目录下的其他临时文件(Java7 普通for循环)
|
||||||
if (fCropTempDir.exists()) {
|
if (fCropTempDir.exists()) {
|
||||||
File[] files = fCropTempDir.listFiles();
|
File[] files = fCropTempDir.listFiles();
|
||||||
if (files != null && files.length > 0) {
|
if (files != null && files.length > 0) {
|
||||||
for (File file : files) {
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
File file = files[i];
|
||||||
|
if (file.isFile()) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 新增:清理优先裁剪目录(CropInner)下的临时文件
|
||||||
|
if (fCropInnerDir != null && fCropInnerDir.exists()) {
|
||||||
|
File[] files = fCropInnerDir.listFiles();
|
||||||
|
if (files != null && files.length > 0) {
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
File file = files[i];
|
||||||
if (file.isFile()) {
|
if (file.isFile()) {
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
@@ -572,7 +675,7 @@ public class BackgroundSourceUtils {
|
|||||||
LogUtils.d(TAG, "【文件管理】裁剪相关临时文件清理完成");
|
LogUtils.d(TAG, "【文件管理】裁剪相关临时文件清理完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对外接口:清理指定旧文件(适配BackgroundSettingsActivity调用)
|
* 对外接口:清理指定旧文件(适配BackgroundSettingsActivity调用)
|
||||||
* @param file 要清理的文件
|
* @param file 要清理的文件
|
||||||
* @param fileDesc 文件描述(用于日志打印)
|
* @param fileDesc 文件描述(用于日志打印)
|
||||||
@@ -604,4 +707,3 @@ public class BackgroundSourceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
name="background_source"
|
name="background_source"
|
||||||
path="BackgroundPictureUtils/BackgroundSource/" />
|
path="BackgroundPictureUtils/BackgroundSource/" />
|
||||||
|
|
||||||
|
<!-- 应用私有外部存储目录(适配BackgroundSourceUtils的目录) -->
|
||||||
|
<external-files-path
|
||||||
|
name="external_file_path"
|
||||||
|
path="BackgroundSourceUtils/" /> <!-- 对应fUtilsDir(BackgroundSourceUtils根目录) -->
|
||||||
|
|
||||||
<!-- 应用外部缓存目录(适配Android11+ 外部缓存场景,如第三方SDK依赖)
|
<!-- 应用外部缓存目录(适配Android11+ 外部缓存场景,如第三方SDK依赖)
|
||||||
路径:/storage/emulated/0/Android/data/${applicationId}/cache/
|
路径:/storage/emulated/0/Android/data/${applicationId}/cache/
|
||||||
关联代码:getExternalCacheDir() -->
|
关联代码:getExternalCacheDir() -->
|
||||||
|
|||||||
Reference in New Issue
Block a user