20251201_151821_718

This commit is contained in:
2025-12-01 15:18:26 +08:00
parent 04b8906a96
commit 1cadc4ed93
6 changed files with 291 additions and 271 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Mon Dec 01 02:15:51 GMT 2025
#Mon Dec 01 07:14:25 GMT 2025
stageCount=13
libraryProject=
baseVersion=15.11
publishVersion=15.11.12
buildCount=48
buildCount=50
baseBetaVersion=15.11.13

View File

@@ -86,7 +86,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_backgroundpicture);
setContentView(R.layout.activity_background_settings);
// 初始化核心控件
bvPreviewBackground = (BackgroundView) findViewById(R.id.activitybackgroundpictureBackgroundView1);
// 初始化工具类(文件管理全依赖此类)
@@ -104,13 +104,25 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 初始化按钮点击事件仅UI交互无文件逻辑
initClickListeners();
// 初始化预览
initPreview();
// 关键窗口启动时将正式Bean的内容完整拷贝到预览Bean覆盖旧预览Bean
initPreviewBeanFromFormal();
// 处理分享图片意图
handleShareIntent();
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成精简版文件管理依赖BackgroundSourceUtils");
}
/**
* 初始化预览Bean从正式Bean拷贝所有属性确保每次操作都是正式图的副本
*/
private void initPreviewBeanFromFormal() {
LogUtils.d(TAG, "【Bean初始化】正式Bean → 预览Bean拷贝初始化");
mBgSourceUtils.setCurrentSourceToPreview();
// 加载预览Bean中的图片窗口启动时显示正式图的预览
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【Bean初始化】预览Bean初始化完成当前预览路径" + mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath());
}
/**
* 初始化工具栏仅UI逻辑
*/
@@ -145,15 +157,15 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
/**
* 初始化预览(调用工具类同步正式背景到预览)
*/
private void initPreview() {
mBgSourceUtils.setCurrentSourceToPreview();
if (bvPreviewBackground != null) {
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【初始化】预览视图已加载BackgroundView 状态:正常");
} else {
LogUtils.e(TAG, "【初始化】bvPreviewBackground 为空,预览加载失败");
}
}
// private void initPreview() {
// mBgSourceUtils.setCurrentSourceToPreview();
// if (bvPreviewBackground != null) {
// bvPreviewBackground.reloadPreviewBackground();
// LogUtils.d(TAG, "【初始化】预览视图已加载BackgroundView 状态:正常");
// } else {
// LogUtils.e(TAG, "【初始化】bvPreviewBackground 为空,预览加载失败");
// }
// }
/**
* 处理分享图片意图仅UI逻辑文件处理调用工具类
@@ -461,6 +473,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 确保父目录存在(调用工具类创建目录,避免重复代码)
File parentDir = fRecivedPicture.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
mBgSourceUtils.copyFile(new File(""), parentDir); // 复用工具类目录创建逻辑
LogUtils.d(TAG, "【压缩准备】目标目录已通过工具类创建:" + parentDir.getAbsolutePath());
}
@@ -528,7 +541,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
public void startCropImageActivity(boolean isCropFree) {
LogUtils.d(TAG, "【裁剪启动】startCropImageActivity 触发,自由裁剪:" + isCropFree);
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseScaledCompress(true);
previewBean.setIsUseBackgroundScaledCompressFile(true);
mBgSourceUtils.saveSettings();
// 1. 预览图片有效性校验(保留强化校验逻辑)
@@ -985,11 +998,24 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return;
}
// 同步到预览并启动裁剪(此时文件已存在,避免传入目录路径
// 关键修复:选图后生成压缩图(确保预览时压缩图存在
LogUtils.d(TAG, "【选图压缩】开始生成预览压缩图");
Bitmap selectBitmap = BitmapFactory.decodeFile(selectedFile.getAbsolutePath());
if (selectBitmap != null && !selectBitmap.isRecycled()) {
compressQualityToRecivedPicture(selectBitmap); // 生成压缩图
selectBitmap.recycle(); // 回收Bitmap避免OOM
} else {
ToastUtils.show("选图后压缩图生成失败");
LogUtils.e(TAG, "【选图压缩失败】无法解析选图文件为Bitmap");
mBgSourceUtils.clearCropTempFiles();
return;
}
// 同步到预览并启动裁剪(此时正式图和压缩图均已存在)
mBgSourceUtils.saveFileToPreviewBean(selectedFile, selectedImage.toString());
bvPreviewBackground.reloadPreviewBackground();
startCropImageActivity(false);
LogUtils.d(TAG, "【选图完成】选图回调处理结束,已启动裁剪");
LogUtils.d(TAG, "【选图完成】选图回调处理结束,已启动裁剪(正式图+压缩图均已生成)");
}
/**
@@ -1062,14 +1088,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
* 保存裁剪后的Bitmap调用工具类保存精简文件逻辑
*/
private void saveCropBitmap(Bitmap bitmap) {
LogUtils.d(TAG, "【保存启动】开始保存裁剪图片");
LogUtils.d(TAG, "【保存启动】开始保存裁剪图片仅更新预览Bean不影响正式Bean");
if (bitmap == null || bitmap.isRecycled()) {
ToastUtils.show("裁剪图片为空");
mBgSourceUtils.clearCropTempFiles();
return;
}
// 内存优化:大图片缩放(调整阈值为5MB更适配低内存机型
// 内存优化:大图片缩放(保留原有逻辑
Bitmap scaledBitmap = bitmap;
int originalSize = bitmap.getByteCount() / 1024 / 1024; // 转换为MB
if (originalSize > 5) { // 超过5MB自动缩放
@@ -1085,77 +1111,98 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}
FileOutputStream fos = null;
// 新增:定义裁剪后新文件的文件名(与工具类保持一致的唯一命名规则)
String cropResultFileName = FileUtils.createUniqueFileName(new File("crop_result.jpg"));
try {
// 从工具类获取预览图片目录(统一路径管理
String backgroundSourceDir = mBgSourceUtils.getBackgroundSourceDirPath();
// 裁剪后新图的目标路径(正式图+压缩图,路径统一)
String cropResultFilePath = backgroundSourceDir + File.separator + cropResultFileName;
String cropCompressFilePath = backgroundSourceDir + File.separator + "ScaledCompress_" + cropResultFileName;
// 1. 生成唯一文件名(仅用于预览图,正式图由退出时拷贝生成
String uniqueFileName = FileUtils.createUniqueFileName(new File("background.jpg"));
String compressFileName = "ScaledCompress_" + uniqueFileName; // 预览压缩图文件名
File targetCompressFile = new File(cropCompressFilePath);
// 2. 定义预览图路径(统一目录,仅操作预览相关文件)
String backgroundDir = mBgSourceUtils.getBackgroundSourceDirPath();
File targetPreviewOriginalFile = new File(backgroundDir, uniqueFileName); // 裁剪后预览正式图
File targetPreviewCompressFile = new File(backgroundDir, compressFileName); // 裁剪后预览压缩图
// 确保目录存在(调用工具类)
File parentDir = targetCompressFile.getParentFile();
// 确保目录存在(调用工具类,避免创建失败
File parentDir = targetPreviewOriginalFile.getParentFile();
if (!parentDir.exists()) {
mBgSourceUtils.copyFile(new File(""), parentDir);
}
// 清理旧文件(调用工具类
if (targetCompressFile.exists()) {
mBgSourceUtils.clearOldFileByExternal(targetCompressFile, "裁剪结果文件");
// 3. 保存裁剪后的预览正式图(仅写入预览目录,不触碰正式图
if (targetPreviewOriginalFile.exists()) {
mBgSourceUtils.clearOldFileByExternal(targetPreviewOriginalFile, "预览正式图");
}
targetCompressFile.createNewFile();
mBgSourceUtils.setFilePermissions(targetCompressFile); // 调用工具类设置权限
targetPreviewOriginalFile.createNewFile();
mBgSourceUtils.setFilePermissions(targetPreviewOriginalFile);
// 压缩保存
fos = new FileOutputStream(targetCompressFile);
Bitmap.CompressFormat format = targetCompressFile.getName().endsWith(".png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG;
boolean success = scaledBitmap.compress(format, 80, fos);
fos = new FileOutputStream(targetPreviewOriginalFile);
Bitmap.CompressFormat format = targetPreviewOriginalFile.getName().endsWith(".png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG;
boolean previewOriginalSaveSuccess = scaledBitmap.compress(format, 80, fos);
fos.flush();
fos.getFD().sync(); // 强制同步磁盘,确保文件100%保存完成
fos.getFD().sync(); // 强制同步磁盘,确保文件写入完成
fos.close(); // 关闭预览正式图流
if (success) {
ToastUtils.show("图片保存成功");
// 核心修复1同步预览Bean的「文件名+压缩图路径」(两者必须匹配)
// 4. 生成预览压缩图预览Bean依赖此图必须生成
LogUtils.d(TAG, "【裁剪压缩】开始生成预览压缩图:" + targetPreviewCompressFile.getAbsolutePath());
FileOutputStream compressFos = new FileOutputStream(targetPreviewCompressFile);
boolean previewCompressSaveSuccess = scaledBitmap.compress(format, 50, compressFos); // 压缩质量50%(按需调整)
compressFos.flush();
compressFos.getFD().sync();
compressFos.close();
LogUtils.d(TAG, "【裁剪压缩】预览压缩图保存" + (previewCompressSaveSuccess ? "成功" : "失败") + ",大小:" + targetPreviewCompressFile.length() + "bytes");
// 5. 关键逻辑仅更新预览Bean正式Bean不改动留待退出时确认
if (previewOriginalSaveSuccess && previewCompressSaveSuccess
&& targetPreviewOriginalFile.length() > 100
&& targetPreviewCompressFile.length() > 100) {
// 获取当前预览Bean窗口启动时已由正式Bean拷贝而来
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setBackgroundFileName(cropResultFileName); // 同步正式文件名(关键!
previewBean.setBackgroundScaledCompressFileName("ScaledCompress_" + cropResultFileName); // 同步压缩文件名
previewBean.setBackgroundScaledCompressFilePath(cropCompressFilePath); // 同步压缩路径
previewBean.setIsUseBackgroundFile(true);
mBgSourceUtils.saveSettings(); // 持久化配置确保BackgroundView读取时生效
// 仅更新预览Bean的路径信息不操作正式Bean
previewBean.setBackgroundFileName(uniqueFileName); // 预览正式图文件名
previewBean.setBackgroundFilePath(targetPreviewOriginalFile.getAbsolutePath()); // 预览正式图路径
previewBean.setBackgroundScaledCompressFileName(compressFileName); // 预览压缩图文件名
previewBean.setBackgroundScaledCompressFilePath(targetPreviewCompressFile.getAbsolutePath()); // 预览压缩图路径
previewBean.setIsUseBackgroundFile(true); // 标记预览图可用
previewBean.setIsUseBackgroundScaledCompressFile(false); // 标记:未确认成为正式图(关键标记)
mBgSourceUtils.saveSettings(); // 仅持久化预览Bean不保存正式Bean
// 核心修复2调用工具类同步新图到预览确保工具类路径与Bean一致
mBgSourceUtils.saveFileToPreviewBean(targetCompressFile, cropCompressFilePath);
// 同步预览文件到预览Bean目录工具类仅操作预览相关文件
mBgSourceUtils.saveFileToPreviewBean(targetPreviewOriginalFile, targetPreviewOriginalFile.getAbsolutePath());
mBgSourceUtils.saveFileToPreviewBean(targetPreviewCompressFile, targetPreviewCompressFile.getAbsolutePath());
// 核心修复3调整刷新时机——文件100%保存完成后,再触发双重刷新(避免过早刷新)
ToastUtils.show("预览图片保存成功(未设为正式背景)");
// 触发预览刷新仅加载预览Bean中的路径
doubleRefreshPreview();
} else {
ToastUtils.show("图片保存失败");
LogUtils.e(TAG, "【保存失败】Bitmap压缩失败");
// 回滚配置
ToastUtils.show("预览图片保存失败");
LogUtils.e(TAG, "预览保存失败】正式图:" + previewOriginalSaveSuccess + ",压缩图:" + previewCompressSaveSuccess);
// 回滚预览Bean标记为未使用文件
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseBackgroundFile(false);
previewBean.setIsUseBackgroundScaledCompressFile(false);
mBgSourceUtils.saveSettings();
}
} catch (Exception e) {
LogUtils.e(TAG, "【保存异常】" + e.getMessage(), e);
ToastUtils.show("图片保存失败");
LogUtils.e(TAG, "裁剪保存异常】" + e.getMessage(), e);
ToastUtils.show("预览图片保存失败");
// 异常时回滚预览Bean
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseBackgroundFile(false);
previewBean.setIsUseBackgroundScaledCompressFile(false);
mBgSourceUtils.saveSettings();
} finally {
// 关闭流
// 关闭流资源
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
LogUtils.e(TAG, "【保存异常】流关闭失败:" + e.getMessage());
LogUtils.e(TAG, "裁剪保存异常】流关闭失败:" + e.getMessage());
}
}
// 回收Bitmap
if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
scaledBitmap.recycle();
}
// 清理裁剪临时文件(调用工具类
// 清理裁剪临时文件(仅清理缓存,不影响预览/正式文件
mBgSourceUtils.clearCropTempFiles();
}
}
@@ -1179,32 +1226,39 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
runOnUiThread(new Runnable() {
@Override
public void run() {
// 新增:校验预览图是否存在(避免文件未保存完成就刷新)
final String previewFilePath = mBgSourceUtils.getPreviewBackgroundFilePath();
File previewFile = new File(previewFilePath);
if (!previewFile.exists() || previewFile.length() <= 100) {
LogUtils.w(TAG, "【预览刷新】预览图未就绪延迟500ms后刷新");
// 延迟500ms重试适配大图片保存延迟
// 1. 同时校验正式图和压缩图(两者都存在才刷新)
String previewOriginalPath = mBgSourceUtils.getPreviewBackgroundFilePath();
final String previewCompressPath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath();
File originalFile = new File(previewOriginalPath);
File compressFile = new File(previewCompressPath);
// 校验逻辑:正式图存在 + 压缩图存在 + 大小均>100字节避免空文件
boolean isPreviewReady = originalFile.exists() && originalFile.length() > 100
&& compressFile.exists() && compressFile.length() > 100;
if (!isPreviewReady) {
LogUtils.w(TAG, "【预览刷新】预览图未就绪(正式图:" + originalFile.exists() + ",压缩图:" + compressFile.exists() + "延迟500ms重试");
// 延迟500ms重试适配压缩图生成延迟
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览刷新】延迟重试刷新500ms");
LogUtils.d(TAG, "【预览刷新】延迟重试刷新500ms,压缩图路径:" + previewCompressPath);
}
}, 500);
return;
}
// 第一次刷新(立即)
// 2. 第一次刷新(立即)
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览刷新】第一次刷新(立即),路径:" + previewFilePath);
LogUtils.d(TAG, "【预览刷新】第一次刷新(立即),正式图路径:" + previewOriginalPath + ",压缩图路径:" + previewCompressPath);
// 第二次刷新延迟300ms适配MIUI渲染延迟
// 3. 第二次刷新延迟300ms适配MIUI渲染延迟
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览刷新】第二次刷新延迟300ms路径" + previewFilePath);
LogUtils.d(TAG, "【预览刷新】第二次刷新延迟300ms压缩图路径:" + previewCompressPath);
}
}, 300);
}
@@ -1347,7 +1401,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
LogUtils.d(TAG, "【图片接收】已启动自由裁剪,裁剪路径由工具类管理");
}
};
/**
* 重写finish方法确保所有退出场景都触发确认提示仅操作Bean无文件逻辑
*/

