diff --git a/powerbell/build.properties b/powerbell/build.properties index b34da277..dc4b9f6b 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Dec 03 08:28:19 GMT 2025 +#Wed Dec 03 09:19:24 GMT 2025 stageCount=13 libraryProject= baseVersion=15.11 publishVersion=15.11.12 -buildCount=121 +buildCount=122 baseBetaVersion=15.11.13 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java index 7d4879ae..b40bc141 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java @@ -197,18 +197,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【分享接收】onAcceptRecivedPicture 触发,图片名:" + szPreRecivedPictureName); } - /** - * 更新背景预览(强制使用预览Bean,全程不操作正式Bean) - */ -// public void updateBackgroundView(File sourceFile, String sourceFileInfo) { -// LogUtils.d(TAG, "【预览更新】updateBackgroundView 触发,sourceFile是否为空:" + (sourceFile == null)); -// if (sourceFile != null) { -// mBgSourceUtils.saveFileToPreviewBean(sourceFile, sourceFileInfo); // 同步到预览Bean -// } -// mBackgroundView.loadBackground2(mBgSourceUtils.getPreviewBackgroundBean()); -// LogUtils.d(TAG, "【预览更新】预览背景更新完成(强制使用previewBackgroundBean)"); -// } - // ======================================== 按钮点击事件(仅UI交互) ======================================== /** * 点击事件:取消背景(仅操作Bean,无文件逻辑) @@ -216,17 +204,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg private View.OnClickListener onOriginNullClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发取消背景功能"); + LogUtils.d(TAG, "【按钮点击】触发取消背景图片功能"); // 1. 修改正式Bean - BackgroundBean currentBackgroundBean = mBgSourceUtils.getCurrentBackgroundBean(); - currentBackgroundBean.setIsUseBackgroundFile(false); - currentBackgroundBean.resetBackgroundConfig(); - mBgSourceUtils.saveSettings(); - // 2. 同步预览Bean(关键优化:确保控件刷新后显示“取消背景”状态) - mBgSourceUtils.setCurrentSourceToPreview(); - // 3. 控件刷新(仍依赖预览Bean) - doubleRefreshPreview(); - ToastUtils.show("背景已取消"); + BackgroundBean previewBackgroundBean = mBgSourceUtils.getPreviewBackgroundBean(); + previewBackgroundBean.setIsUseBackgroundFile(false); + doubleRefreshPreview(); } }; @@ -317,29 +299,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg @Override public void onClick(View v) { LogUtils.d(TAG, "【按钮点击】触发固定比例裁剪功能"); - File targetFile = new File(mBgSourceUtils.getCurrentBackgroundFilePath()); - if (targetFile.exists()) { - // 适配MIUI:弹出裁剪提示(建议选择系统相机) - if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) { - new AlertDialog.Builder(BackgroundSettingsActivity.this) - .setTitle("裁剪提示") - .setMessage("若裁剪失败,请选择「系统相机」作为裁剪工具") - .setPositiveButton("确定", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startCropImageActivity(false); // 启动固定比例裁剪 - } - }) - .setNegativeButton("取消", null) - .show(); - } else { - startCropImageActivity(false); // 非MIUI机型直接启动裁剪 - } - LogUtils.d(TAG, "【裁剪启动】固定比例裁剪已启动"); - } else { - ToastUtils.show("无可用裁剪图片,请先选择/拍照"); - LogUtils.e(TAG, "【裁剪失败】无可用裁剪图片"); - } + startCropImageActivity(false); } }; @@ -350,29 +310,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg @Override public void onClick(View v) { LogUtils.d(TAG, "【按钮点击】触发自由裁剪功能"); - File targetFile = new File(mBgSourceUtils.getCurrentBackgroundFilePath()); - if (targetFile.exists()) { - // 适配MIUI:弹出裁剪提示(建议选择系统相机) - if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) { - new AlertDialog.Builder(BackgroundSettingsActivity.this) - .setTitle("裁剪提示") - .setMessage("若裁剪失败,请选择「系统相机」作为裁剪工具") - .setPositiveButton("确定", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startCropImageActivity(true); // 启动自由裁剪 - } - }) - .setNegativeButton("取消", null) - .show(); - } else { - startCropImageActivity(true); // 非MIUI机型直接启动裁剪 - } - LogUtils.d(TAG, "【裁剪启动】自由裁剪已启动"); - } else { - ToastUtils.show("无可用裁剪图片,请先选择/拍照"); - LogUtils.e(TAG, "【裁剪失败】无可用裁剪图片"); - } + startCropImageActivity(true); } }; @@ -483,7 +421,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); previewBean.setIsUseBackgroundScaledCompressFile(false); mBgSourceUtils.saveSettings(); - doubleRefreshPreview(); File previewFile = new File(previewBean.getBackgroundFilePath()); // 裁剪缓存图片 @@ -625,123 +562,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return gcdResult; } - /** - * 保存裁剪后的图片(修复:路径同步时序+压缩图路径强绑定) - * 作用:裁剪后保存原图→生成压缩图→同步双路径到预览Bean→清理临时文件 - */ -// private void saveCropBitmap(Bitmap bitmap) { -// LogUtils.d(TAG, "【保存启动】开始保存裁剪图片(仅更新预览Bean,不影响正式Bean)"); -// if (bitmap == null || bitmap.isRecycled()) { -// ToastUtils.show("裁剪图片为空"); -// //mBgSourceUtils.clearCropTempFiles(); -// return; -// } -// -// // 内存优化:大图片自动缩放(保留原有逻辑,避免OOM) -// Bitmap scaledBitmap = bitmap; -// int originalSize = bitmap.getByteCount() / 1024 / 1024; -// if (originalSize > 5) { -// float scale = 1.0f; -// while (scaledBitmap.getByteCount() / 1024 / 1024 > 2) { -// scale -= 0.2f; -// if (scale < 0.2f) break; -// scaledBitmap = scaleBitmap(scaledBitmap, scale); // 复用原有缩放方法 -// } -// LogUtils.d(TAG, "【内存优化】大图片自动缩放:原始大小=" + originalSize + "MB,缩放后大小=" + scaledBitmap.getByteCount() / 1024 / 1024 + "MB"); -// } -// -// File cropSaveFile = new File(mBgSourceUtils.getPreviewBackgroundFilePath()); -// FileOutputStream fos = null; -// BufferedOutputStream bos = null; -// try { -// // 1. 清理旧的裁剪预览图(避免文件残留) -//// if (cropSaveFile.exists()) { -//// mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "旧裁剪预览图"); -//// } -// // 确保父目录存在(兼容Android 10+分区存储,多包名环境下目录适配) -// File parentDir = cropSaveFile.getParentFile(); -// if (parentDir != null && !parentDir.exists()) { -// parentDir.mkdirs(); -// mBgSourceUtils.copyFile(new File(""), parentDir); // 复用原有目录创建逻辑 -// } -// if (parentDir == null) { -// LogUtils.e(TAG, "【裁剪保存失败】目标文件父目录为空"); -// ToastUtils.show("裁剪图片保存失败"); -// return; -// } -// cropSaveFile.createNewFile(); -// -// // 2. 写入裁剪原图到指定路径(覆盖旧文件) -// fos = new FileOutputStream(cropSaveFile); -// bos = new BufferedOutputStream(fos); -// scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 85, bos); -// bos.flush(); -// // 强制同步到磁盘(避免Android 10+异步写入导致文件读取不到) -// if (fos != null) { -// try { -// fos.getFD().sync(); -// } catch (IOException e) { -// LogUtils.w(TAG, "【裁剪保存】sync()调用失败,用flush()兜底:" + e.getMessage()); -// bos.flush(); -// } -// } -// LogUtils.d(TAG, "【裁剪保存】预览原图保存成功:" + cropSaveFile.getAbsolutePath() + ",大小=" + cropSaveFile.length() + "bytes"); -// -// // 3. 生成新压缩图(关键:先获取/生成压缩路径,再压缩) -// String newCompressPath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath(); -// // 兜底:若压缩路径为空,生成新的唯一路径(避免空指针,兼容多包名路径隔离) -// if (TextUtils.isEmpty(newCompressPath)) { -// String sourceDir = mBgSourceUtils.getBackgroundSourceDirPath(); -// newCompressPath = new File(sourceDir, "ScaledCompress_" + System.currentTimeMillis() + ".jpg").getAbsolutePath(); -// } -// // 调用重载方法压缩,传入指定压缩路径 -// mBgSourceUtils.compressQualityToRecivedPicture(scaledBitmap, newCompressPath); -// -// // 4. 同步更新预览Bean(核心修复:同时绑定原图路径+压缩图路径) -// mBgSourceUtils.saveFileToPreviewBean(cropSaveFile, "裁剪后图片"); // 原有方法:更新原图路径 -// BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); -// if (previewBean != null) { -// previewBean.setBackgroundScaledCompressFilePath(newCompressPath); // 新增:绑定压缩图路径 -// mBgSourceUtils.saveSettings(); // 持久化Bean配置,避免路径丢失(多包名环境下配置隔离) -// LogUtils.d(TAG, "【路径同步】预览Bean双路径同步完成:"); -// LogUtils.d(TAG, "→ 原图路径:" + previewBean.getBackgroundFilePath()); -// LogUtils.d(TAG, "→ 压缩图路径:" + previewBean.getBackgroundScaledCompressFilePath()); -// } else { -// LogUtils.e(TAG, "【路径同步失败】预览Bean为空"); -// } -// -// ToastUtils.show("裁剪图片保存成功"); -// } catch (IOException e) { -// LogUtils.e(TAG, "【裁剪保存失败】IO异常:" + e.getMessage(), e); -// ToastUtils.show("裁剪图片保存失败"); -// } finally { -// // 资源回收(避免内存泄漏,兼容低版本Android) -// if (bos != null) { -// try { -// bos.close(); -// } catch (IOException e) { -// LogUtils.e(TAG, "【流关闭失败】BufferedOutputStream:" + e.getMessage()); -// } -// } -// if (fos != null) { -// try { -// fos.close(); -// } catch (IOException e) { -// LogUtils.e(TAG, "【流关闭失败】FileOutputStream:" + e.getMessage()); -// } -// } -// // 回收缩放后的Bitmap(避免重复回收) -// if (scaledBitmap != bitmap && scaledBitmap != null && !scaledBitmap.isRecycled()) { -// scaledBitmap.recycle(); -// } -// if (bitmap != null && !bitmap.isRecycled()) { -// bitmap.recycle(); -// } -// } -// -// LogUtils.d(TAG, "【裁剪保存】流程结束。"); -// } - /** * 图片缩放(核心用途:大图片自动缩小,降低内存占用,避免OOM崩溃) * 逻辑:按指定缩放比例(0~1之间)缩小Bitmap,保持宽高比不变,生成新的Bitmap并回收旧图 @@ -916,7 +736,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【拍照Bitmap解析】开始解析拍照结果"); // 方案1:优先从拍照临时文件解析(推荐,获取清晰原图,避免缩略图模糊) - if (mfTakePhoto != null && isFileActuallyReadable(mfTakePhoto)) { + if (mfTakePhoto != null && mfTakePhoto.exists()) { LogUtils.d(TAG, "【拍照Bitmap解析】优先从临时文件解析:" + mfTakePhoto.getAbsolutePath()); // 复用已定义的parseCropTempFileToBitmap函数(采样率加载,防OOM,无需重复写逻辑) Bitmap photoBitmap = parseCropTempFileToBitmap(mfTakePhoto); @@ -975,48 +795,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 其他失败场景 handleOperationCancelOrFail(); } - // 打印校验日志 -// LogUtils.d(TAG, "【裁剪回调】校验:resultCode=" + resultCode + ",文件存在=" + isFileExist + ",大小=" + fileSize + "bytes,是否成功=" + isCropSuccess); -// -// // 处理MIUI裁剪取消(resultCode=0视为取消) -// if (resultCode == 0 && !isCropSuccess) { -// LogUtils.d(TAG, "【裁剪回调】MIUI 裁剪工具已取消"); -// ToastUtils.show("裁剪已取消"); -// //mBgSourceUtils.clearCropTempFiles(); -// return; -// } -// -// // 处理裁剪文件为空(真正的裁剪失败) -// if (isFileExist && fileSize == 0) { -// LogUtils.e(TAG, "【裁剪失败】裁剪文件为空(MIUI适配问题)"); -// ToastUtils.show("裁剪失败,请尝试选择「系统相机」裁剪或更换图片"); -// //mBgSourceUtils.clearCropTempFiles(); -// return; -// } -// -// // 裁剪成功:解析Bitmap并保存 -// if (isCropSuccess) { -// BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); -// previewBean.setIsUseBackgroundScaledCompressFile(isCropSuccess); -// mBgSourceUtils.saveSettings(); -// -// doubleRefreshPreview(); -// -// Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile); -// if (cropBitmap != null && !cropBitmap.isRecycled()) { -// saveCropBitmap(cropBitmap); // 保存裁剪结果 -// // 更新预览数据集设置 -// BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); -// previewBean.setIsUseBackgroundScaledCompressFile(isCropSuccess); -// mBgSourceUtils.saveSettings(); -// -// doubleRefreshPreview(); // 双重刷新预览(适配MIUI渲染延迟) -// LogUtils.d(TAG, "【裁剪完成】裁剪回调处理结束"); -// } else { -// ToastUtils.show("获取裁剪图片失败"); -// LogUtils.e(TAG, "【裁剪回调失败】Bitmap解析异常"); -// //mBgSourceUtils.clearCropTempFiles(); -// } } /** @@ -1158,52 +936,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "→ 压缩图路径 :" + targetCompressPath); startCropImageActivity(false); - - // 关键修复3:生成压缩图(指定BackgroundCrops目录下的路径,确保与预览Bean路径一致) - //LogUtils.d(TAG, "【选图压缩】开始生成预览压缩图,目标路径:" + targetCompressPath); -// Bitmap selectBitmap = null; -// try { -// // 优化:使用采样率加载Bitmap,避免OOM(原有直接decodeFile易导致大图片崩溃) -// BitmapFactory.Options options = new BitmapFactory.Options(); -// options.inJustDecodeBounds = true; -// BitmapFactory.decodeFile(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath(), options); -// // 计算采样率(最大尺寸限制为2048px) -// int maxSize = 2048; -// int sampleSize = 1; -// while (options.outWidth / sampleSize > maxSize || options.outHeight / sampleSize > maxSize) { -// sampleSize *= 2; -// } -// options.inJustDecodeBounds = false; -// options.inSampleSize = sampleSize; -// options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存 -// selectBitmap = BitmapFactory.decodeFile(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath(), options); -// LogUtils.d(TAG, ""); -// } catch (Exception e) { -// LogUtils.e(TAG, "【选图压缩】Bitmap加载失败:" + e.getMessage(), e); -// } - - // 关键修复4:校验压缩图是否生成成功(避免压缩失败但仍启动裁剪) -// File compressFile = new File(targetCompressPath); -// if (!compressFile.exists() || compressFile.length() <= 0) { -// ToastUtils.show("压缩图生成失败,请重新选择图片"); -// LogUtils.e(TAG, "【选图压缩失败】压缩图文件无效:" + targetCompressPath); -// return; -// } - - // 刷新预览并启动裁剪(无需重复同步预览Bean,已提前绑定) - /*doubleRefreshPreview(); - - // 新增:延迟50ms启动裁剪(适配部分机型选图后文件写入延迟) - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - if (!isFinishing()) { - startCropImageActivity(false); - LogUtils.d(TAG, "【选图完成】选图回调处理结束,已启动裁剪(正式图+压缩图均已生成)"); - } - } - }, 50); - */ } /** @@ -1211,8 +943,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg */ private void handleOperationCancelOrFail() { initBackgroundViewByPreviewBean(); - doubleRefreshPreview(); - LogUtils.d(TAG, "【操作回调】操作取消或失败"); ToastUtils.show("操作已取消"); } @@ -1239,66 +969,4 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【选图权限】已添加持久化读取权限"); } } - - /** - * 精准校验文件是否实际可读取(核心用途:解决Android14+ 中 File.canRead() 假阳性问题,避免“文件存在但无法读取”) - * 逻辑:不依赖canRead(),而是通过实际打开文件流读取1字节,验证是否真的有读取权限(最可靠的校验方式) - * @param file 待校验的文件(如选图临时文件、裁剪文件) - * @return true=文件实际可读取,false=文件不可读(不存在/无权限/不是文件) - */ - private boolean isFileActuallyReadable(File file) { - // 第一重:基础校验(快速过滤明显无效的文件) - if (file == null) { - LogUtils.w(TAG, "【文件可读性校验】文件对象为空,直接返回false"); - return false; - } - if (!file.exists()) { - LogUtils.w(TAG, "【文件可读性校验】文件不存在,路径:" + file.getAbsolutePath()); - return false; - } - if (!file.isFile()) { - LogUtils.w(TAG, "【文件可读性校验】路径不是合法文件(可能是目录),路径:" + file.getAbsolutePath()); - return false; - } - if (file.length() <= 0) { - LogUtils.w(TAG, "【文件可读性校验】文件为空(大小=0字节),路径:" + file.getAbsolutePath()); - return false; - } - - // 第二重:实际流读取校验(核心,规避canRead()假阳性)- Java7 手动流操作 - FileInputStream fis = null; - try { - // 尝试打开文件输入流(若无权限,会直接抛出异常) - fis = new FileInputStream(file); - // 读取1字节(无需读取完整文件,仅验证“能否读取”) - fis.read(new byte[1]); // 读取1字节,验证流是否可用 - LogUtils.d(TAG, "【文件可读性校验】文件实际可读取,路径:" + file.getAbsolutePath()); - return true; - - } catch (SecurityException e) { - // 捕获权限异常(最常见:有文件但无读取权限) - LogUtils.e(TAG, "【文件可读性校验】无读取权限,路径:" + file.getAbsolutePath() + ",异常:" + e.getMessage()); - return false; - - } catch (IOException e) { - // 捕获IO异常(文件破损/被占用/无法打开) - LogUtils.e(TAG, "【文件可读性校验】文件无法读取(可能破损/被占用),路径:" + file.getAbsolutePath() + ",异常:" + e.getMessage()); - return false; - - } catch (Exception e) { - // 捕获其他异常(如文件路径非法等) - LogUtils.e(TAG, "【文件可读性校验】未知异常,路径:" + file.getAbsolutePath() + ",异常:" + e.getMessage()); - return false; - - } finally { - // 手动关闭流(Java7 标准写法,避免内存泄漏) - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { - LogUtils.e(TAG, "【文件可读性校验】输入流关闭失败,路径:" + file.getAbsolutePath() + ",异常:" + e.getMessage()); - } - } - } - } }