20251201_151821_718
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,无文件逻辑)
|
||||
*/
|
||||
|
||||
@@ -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 → 新isUseBackgroundScaledCompressFile(Getter/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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/PowerBell,JSON→应用外置存储)
|
||||
* @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();
|
||||
// 加载配置(正式/预览Bean,JSON目录下)
|
||||
// 加载配置(确保正式/预览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/BackgroundSource,JSON仍存应用外置存储)
|
||||
* @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(); // 持久化保存正式Bean(JSON写入应用外置存储)
|
||||
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(); // 持久化保存预览Bean(JSON写入应用外置存储)
|
||||
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 目标目录
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user