View File

@@ -16,16 +16,18 @@ public class BackgroundBean extends BaseBean {
// 核心字段背景图片文件名对应应用私有目录下的图片文件与BackgroundSettingsActivity的_mSourceCroppedFile匹配
private String backgroundFileName = "";
// 核心字段背景图片完整路径解决仅存文件名导致的路径拼接错误与backgroundScaledCompressFilePath对应
private String backgroundFilePath = "";
// 附加字段图片信息如Uri、网络地址等仅作备注不参与路径生成
private String backgroundFileInfo = "";
// 控制字段是否启用背景图片true-显示背景图false-显示透明背景)
private boolean isUseBackgroundFile = false;
// 核心字段压缩后背景图片文件名对应应用私有目录下的压缩图片与saveCropBitmap的压缩图匹配
private String backgroundScaledCompressFileName = "";
// 新增压缩后背景图片完整路径解决仅存文件名导致的路径拼接错误适配BackgroundSettingsActivity的私有目录
// 核心字段压缩后背景图片完整路径解决仅存文件名导致的路径拼接错误适配BackgroundSettingsActivity的私有目录
private String backgroundScaledCompressFilePath = "";
// 控制字段:是否启用压缩背景图(true-加载压缩图false-加载原图
private boolean isUseScaledCompress = false;
// 重命名字段:是否启用压缩背景图(原isUseScaledCompress → 新isUseBackgroundScaledCompressFile语义更清晰
private boolean isUseBackgroundScaledCompressFile = false;
// 裁剪比例字段背景图宽高比默认1:1用于固定比例裁剪
private int backgroundWidth = 100;
private int backgroundHeight = 100;
@@ -38,7 +40,7 @@ public class BackgroundBean extends BaseBean {
public BackgroundBean() {
}
// ====================================== Getter/Setter 方法(全字段,确保序列化/反序列化完整======================================
// ====================================== Getter/Setter 方法(全字段,含重命名+新增字段======================================
public String getBackgroundFileName() {
return backgroundFileName;
}
@@ -47,6 +49,14 @@ public class BackgroundBean extends BaseBean {
this.backgroundFileName = backgroundFileName == null ? "" : backgroundFileName; // 防null避免空指针
}
public String getBackgroundFilePath() {
return backgroundFilePath;
}
public void setBackgroundFilePath(String backgroundFilePath) {
this.backgroundFilePath = backgroundFilePath == null ? "" : backgroundFilePath; // 防null避免路径拼接错误
}
public String getBackgroundFileInfo() {
return backgroundFileInfo;
}
@@ -71,9 +81,6 @@ public class BackgroundBean extends BaseBean {
this.backgroundScaledCompressFileName = backgroundScaledCompressFileName == null ? "" : backgroundScaledCompressFileName; // 防null
}
/**
* 新增:压缩图完整路径 Getter/Setter适配BackgroundSettingsActivity的saveCropBitmap方法
*/
public String getBackgroundScaledCompressFilePath() {
return backgroundScaledCompressFilePath;
}
@@ -82,12 +89,16 @@ public class BackgroundBean extends BaseBean {
this.backgroundScaledCompressFilePath = backgroundScaledCompressFilePath == null ? "" : backgroundScaledCompressFilePath; // 防null避免路径错误
}
public boolean isUseScaledCompress() {
return isUseScaledCompress;
/**
* 重命名原isUseScaledCompress → 新isUseBackgroundScaledCompressFileGetter/Setter同步修改
* 语义:明确表示“是否启用背景压缩图文件”,避免与其他压缩逻辑混淆
*/
public boolean isUseBackgroundScaledCompressFile() {
return isUseBackgroundScaledCompressFile;
}
public void setIsUseScaledCompress(boolean isUseScaledCompress) {
this.isUseScaledCompress = isUseScaledCompress;
public void setIsUseBackgroundScaledCompressFile(boolean isUseBackgroundScaledCompressFile) {
this.isUseBackgroundScaledCompressFile = isUseBackgroundScaledCompressFile;
}
public int getBackgroundWidth() {
@@ -114,45 +125,54 @@ public class BackgroundBean extends BaseBean {
this.pixelColor = pixelColor;
}
// ====================================== 序列化/反序列化方法(适配JSON读写确保数据持久化完整======================================
// ====================================== 序列化/反序列化方法(适配重命名字段,兼容旧版本======================================
@Override
public String getName() {
return BackgroundBean.class.getName(); // 必须重写BaseBean序列化时需类名标识
}
/**
* 将Bean数据写入JSON序列化保存到文件
* 确保所有字段都被写入无遗漏新增backgroundScaledCompressFilePath字段
* 序列化同步重命名字段原isUseScaledCompress → 新isUseBackgroundScaledCompressFile
* 确保新字段能正常持久化同时兼容旧版本JSON可选保留旧字段写入避免旧版本读取异常
*/
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
BackgroundBean bean = this;
jsonWriter.name("backgroundFileName").value(bean.getBackgroundFileName());
jsonWriter.name("backgroundFilePath").value(bean.getBackgroundFilePath()); // 新增字段:背景原图完整路径
jsonWriter.name("backgroundFileInfo").value(bean.getBackgroundFileInfo());
jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile());
jsonWriter.name("backgroundScaledCompressFileName").value(bean.getBackgroundScaledCompressFileName());
jsonWriter.name("backgroundScaledCompressFilePath").value(bean.getBackgroundScaledCompressFilePath()); // 新增字段序列化
jsonWriter.name("isUseScaledCompress").value(bean.isUseScaledCompress());
jsonWriter.name("backgroundScaledCompressFilePath").value(bean.getBackgroundScaledCompressFilePath());
// 关键:新字段序列化(核心)
jsonWriter.name("isUseBackgroundScaledCompressFile").value(bean.isUseBackgroundScaledCompressFile());
// 兼容旧版本保留旧字段名写入可选避免旧版本Bean读取时缺失字段
jsonWriter.name("isUseScaledCompress").value(bean.isUseBackgroundScaledCompressFile());
jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth());
jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight());
jsonWriter.name("pixelColor").value(bean.getPixelColor());
}
/**
* 从JSON读取数据到Bean反序列化从文件加载
* 确保所有字段都被读取,兼容旧版本(无字段时设默认值,新增字段兼容
* 反序列化同步处理重命名字段兼容旧版本JSON新旧字段都能读取
* 逻辑:优先读取新字段,若新字段不存在则读取旧字段(确保升级后旧配置仍有效
*/
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
BackgroundBean bean = new BackgroundBean();
jsonReader.beginObject();
// 临时变量:存储旧字段值(用于兼容)
boolean tempUseScaledCompress = false;
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
switch (name) {
case "backgroundFileName":
bean.setBackgroundFileName(jsonReader.nextString());
break;
case "backgroundFilePath":
bean.setBackgroundFilePath(jsonReader.nextString()); // 新增字段:读取背景原图完整路径
break;
case "backgroundFileInfo":
bean.setBackgroundFileInfo(jsonReader.nextString());
break;
@@ -163,10 +183,15 @@ public class BackgroundBean extends BaseBean {
bean.setBackgroundScaledCompressFileName(jsonReader.nextString());
break;
case "backgroundScaledCompressFilePath":
bean.setBackgroundScaledCompressFilePath(jsonReader.nextString()); // 新增字段反序列化
bean.setBackgroundScaledCompressFilePath(jsonReader.nextString());
break;
// 关键:读取新字段(优先)
case "isUseBackgroundScaledCompressFile":
bean.setIsUseBackgroundScaledCompressFile(jsonReader.nextBoolean());
break;
// 兼容旧版本:读取旧字段(若新字段未读取,则用旧字段值)
case "isUseScaledCompress":
bean.setIsUseScaledCompress(jsonReader.nextBoolean());
tempUseScaledCompress = jsonReader.nextBoolean();
break;
case "backgroundWidth":
bean.setBackgroundWidth(jsonReader.nextInt());
@@ -178,39 +203,52 @@ public class BackgroundBean extends BaseBean {
bean.setPixelColor(jsonReader.nextInt());
break;
default:
jsonReader.skipValue(); // 跳过未知字段兼容旧版本Bean
jsonReader.skipValue(); // 跳过未知字段兼容旧版本Bean(避免崩溃)
break;
}
}
jsonReader.endObject();
// 兼容逻辑若新字段未被赋值旧版本JSON无此字段则用旧字段值填充
if (!jsonReader.toString().contains("isUseBackgroundScaledCompressFile")) {
bean.setIsUseBackgroundScaledCompressFile(tempUseScaledCompress);
}
return bean;
}
// ====================================== 辅助方法(优化BackgroundSettingsActivity调用体验======================================
// ====================================== 辅助方法(同步更新重命名字段======================================
/**
* 重置背景配置(适配“取消背景”功能,避免残留无效数据
* 重置背景配置(适配“取消背景”功能,同步重置重命名字段
*/
public void resetBackgroundConfig() {
this.backgroundFileName = "";
this.backgroundFilePath = ""; // 新增:重置背景原图完整路径
this.backgroundScaledCompressFileName = "";
this.backgroundScaledCompressFilePath = "";
this.backgroundFileInfo = "";
this.isUseBackgroundFile = false;
this.isUseScaledCompress = false;
this.isUseBackgroundScaledCompressFile = false; // 重命名字段重置为false
this.backgroundWidth = 100;
this.backgroundHeight = 100;
}
/**
* 检查背景配置是否有效适配BackgroundSettingsActivity的预览/保存校验)
* 同步使用重命名字段判断压缩图是否启用
* @return true-配置有效可显示背景图false-配置无效
*/
public boolean isBackgroundConfigValid() {
// 启用背景图时,需确保文件名或完整路径非空
// 启用背景图时,需确保:原图路径/文件名 或 压缩图路径/文件名 非空
if (!isUseBackgroundFile) {
return false;
}
return !((backgroundFileName.isEmpty() && backgroundScaledCompressFileName.isEmpty())
|| backgroundScaledCompressFilePath.isEmpty());
// 原图校验:路径非空 或 文件名非空
boolean isOriginalValid = !backgroundFilePath.isEmpty() || !backgroundFileName.isEmpty();
// 压缩图校验:启用压缩图时,路径/文件名需非空
boolean isCompressValid = true;
if (isUseBackgroundScaledCompressFile()) { // 重命名字段:判断是否启用压缩图
isCompressValid = !backgroundScaledCompressFilePath.isEmpty() || !backgroundScaledCompressFileName.isEmpty();
}
// 逻辑:启用压缩图则需压缩图有效;不启用压缩图则需原图有效
return isUseBackgroundScaledCompressFile() ? isCompressValid : isOriginalValid;
}
}

View File

@@ -8,16 +8,12 @@ import cc.winboll.studio.powerbell.model.BackgroundBean;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 12:07:20
* @Describe 背景图片工具集(调整版:图片存储→/Pictures/PowerBellJSON→应用外置存储
* @Describe 背景图片工具集(精简版复用FileUtils聚焦业务逻辑
*/
public class BackgroundSourceUtils {
@@ -26,17 +22,17 @@ public class BackgroundSourceUtils {
private static final String CROP_TEMP_DIR_NAME = "cache"; // 裁剪缓存目录(基础目录下)
private static final String CROP_TEMP_FILE_NAME = "SourceCropTemp.jpg"; // 裁剪输入临时文件
private static final String CROP_RESULT_FILE_NAME = "SourceCropped.jpg"; // 裁剪输出结果文件
private 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";
// 1. 静态实例加volatile禁止指令重排保证可见性双重校验锁单例核心
private static volatile BackgroundSourceUtils sInstance;
private Context mContext;
private File currentBackgroundBeanFile;
private BackgroundBean currentBackgroundBean;
private BackgroundBean currentBackgroundBean; // 正式Bean独立实例
private File previewBackgroundBeanFile;
private BackgroundBean previewBackgroundBean;
private BackgroundBean previewBackgroundBean; // 预览Bean独立实例与正式Bean完全分离
// 2. 统一文件目录分两类图片目录→系统公共目录JSON目录→应用外置存储
// 图片操作目录(系统公共目录:/storage/emulated/0/Pictures/PowerBell/
@@ -63,7 +59,7 @@ public class BackgroundSourceUtils {
initJsonDirs(); // 初始化JSON配置目录应用外置存储
// 初始化所有文件(裁剪临时文件/结果文件等)
initAllFiles();
// 加载配置(正式/预览BeanJSON目录下
// 加载配置(确保正式/预览Bean是两份独立实例
loadSettings();
}
@@ -80,7 +76,7 @@ public class BackgroundSourceUtils {
}
/**
* 初始化图片操作目录(核心调整:系统公共图片目录 /Pictures/PowerBell/
* 初始化图片操作目录(核心:系统公共图片目录 /Pictures/PowerBell/
*/
private void initPictureDirs() {
// 1. 图片基础目录:/storage/emulated/0/Pictures/PowerBell
@@ -90,7 +86,7 @@ public class BackgroundSourceUtils {
// 3. 裁剪缓存目录:基础目录下/cache所有裁剪操作在此目录
fPictureCacheDir = new File(fPictureBaseDir, CROP_TEMP_DIR_NAME);
// 4. 递归创建目录(系统公共目录需强制授权,确保创建成功
// 4. 递归创建目录(复用FileUtils间接创建简化逻辑
createDirWithPermission(fPictureBaseDir, "图片基础目录(/Pictures/PowerBell");
createDirWithPermission(fBackgroundSourceDir, "图片存储目录(基础目录下)");
createDirWithPermission(fPictureCacheDir, "裁剪缓存目录(基础目录/cache");
@@ -112,11 +108,11 @@ public class BackgroundSourceUtils {
fModelDir = new File(fUtilsDir, "ModelDir");
createDirWithPermission(fModelDir, "JSON配置目录应用外置存储");
// 3. 初始化JSON文件对象
// 3. 初始化JSON文件对象两份独立文件对应两份Bean实例
currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json");
previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json");
LogUtils.d(TAG, "【JSON目录初始化】完成目录=" + fModelDir.getAbsolutePath());
LogUtils.d(TAG, "【JSON目录初始化】完成目录=" + fModelDir.getAbsolutePath() + "正式JSON=" + currentBackgroundBeanFile.getName() + "预览JSON=" + previewBackgroundBeanFile.getName());
}
/**
@@ -128,7 +124,7 @@ public class BackgroundSourceUtils {
// 2. 裁剪结果文件(图片存储目录下,最终保存的裁剪图)
cropResultFile = new File(fBackgroundSourceDir, CROP_RESULT_FILE_NAME);
// 3. 初始化时清理旧文件(避免文件锁定/权限残留
// 3. 初始化时清理旧文件(复用FileUtils简化清理逻辑
clearOldFile(cropTempFile, "旧裁剪临时文件(/Pictures/PowerBell/cache");
clearOldFile(cropResultFile, "旧裁剪结果文件(/Pictures/PowerBell/BackgroundSource");
@@ -197,24 +193,25 @@ public class BackgroundSourceUtils {
}
/**
* 加载背景图片配置数据(JSON文件仍在应用外置存储不改变
* 加载背景图片配置数据(核心确保current/preview是两份独立的BackgroundBean实例
*/
void loadSettings() {
// 加载正式Bean
// 1. 加载正式Bean独立实例从currentBackgroundBean.json加载不存在则新建
currentBackgroundBean = BackgroundBean.loadBeanFromFile(currentBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
if (currentBackgroundBean == null) {
currentBackgroundBean = new BackgroundBean();
currentBackgroundBean = new BackgroundBean(); // 正式Bean独立实例初始化
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在创建默认Bean");
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在创建独立实例并保存到JSON");
}
// 加载预览Bean
// 2. 加载预览Bean独立实例从previewBackgroundBean.json加载不存在则新建与正式Bean完全分离
previewBackgroundBean = BackgroundBean.loadBeanFromFile(previewBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
if (previewBackgroundBean == null) {
previewBackgroundBean = new BackgroundBean();
previewBackgroundBean = new BackgroundBean(); // 预览Bean独立实例初始化
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在创建默认Bean");
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在创建独立实例并保存到JSON");
}
LogUtils.d(TAG, "【配置管理】两份Bean实例初始化完成正式Bean=" + currentBackgroundBean.hashCode() + "预览Bean=" + previewBackgroundBean.hashCode() + "hash不同证明实例独立");
}
// ------------------------------ 对外提供的核心方法(路径已适配新目录)------------------------------
@@ -230,9 +227,7 @@ public class BackgroundSourceUtils {
* 获取正式背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
*/
public String getCurrentBackgroundFilePath() {
// 移除loadSettings(); // 关键修复避免每次调用都重新加载Bean导致字段被重置为空
String fileName = currentBackgroundBean.getBackgroundFileName();
// 强化校验:若文件名为空,返回空路径(避免拼接目录路径)
if (TextUtils.isEmpty(fileName)) {
LogUtils.e(TAG, "【路径管理】正式背景文件名为空,返回空路径");
return "";
@@ -246,9 +241,7 @@ public class BackgroundSourceUtils {
* 获取预览背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
*/
public String getPreviewBackgroundFilePath() {
// 移除loadSettings(); // 关键修复避免每次调用都重新加载Bean导致字段被重置为空
String fileName = previewBackgroundBean.getBackgroundFileName();
// 强化校验:若文件名为空,返回空路径(避免拼接目录路径)
if (TextUtils.isEmpty(fileName)) {
LogUtils.e(TAG, "【路径管理】预览背景文件名为空,返回空路径");
return "";
@@ -262,7 +255,6 @@ public class BackgroundSourceUtils {
* 获取预览背景压缩图片路径(同步修复:移除 loadSettings(),强化非空校验)
*/
public String getPreviewBackgroundScaledCompressFilePath() {
// 移除loadSettings(); // 关键修复
String compressFileName = previewBackgroundBean.getBackgroundScaledCompressFileName();
if (TextUtils.isEmpty(compressFileName)) {
LogUtils.e(TAG, "【路径管理】预览压缩背景文件名为空,返回空路径");
@@ -274,12 +266,12 @@ public class BackgroundSourceUtils {
}
/**
* 保存配置JSON文件仍写入应用外置存储
* 保存配置(核心将两份独立Bean实例分别写入各自的JSON文件
*/
public void saveSettings() {
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "【配置管理】配置保存成功:正式Bean=" + currentBackgroundBeanFile.getAbsolutePath() + ",预览Bean=" + previewBackgroundBeanFile.getAbsolutePath());
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); // 正式Bean→正式JSON
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); // 预览Bean→预览JSON
LogUtils.d(TAG, "【配置管理】两份配置保存成功:正式JSON=" + currentBackgroundBeanFile.getAbsolutePath() + ",预览JSON=" + previewBackgroundBeanFile.getAbsolutePath());
}
/**
@@ -301,73 +293,7 @@ public class BackgroundSourceUtils {
return FILE_PROVIDER_AUTHORITY;
}
// ------------------------------ 工具方法(适配新目录权限------------------------------
/**
* 流复制文件适配系统公共目录解决Android14+ 权限问题)
*/
public boolean copyFileByStream(File source, File target) {
if (source == null || !source.exists() || !source.isFile() || target == null) {
LogUtils.e(TAG, "【文件管理】流复制失败:源文件无效或目标文件为空");
return false;
}
// 确保目标目录存在(系统公共目录需强制创建,适配/Pictures/PowerBell/路径)
File targetDir = target.getParentFile();
if (!targetDir.exists()) {
createDirWithPermission(targetDir, "流复制目标目录(/Pictures/PowerBell下");
}
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 打开源文件输入流(支持共享存储私有文件/系统公共目录文件)
fis = new FileInputStream(source);
// 打开目标文件输出流(适配/Pictures/PowerBell目录权限
fos = new FileOutputStream(target);
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区提升复制效率
int len;
// 循环读取流并写入目标文件Java7 兼容语法)
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.flush();
fos.getFD().sync(); // 强制同步到磁盘,避免系统公共目录缓存导致文件损坏
LogUtils.d(TAG, "【文件管理】流复制成功:" + source.getAbsolutePath() + "" + target.getAbsolutePath() + ",大小:" + target.length() + "bytes");
// 复制成功后强制设置目标文件权限(确保后续裁剪/预览可读写)
setFilePermissions(target);
return true;
} catch (Exception e) {
LogUtils.e(TAG, "【文件管理】流复制异常(/Pictures/PowerBell目录" + e.getMessage(), e);
// 复制失败时删除目标文件(避免残留空文件导致后续逻辑异常)
if (target.exists()) {
clearOldFileByExternal(target, "流复制失败残留文件");
}
// 针对系统公共目录权限异常,给出明确提示
if (e instanceof SecurityException || e.getMessage().contains("EACCES")) {
ToastUtils.show("图片复制失败,请授予应用「存储权限」和「所有文件访问权限」");
}
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());
}
}
}
}
// ------------------------------ 核心业务方法复用FileUtils简化文件操作------------------------------
/**
* 保存图片到预览Bean图片存储到/Pictures/PowerBell/BackgroundSourceJSON仍存应用外置存储
* @param sourceFile 源图片文件(非空,必须存在)
@@ -387,29 +313,30 @@ public class BackgroundSourceUtils {
createDirWithPermission(fBackgroundSourceDir, "图片存储目录saveFileToPreviewBean");
}
// 生成唯一文件名(基于源文件后缀,避免重复,适配系统公共目录
// 生成唯一文件名(直接复用FileUtils工具方法无需重复实现
String uniqueFileName = FileUtils.createUniqueFileName(sourceFile);
File previewBackgroundFile = new File(fBackgroundSourceDir, uniqueFileName);
// 核心:用流复制替代原FileUtils.copyFile,适配/Pictures/PowerBell目录权限
boolean copySuccess = copyFileByStream(sourceFile, previewBackgroundFile);
// 核心用FileUtils.copyFile替代自定义流复制,精简代码且保证高效
boolean copySuccess = FileUtils.copyFile(sourceFile, previewBackgroundFile);
if (!copySuccess) {
LogUtils.e(TAG, "【文件管理】图片复制到预览目录失败:" + sourceFile.getAbsolutePath() + "" + previewBackgroundFile.getAbsolutePath());
ToastUtils.show("预览图片保存失败");
return previewBackgroundBean;
}
// 正确赋值预览Bean(确保文件名非空
previewBackgroundBean = new BackgroundBean();
// 预览Bean赋值仍用独立实例不影响正式Bean
previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); // 唯一文件名(非空)
previewBackgroundBean.setBackgroundFilePath(previewBackgroundFile.getAbsolutePath()); // 新增:设置预览原图完整路径
previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_" + previewBackgroundFile.getName());
previewBackgroundBean.setBackgroundScaledCompressFilePath(fBackgroundSourceDir.getAbsolutePath() + File.separator + "ScaledCompress_" + previewBackgroundFile.getName()); // 新增:设置预览压缩图完整路径
previewBackgroundBean.setBackgroundFileInfo(fileInfo);
previewBackgroundBean.setIsUseBackgroundFile(true);
previewBackgroundBean.setIsUseScaledCompress(true);
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(true); // 重命名字段:设置启用压缩图
previewBackgroundBean.setBackgroundWidth(100);
previewBackgroundBean.setBackgroundHeight(100);
// 关键强化:强制保存Bean到JSON确保后续loadSettings()能加载到有效Bean
// 强制保存预览Bean到对应JSON不影响正式Bean的JSON
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "【文件管理】预览Bean强制保存到JSON" + previewBackgroundBeanFile.getAbsolutePath());
@@ -419,44 +346,53 @@ public class BackgroundSourceUtils {
}
/**
* 提交预览背景到正式背景(预览Bean深拷贝到正式Bean应用预览配置
* 提交预览背景到正式背景预览Bean → 正式Bean深拷贝新建正式Bean实例+逐字段拷贝
* 核心深拷贝后修改正式Bean不会影响预览Bean两份实例完全独立
*/
public void commitPreviewSourceToCurrent() {
// 深拷贝新建正式Bean复制预览Bean的所有字段避免浅拷贝导致数据污染
// 深拷贝第一步新建正式Bean独立实例彻底脱离预览Bean的引用
currentBackgroundBean = new BackgroundBean();
// 深拷贝第二步逐字段拷贝预览Bean的所有值确保字段无遗漏
currentBackgroundBean.setBackgroundFileName(previewBackgroundBean.getBackgroundFileName());
currentBackgroundBean.setBackgroundFilePath(previewBackgroundBean.getBackgroundFilePath()); // 新增字段:拷贝原图完整路径
currentBackgroundBean.setBackgroundFileInfo(previewBackgroundBean.getBackgroundFileInfo());
currentBackgroundBean.setIsUseBackgroundFile(previewBackgroundBean.isUseBackgroundFile());
currentBackgroundBean.setBackgroundScaledCompressFileName(previewBackgroundBean.getBackgroundScaledCompressFileName());
currentBackgroundBean.setIsUseScaledCompress(previewBackgroundBean.isUseScaledCompress());
currentBackgroundBean.setBackgroundScaledCompressFilePath(previewBackgroundBean.getBackgroundScaledCompressFilePath()); // 新增字段:拷贝压缩图完整路径
currentBackgroundBean.setIsUseBackgroundScaledCompressFile(previewBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
currentBackgroundBean.setBackgroundWidth(previewBackgroundBean.getBackgroundWidth());
currentBackgroundBean.setBackgroundHeight(previewBackgroundBean.getBackgroundHeight());
currentBackgroundBean.setPixelColor(previewBackgroundBean.getPixelColor());
saveSettings(); // 持久化保存正式BeanJSON写入应用外置存储
LogUtils.d(TAG, "【配置管理】预览背景提交成功:正式背景更新为/Pictures/PowerBell下的预览背景");
saveSettings(); // 分别保存正式Bean→currentJSON预览Bean→previewJSON两份独立
LogUtils.d(TAG, "【配置管理】预览背景深拷贝到正式Bean两份实例独立JSON分别存储");
ToastUtils.show("背景图片应用成功");
}
/**
* 将正式背景同步到预览背景(深拷贝,初始化预览为当前正式背景
* 将正式背景同步到预览背景(正式Bean → 预览Bean深拷贝新建预览Bean实例+逐字段拷贝
* 核心深拷贝后修改预览Bean不会影响正式Bean两份实例完全独立
*/
public void setCurrentSourceToPreview() {
// 深拷贝新建预览Bean复制正式Bean的所有字段避免浅拷贝导致数据污染
// 深拷贝第一步新建预览Bean独立实例彻底脱离正式Bean的引用
previewBackgroundBean = new BackgroundBean();
// 深拷贝第二步逐字段拷贝正式Bean的所有值确保字段无遗漏
previewBackgroundBean.setBackgroundFileName(currentBackgroundBean.getBackgroundFileName());
previewBackgroundBean.setBackgroundFilePath(currentBackgroundBean.getBackgroundFilePath()); // 新增字段:拷贝原图完整路径
previewBackgroundBean.setBackgroundFileInfo(currentBackgroundBean.getBackgroundFileInfo());
previewBackgroundBean.setIsUseBackgroundFile(currentBackgroundBean.isUseBackgroundFile());
previewBackgroundBean.setBackgroundScaledCompressFileName(currentBackgroundBean.getBackgroundScaledCompressFileName());
previewBackgroundBean.setIsUseScaledCompress(currentBackgroundBean.isUseScaledCompress());
previewBackgroundBean.setBackgroundScaledCompressFilePath(currentBackgroundBean.getBackgroundScaledCompressFilePath()); // 新增字段:拷贝压缩图完整路径
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(currentBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
previewBackgroundBean.setBackgroundWidth(currentBackgroundBean.getBackgroundWidth());
previewBackgroundBean.setBackgroundHeight(currentBackgroundBean.getBackgroundHeight());
previewBackgroundBean.setPixelColor(currentBackgroundBean.getPixelColor());
saveSettings(); // 持久化保存预览BeanJSON写入应用外置存储
LogUtils.d(TAG, "【配置管理】正式背景同步到预览:预览背景更新为/Pictures/PowerBell下的正式背景");
saveSettings(); // 分别保存正式Bean→currentJSON预览Bean→previewJSON两份独立
LogUtils.d(TAG, "【配置管理】正式背景深拷贝到预览Bean两份实例独立JSON分别存储");
}
// ------------------------------ 必需保留的工具方法(与业务/权限强相关无法复用FileUtils------------------------------
/**
* 工具方法:创建目录并设置权限(适配系统公共目录/Pictures/PowerBell确保可读写
* @param dir 要创建的目录
@@ -601,64 +537,6 @@ public class BackgroundSourceUtils {
}
}
/**
* 工具方法复制文件适配大文件避免OOM兼容源文件为空场景
* 【关键优化】适配Activity中mBgSourceUtils.copyFile(new File(""), parentDir)调用
* @param source 源文件(可为空,为空时仅创建目标目录)
* @param target 目标文件/目录若源文件为空target视为目录并创建
* @return true=复制/创建成功false=失败
*/
public boolean copyFile(File source, File target) {
// 场景1源文件为空 → 仅创建目标目录适配Activity中mBgSourceUtils.copyFile(new File(""), parentDir)调用)
if (source == null || (source.exists() && source.length() <= 0)) {
if (target == null) {
LogUtils.e(TAG, "【文件管理】目录创建失败目标目录对象为null");
return false;
}
// 若target是文件取其父目录若本身是目录直接创建适配/Pictures/PowerBell目录
File targetDir = target.isFile() ? target.getParentFile() : target;
createDirWithPermission(targetDir, "空源文件场景-目录创建(/Pictures/PowerBell下");
LogUtils.d(TAG, "【文件管理】空源文件场景:目录创建完成,路径=" + targetDir.getAbsolutePath());
return true;
}
// 场景2正常文件复制源文件非空且存在适配系统公共目录/Pictures/PowerBell
if (!source.exists() || target == null) {
LogUtils.e(TAG, "【文件管理】文件复制失败:源文件无效或目标文件为空");
return false;
}
// 确保目标目录存在(系统公共目录需强制创建,避免权限问题)
File targetDir = target.getParentFile();
if (!targetDir.exists()) {
createDirWithPermission(targetDir, "文件复制目标目录(/Pictures/PowerBell下");
}
// 调用流复制方法适配系统公共目录权限避免Permission denied
return copyFileByStream(source, target);
}
/**
* 工具方法清理裁剪相关临时文件对外提供Activity退出时调用适配/Pictures/PowerBell/cache目录
*/
public void clearCropTempFiles() {
clearOldFile(cropTempFile, "裁剪临时文件(/Pictures/PowerBell/cache");
clearOldFile(cropResultFile, "裁剪结果文件(/Pictures/PowerBell/BackgroundSource");
// 清理裁剪缓存目录下的其他临时文件Java7 普通for循环兼容语法
if (fPictureCacheDir.exists()) {
File[] files = fPictureCacheDir.listFiles();
if (files != null && files.length > 0) {
for (int i = 0; i < files.length; i++) {
File file = files[i];
if (file.isFile()) {
// 强制设置为可写后删除(避免系统公共目录文件只读)
file.setWritable(true, false);
file.delete();
}
}
}
}
LogUtils.d(TAG, "【文件管理】裁剪相关临时文件清理完成(/Pictures/PowerBell下");
}
/**
* 对外接口清理指定旧文件适配BackgroundSettingsActivity调用支持/Pictures/PowerBell目录
* @param file 要清理的文件
@@ -668,6 +546,56 @@ public class BackgroundSourceUtils {
clearOldFile(file, fileDesc); // 调用内部private方法复用逻辑
}
/**
* 清理裁剪相关临时文件对外提供Activity退出时调用适配/Pictures/PowerBell/cache目录
* 【恢复函数】:兼容原代码中 mBgSourceUtils.clearCropTempFiles() 调用
*/
public void clearCropTempFiles() {
// 清理核心裁剪文件复用内部clearOldFile方法逻辑统一
clearOldFile(cropTempFile, "裁剪临时文件(/Pictures/PowerBell/cache");
//clearOldFile(cropResultFile, "裁剪结果文件(/Pictures/PowerBell/BackgroundSource");
// 清理裁剪缓存目录下的所有临时文件(避免残留)
if (fPictureCacheDir.exists()) {
File[] files = fPictureCacheDir.listFiles();
if (files != null && files.length > 0) {
for (int i = 0; i < files.length; i++) {
File file = files[i];
if (file.isFile()) {
// 仅清理文件(不删除目录),确保后续裁剪可正常创建文件
clearOldFile(file, "裁剪缓存目录残留文件");
}
}
}
}
LogUtils.d(TAG, "【文件管理】裁剪相关临时文件清理完成(/Pictures/PowerBell下");
}
/**
* 适配原调用mBgSourceUtils.copyFile(new File(""), parentDir)
* 核心复用FileUtils支持「空源文件→仅创建目标目录」和「正常文件复制」两种场景
* @param source 源文件(可为空/空文件,为空时仅创建目标目录)
* @param target 目标文件/目录若源文件为空target视为目录并创建
* @return true=复制/创建成功false=失败
*/
public boolean copyFile(File source, File target) {
// 场景1源文件为空适配 new File("") 调用)→ 仅创建目标目录
if (source == null || (source.exists() && source.length() <= 0) || TextUtils.isEmpty(source.getPath())) {
if (target == null) {
LogUtils.e(TAG, "【文件管理】目录创建失败目标目录对象为null");
return false;
}
// 若target是文件取其父目录若本身是目录直接创建适配/Pictures/PowerBell目录
File targetDir = target.isFile() ? target.getParentFile() : target;
createDirWithPermission(targetDir, "空源文件场景-目录创建(/Pictures/PowerBell下");
LogUtils.d(TAG, "【文件管理】空源文件场景:目录创建完成,路径=" + targetDir.getAbsolutePath());
return true;
}
// 场景2正常文件复制源文件非空且存在→ 复用FileUtils.copyFile确保高效兼容
return FileUtils.copyFile(source, target);
}
/**
* 工具方法:获取目录类型描述(用于日志调试,明确目录类型,适配新目录结构)
* @param dir 目标目录

View File

@@ -127,7 +127,7 @@ public class BackgroundView extends RelativeLayout {
}
// 优先加载压缩图,无则加载原图(保持原逻辑)
String backgroundPath;
if (previewBean.isUseScaledCompress()) {
if (previewBean.isUseBackgroundScaledCompressFile()) {
backgroundPath = backgroundSourceUtils.getPreviewBackgroundScaledCompressFilePath();
} else {
backgroundPath = backgroundSourceUtils.getPreviewBackgroundFilePath();
@@ -243,7 +243,7 @@ public class BackgroundView extends RelativeLayout {
adjustImageViewSize();
}
});
LogUtils.d(TAG, "父容器未测量完成延迟调整ImageView尺寸");
//LogUtils.d(TAG, "父容器未测量完成延迟调整ImageView尺寸");
return;
}