From fd556fd06fba5a6666b504c16889dc1e68268633 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 11 Dec 2025 13:04:44 +0800 Subject: [PATCH] 20251211_130438_907 --- powerbell/build.properties | 4 +- .../src/main/assets/images/blank10x10.png | Bin 0 -> 100 bytes .../BackgroundSettingsActivity.java | 349 +++++++++--------- .../powerbell/utils/AssetsCopyUtils.java | 122 ++++++ .../utils/BackgroundSourceUtils.java | 141 ++++--- .../powerbell/utils/BitmapCacheUtils.java | 2 +- 6 files changed, 376 insertions(+), 242 deletions(-) create mode 100644 powerbell/src/main/assets/images/blank10x10.png create mode 100644 powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AssetsCopyUtils.java diff --git a/powerbell/build.properties b/powerbell/build.properties index 39e0ed4..fd9052c 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Dec 11 00:57:37 GMT 2025 +#Thu Dec 11 04:59:14 GMT 2025 stageCount=15 libraryProject= baseVersion=15.12 publishVersion=15.12.14 -buildCount=13 +buildCount=21 baseBetaVersion=15.12.15 diff --git a/powerbell/src/main/assets/images/blank10x10.png b/powerbell/src/main/assets/images/blank10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..a1916ccf60a8812927219ecda72fbdc8af894a8e GIT binary patch literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>VJUX<4B-HR r8jh3>AfM0E#W6%;YI1@EYqKy=?JNdHu~U~bfieu9u6{1-oD!M= SDK_VERSION_TIRAMISU) { - if (mPermissionUtils.checkAndRequestMediaImagesPermission(BackgroundSettingsActivity.this, REQUEST_READ_MEDIA)) { - launchImageSelector(); - } - } else { - if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) { - launchImageSelector(); - } - } - } - }; + @Override + public void onClick(View v) { + LogUtils.d(TAG, "【按钮点击】选择图片"); + // 适配Android 13+权限(已替换为手动定义的常量) + if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) { + if (mPermissionUtils.checkAndRequestMediaImagesPermission(BackgroundSettingsActivity.this, REQUEST_READ_MEDIA)) { + launchImageSelector(); + } + } else { + if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) { + launchImageSelector(); + } + } + } + }; private void launchImageSelector() { LogUtils.d(TAG, "【选图权限】已获取,启动选择器"); @@ -437,7 +435,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } Bitmap photoBitmap = getTakePhotoBitmap(data); - if (photoBitmap != null && !photoBitmap.isRecycled()) { + if (isBitmapValid(photoBitmap)) { mBgSourceUtils.compressQualityToRecivedPicture(photoBitmap); } else { ToastUtils.show("拍照图片为空"); @@ -462,7 +460,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (mfTakePhoto != null && mfTakePhoto.exists()) { LogUtils.d(TAG, "【拍照Bitmap解析】从文件解析"); Bitmap photoBitmap = parseCropTempFileToBitmap(mfTakePhoto); - if (photoBitmap != null && !photoBitmap.isRecycled()) { + if (isBitmapValid(photoBitmap)) { LogUtils.d(TAG, "【拍照Bitmap解析】成功"); return photoBitmap; } else { @@ -475,7 +473,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (data != null) { try { Bitmap thumbnailBitmap = (Bitmap) data.getParcelableExtra("data"); - if (thumbnailBitmap != null && !thumbnailBitmap.isRecycled()) { + if (isBitmapValid(thumbnailBitmap)) { LogUtils.d(TAG, "【拍照Bitmap解析】从Intent获取成功"); return thumbnailBitmap; } else { @@ -494,76 +492,57 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } private void handleSelectPictureResult(int resultCode, Intent data) { - if (resultCode != RESULT_OK || data == null) { - handleOperationCancelOrFail(); - return; - } + if (resultCode != RESULT_OK || data == null) { + handleOperationCancelOrFail(); + return; + } - Uri selectedImage = data.getData(); - if (selectedImage == null) { - ToastUtils.show("图片Uri为空"); - return; - } - LogUtils.d(TAG, "【选图回调】系统返回Uri : " + selectedImage.toString()); + Uri selectedImage = data.getData(); + if (selectedImage == null) { + ToastUtils.show("图片Uri为空"); + return; + } + LogUtils.d(TAG, "【选图回调】系统返回Uri : " + selectedImage.toString()); - // 1. 申请持久化读取权限 - if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) { - getContentResolver().takePersistableUriPermission( - selectedImage, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ); - LogUtils.d(TAG, "【选图权限】已添加持久化权限"); - } + // 1. 申请持久化读取权限 + if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) { + getContentResolver().takePersistableUriPermission( + selectedImage, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ); + LogUtils.d(TAG, "【选图权限】已添加持久化权限"); + } - // 2. 强制拷贝到应用私有目录(彻底规避系统虚拟路径问题) -// String privateImagePath = copyUriToPrivateDir(selectedImage); -// if (TextUtils.isEmpty(privateImagePath)) { -// ToastUtils.show("系统图片路径无效,拷贝失败"); -// LogUtils.e(TAG, "【选图校验】系统Uri拷贝失败,Uri=" + selectedImage.toString()); -// return; -// } -// -// // 3. 严格校验私有目录文件有效性 -// File srcFile = new File(privateImagePath); -// if (srcFile == null -// || !srcFile.exists() -// || !srcFile.isFile() -// || srcFile.length() <= 0) { -// ToastUtils.show("系统图片文件无效"); -// LogUtils.e(TAG, "【选图校验】私有目录文件无效,路径=" + privateImagePath); -// // 清理无效文件 -// if (srcFile != null && srcFile.exists()) { -// srcFile.delete(); -// } -// return; -// } - - //LogUtils.d(TAG, "【选图校验】系统图片校验通过,私有路径:" + privateImagePath); - - // 4. 同步到预览 Bean 并启动裁剪 - if (putUriFileToPreviewSource(selectedImage)) { - LogUtils.d(TAG, "【选图同步】路径绑定完成"); - mBitmapCache.removeCachedBitmap(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); - ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, + // 2. 同步到预览 Bean 并启动裁剪 + if (putUriFileToPreviewSource(selectedImage)) { + LogUtils.d(TAG, "【选图同步】路径绑定完成"); + mBitmapCache.removeCachedBitmap(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); + ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, mBgSourceUtils.getPreviewBackgroundBean(), mBackgroundView.getWidth(), mBackgroundView.getHeight(), false, REQUEST_CROP_IMAGE ); - } else { - ToastUtils.show("图片同步失败"); - LogUtils.e(TAG, "【选图同步】文件复制失败"); - } - } + } else { + ToastUtils.show("图片同步失败"); + LogUtils.e(TAG, "【选图同步】文件复制失败"); + } + } boolean putUriFileToPreviewSource(Uri srcUriFile) { - File srcFile = new File(UriUtil.getFilePathFromUri(this, srcUriFile)); + String filePath = UriUtil.getFilePathFromUri(this, srcUriFile); + // 关键修复:校验路径是否为空 + if (TextUtils.isEmpty(filePath)) { + LogUtils.e(TAG, "putUriFileToPreviewSource: Uri解析路径为空"); + return false; + } + File srcFile = new File(filePath); return putUriFileToPreviewSource(srcFile); } boolean putUriFileToPreviewSource(File srcFile) { - LogUtils.d(TAG, String.format("putUriFileToPreviewSource(File srcFile) srcFile %s", srcFile)); + LogUtils.d(TAG, String.format("putUriFileToPreviewSource(File srcFile) srcFile %s", srcFile)); mBgSourceUtils.loadSettings(); File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); return FileUtils.copyFile(srcFile, dstFile); @@ -588,16 +567,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg float systemFileRatio = getRatioFromSystemCropFile(cropTempFile); if (systemFileRatio > 0) { Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile); - if (cropBitmap != null && !cropBitmap.isRecycled()) { + // 校验解析后的Bitmap是否有效 + if (isBitmapValid(cropBitmap)) { Bitmap scaledCropBitmap = adjustBitmapToFinalRatio(cropBitmap, systemFileRatio); - saveScaledBitmapToFile(scaledCropBitmap, cropTempFile); - - if (scaledCropBitmap != cropBitmap && !scaledCropBitmap.isRecycled()) { + // 仅当新Bitmap生成成功时,才保存并回收 + if (isBitmapValid(scaledCropBitmap)) { + saveScaledBitmapToFile(scaledCropBitmap, cropTempFile); scaledCropBitmap.recycle(); } - if (!cropBitmap.isRecycled()) { - cropBitmap.recycle(); - } + cropBitmap.recycle(); + } else { + LogUtils.e(TAG, "handleCropImageResult: 裁剪Bitmap解析无效"); } } @@ -638,11 +618,21 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return systemRatio; } + /** + * 校验 Bitmap 是否有效(未被回收且不为空) + * @param bitmap 待校验的 Bitmap + * @return true=有效 false=无效 + */ + private boolean isBitmapValid(Bitmap bitmap) { + return bitmap != null && !bitmap.isRecycled(); + } + private Bitmap adjustBitmapToFinalRatio(Bitmap originalBitmap, float finalCropRatio) { LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整比例"); - if (originalBitmap == null || originalBitmap.isRecycled() || finalCropRatio <= 0) { + // 增加 Bitmap 有效性校验 + if (!isBitmapValid(originalBitmap) || finalCropRatio <= 0) { LogUtils.e(TAG, "adjustBitmapToFinalRatio: 参数无效"); - return originalBitmap; + return null; // 直接返回null,避免传递无效Bitmap } int originalWidth = originalBitmap.getWidth(); @@ -650,8 +640,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg float originalRatio = (float) originalWidth / originalHeight; if (Math.abs(originalRatio - finalCropRatio) < 0.001f) { - LogUtils.d(TAG, "adjustBitmapToFinalRatio: 比例一致"); - return originalBitmap; + LogUtils.d(TAG, "adjustBitmapToFinalRatio: 比例一致,生成副本"); + // 关键修复:不返回原Bitmap,而是生成副本,避免原Bitmap被回收后失效 + return originalBitmap.copy(originalBitmap.getConfig(), false); } int targetWidth, targetHeight; @@ -665,20 +656,24 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg targetWidth = Math.round(targetHeight * finalCropRatio); LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight); - Bitmap adjustedBitmap = Bitmap.createBitmap( - originalBitmap, - (originalWidth - targetWidth) / 2, - (originalHeight - targetHeight) / 2, - targetWidth, - targetHeight - ); - - return adjustedBitmap; + try { + Bitmap adjustedBitmap = Bitmap.createBitmap( + originalBitmap, + (originalWidth - targetWidth) / 2, + (originalHeight - targetHeight) / 2, + targetWidth, + targetHeight + ); + return adjustedBitmap; + } catch (OutOfMemoryError e) { + LogUtils.e(TAG, "adjustBitmapToFinalRatio: OOM异常"); + return null; + } } private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) { LogUtils.d(TAG, "saveScaledBitmapToFile: 保存图片"); - if (bitmap == null || bitmap.isRecycled() || targetFile == null) { + if (!isBitmapValid(bitmap) || targetFile == null) { LogUtils.e(TAG, "saveScaledBitmapToFile: 参数无效"); return; } @@ -735,7 +730,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg Bitmap cropBitmap = null; try { cropBitmap = BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); - if (cropBitmap == null || cropBitmap.isRecycled()) { + if (!isBitmapValid(cropBitmap)) { LogUtils.e(TAG, "parseCropTempFileToBitmap: 解析失败"); return null; } @@ -754,38 +749,29 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg private void doubleRefreshPreview() { LogUtils.d(TAG, "【双重刷新】开始"); - // 清空缓存工具类的旧数据 - if (mBgSourceUtils != null) { - BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); - if (previewBean != null && previewBean.isUseBackgroundFile()) { - String bgPath = previewBean.isUseBackgroundScaledCompressFile() - ? previewBean.getBackgroundScaledCompressFilePath() - : previewBean.getBackgroundFilePath(); - mBitmapCache.removeCachedBitmap(bgPath); - LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath); - } + // 增加非空校验 + if (mBgSourceUtils == null || mBackgroundView == null || isFinishing()) { + LogUtils.w(TAG, "【双重刷新】跳过:对象为空或Activity已结束"); + return; } - // 清空 BackgroundView 自身的 Bitmap 引用 -// if (mBackgroundView != null) { -// if (mBackgroundView instanceof ImageView) { -// ((ImageView) mBackgroundView).setImageBitmap(null); -// } -// Drawable drawable = mBackgroundView.getBackground(); -// if (drawable != null) { -// drawable.setCallback(null); -// mBackgroundView.setBackground(null); -// } -// LogUtils.d(TAG, "【双重刷新】已清空控件 Bitmap 引用"); -// } + // 清空缓存工具类的旧数据 + BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); + if (previewBean != null && previewBean.isUseBackgroundFile()) { + String bgPath = previewBean.isUseBackgroundScaledCompressFile() + ? previewBean.getBackgroundScaledCompressFilePath() + : previewBean.getBackgroundFilePath(); + mBitmapCache.removeCachedBitmap(bgPath); + LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath); + } // 重新加载最新数据 - if (mBackgroundView != null && !isFinishing()) { + try { mBgSourceUtils.loadSettings(); mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); LogUtils.d(TAG, "【双重刷新】第一重完成"); - } else { - LogUtils.w(TAG, "【双重刷新】跳过"); + } catch (Exception e) { + LogUtils.e(TAG, "【双重刷新】第一重异常:" + e.getMessage()); return; } @@ -793,10 +779,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { - if (mBackgroundView != null && !isFinishing()) { - mBgSourceUtils.loadSettings(); - mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); - LogUtils.d(TAG, "【双重刷新】第二重完成"); + if (mBackgroundView != null && !isFinishing() && mBgSourceUtils != null) { + try { + mBgSourceUtils.loadSettings(); + mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); + LogUtils.d(TAG, "【双重刷新】第二重完成"); + } catch (Exception e) { + LogUtils.e(TAG, "【双重刷新】第二重异常:" + e.getMessage()); + } } } }, 200); @@ -863,62 +853,57 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg /** * 解析Uri到真实文件路径,兼容所有Android版本 - * @param uri 图片的Content Uri - * @return 真实文件路径 / null */ -// private String getRealPathFromUri(Uri uri) { -// String path = null; -// String[] projection = {MediaStore.Images.Media.DATA}; -// Cursor cursor = getContentResolver().query(uri, projection, null, null, null); -// if (cursor != null) { -// if (cursor.moveToFirst()) { -// int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); -// path = cursor.getString(columnIndex); -// } -// cursor.close(); -// } -// // 针对Android 10+沙箱限制,拷贝到私有目录 -// if (TextUtils.isEmpty(path)) { -// -// path = copyUriToPrivateDir(uri); -// } -// return path; -// } + private String getRealPathFromUri(Uri uri) { + String path = null; + String[] projection = {MediaStore.Images.Media.DATA}; + Cursor cursor = getContentResolver().query(uri, projection, null, null, null); + if (cursor != null) { + if (cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + path = cursor.getString(columnIndex); + } + cursor.close(); + } + // 针对Android 10+沙箱限制,拷贝到私有目录 + if (TextUtils.isEmpty(path)) { + path = copyUriToPrivateDir(uri); + } + return path; + } /** * 将Uri对应的图片拷贝到应用私有临时目录 - * @param uri 图片Uri - * @return 拷贝后的文件路径 / null */ -// private String copyUriToPrivateDir(Uri uri) { -// try { -// InputStream inputStream = getContentResolver().openInputStream(uri); -// if (inputStream == null) { -// return null; -// } -// File tempDir = new File(App.getTempDirPath()); -// if (!tempDir.exists()) { -// tempDir.mkdirs(); -// } -// String fileName = "select_temp_" + System.currentTimeMillis() + ".jpg"; -// File destFile = new File(tempDir, fileName); -// -// OutputStream outputStream = new FileOutputStream(destFile); -// byte[] buffer = new byte[1024 * 4]; -// int len; -// while ((len = inputStream.read(buffer)) != -1) { -// outputStream.write(buffer, 0, len); -// } -// outputStream.flush(); -// inputStream.close(); -// outputStream.close(); -// -// LogUtils.d(TAG, "【选图拷贝】文件已拷贝到私有目录:" + destFile.getAbsolutePath()); -// return destFile.getAbsolutePath(); -// } catch (IOException e) { -// LogUtils.e(TAG, "【选图拷贝】异常:" + e.getMessage()); -// return null; -// } -// } + private String copyUriToPrivateDir(Uri uri) { + try { + InputStream inputStream = getContentResolver().openInputStream(uri); + if (inputStream == null) { + return null; + } + File tempDir = new File(App.getTempDirPath()); + if (!tempDir.exists()) { + tempDir.mkdirs(); + } + String fileName = "select_temp_" + System.currentTimeMillis() + ".jpg"; + File destFile = new File(tempDir, fileName); + + OutputStream outputStream = new FileOutputStream(destFile); + byte[] buffer = new byte[1024 * 4]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + outputStream.flush(); + inputStream.close(); + outputStream.close(); + + LogUtils.d(TAG, "【选图拷贝】文件已拷贝到私有目录:" + destFile.getAbsolutePath()); + return destFile.getAbsolutePath(); + } catch (IOException e) { + LogUtils.e(TAG, "【选图拷贝】异常:" + e.getMessage()); + return null; + } + } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AssetsCopyUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AssetsCopyUtils.java new file mode 100644 index 0000000..b275dd4 --- /dev/null +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AssetsCopyUtils.java @@ -0,0 +1,122 @@ +package cc.winboll.studio.powerbell.utils; + +import android.content.Context; +import android.util.Log; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/12/11 09:14 + * @Describe Assets 目录拷贝工具类 + * 支持将 assets/images/ 下所有文件、子目录拷贝到指定路径 + */ +public class AssetsCopyUtils { + public static final String TAG = "AssetsCopyUtils"; + private static final int BUFFER_SIZE = 1024 * 8; + + /** + * 拷贝 assets/images/ 目录到指定目标目录 + * @param context 上下文 + * @param targetDirPath 目标目录完整路径(如 /sdcard/PowerBell/assets_images) + * @return 拷贝是否成功 + */ + public static boolean copyAssetsImagesToDir(Context context, String targetDirPath) { + // 拷贝 assets/images 根目录 + return copyAssetsDirToDir(context, "images", targetDirPath); + } + + /** + * 递归拷贝 assets 下指定目录到目标目录 + * @param context 上下文 + * @param assetsDir assets 下的源目录(如 "images"、"images/subdir") + * @param targetDirPath 目标目录完整路径 + * @return 拷贝是否成功 + */ + public static boolean copyAssetsDirToDir(Context context, String assetsDir, String targetDirPath) { + File targetDir = new File(targetDirPath); + // 创建目标目录(含多级父目录) + if (!targetDir.exists() && !targetDir.mkdirs()) { + Log.e(TAG, "创建目标目录失败:" + targetDirPath); + return false; + } + + try { + // 获取 assets 目录下的文件/子目录列表 + String[] fileList = context.getAssets().list(assetsDir); + if (fileList == null || fileList.length == 0) { + Log.d(TAG, "assets 目录为空:" + assetsDir); + return true; + } + + for (String fileName : fileList) { + String assetsFilePath = assetsDir + File.separator + fileName; + String targetFilePath = targetDirPath + File.separator + fileName; + + // 判断当前项是文件还是子目录 + String[] subFileList = context.getAssets().list(assetsFilePath); + if (subFileList != null && subFileList.length > 0) { + // 是子目录,递归拷贝 + if (!copyAssetsDirToDir(context, assetsFilePath, targetFilePath)) { + return false; + } + } else { + // 是文件,直接拷贝 + if (!copyAssetsFileToDir(context, assetsFilePath, targetFilePath)) { + return false; + } + } + } + Log.d(TAG, "assets 目录拷贝完成:" + assetsDir + " -> " + targetDirPath); + return true; + } catch (IOException e) { + Log.e(TAG, "拷贝 assets 目录异常:" + e.getMessage()); + return false; + } + } + + /** + * 拷贝 assets 下单个文件到指定路径 + * @param context 上下文 + * @param assetsFilePath assets 下的文件路径(如 "images/cloud.png") + * @param targetFilePath 目标文件完整路径 + * @return 拷贝是否成功 + */ + public static boolean copyAssetsFileToDir(Context context, String assetsFilePath, String targetFilePath) { + InputStream inputStream = null; + OutputStream outputStream = null; + try { + inputStream = context.getAssets().open(assetsFilePath); + File targetFile = new File(targetFilePath); + // 覆盖已存在的文件 + if (targetFile.exists() && !targetFile.delete()) { + Log.w(TAG, "覆盖目标文件失败,跳过:" + targetFilePath); + return true; + } + + outputStream = new FileOutputStream(targetFile); + byte[] buffer = new byte[BUFFER_SIZE]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, length); + } + Log.d(TAG, "文件拷贝成功:" + assetsFilePath + " -> " + targetFilePath); + return true; + } catch (IOException e) { + Log.e(TAG, "拷贝文件失败:" + assetsFilePath + ",异常:" + e.getMessage()); + return false; + } finally { + // 关闭流 + try { + if (inputStream != null) inputStream.close(); + if (outputStream != null) outputStream.close(); + } catch (IOException e) { + Log.e(TAG, "关闭流异常:" + e.getMessage()); + } + } + } +} + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java index 4f48a19..968c48e 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java @@ -246,75 +246,97 @@ public class BackgroundSourceUtils { return contentUri; } + boolean checkEmptyBackgroundAndCreateBlankBackgroundBean(BackgroundBean checkBackgroundBean) { + File fCheckBackgroundFile = new File(checkBackgroundBean.getBackgroundFilePath()); + if (!fCheckBackgroundFile.exists()) { + String newCropFileName = "blank10x10"; + String fileSuffix = "png"; + mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix); + mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix); + + AssetsCopyUtils.copyAssetsFileToDir(mContext, "images/blank10x10.png", mCropSourceFile.getAbsolutePath()); + try { + mCropResultFile.createNewFile(); + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + + // 加载图片数据模型数据 + loadSettings(); + // 修改预览数据模型 + previewBackgroundBean.setIsUseBackgroundFile(true); + previewBackgroundBean.setIsUseBackgroundScaledCompressFile(false); + + previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName()); + previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath()); + + previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName()); + previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath()); + // 保存数据模型数据更改 + saveSettings(); + return true; + } + return false; + } + /* * 创建预览数据剪裁环境 */ - public boolean createAndUpdatePreviewEnvironmentForCropping(boolean isCreateNewPreviewBean, BackgroundBean previewBackgroundBean) { + public boolean createAndUpdatePreviewEnvironmentForCropping(BackgroundBean oldPreviewBackgroundBean) { InputStream is = null; FileOutputStream fos = null; try { clearCropTempFiles(); + if(checkEmptyBackgroundAndCreateBlankBackgroundBean(oldPreviewBackgroundBean)) { + return true; + } + + Uri uri = UriUtil.getUriForFile(mContext, oldPreviewBackgroundBean.getBackgroundFilePath()); + //String szType = mContext.getContentResolver().getType(uri); + // 2. 截取MIME类型后缀(如从image/jpeg中提取jpeg)【核心新增逻辑】 + String fileSuffix = FileUtils.getFileSuffix(mContext, uri); String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis(); - String fileSuffix; + mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix); + mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix); - if (isCreateNewPreviewBean) { - fileSuffix = "png"; - mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix); - mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix); - DrawableToFileUtils.saveDrawableToFile(mContext, R.drawable.blank10x10, mCropSourceFile.getAbsolutePath()); - mCropResultFile.createNewFile(); + if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath())) { + FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile); } else { - if (FileUtils.isFileExists(previewBackgroundBean.getBackgroundFilePath())) { - File fSourceFileOld = new File(previewBackgroundBean.getBackgroundFilePath()); - Uri uri = UriUtil.getUriForFile(mContext, fSourceFileOld); - fileSuffix = FileUtils.getFileSuffix(mContext, uri); - mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix); - mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix); + mCropResultFile.createNewFile(); + } - FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundFilePath()), mCropSourceFile); - if (FileUtils.isFileExists(previewBackgroundBean.getBackgroundScaledCompressFilePath())) { - FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile); - } else { - mCropResultFile.createNewFile(); - } - - mCropSourceFile.createNewFile(); - // 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式) - is = mContext.getContentResolver().openInputStream(uri); - if (is == null) { - LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString()); - return false; - } - - // 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources) - fos = new FileOutputStream(mCropSourceFile); - byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,平衡读写性能与内存占用 - int readLen; // 每次读取的字节长度 - - // 3. 流复制(Java7 标准while循环,避免Java8+语法) - while ((readLen = is.read(buffer)) != -1) { - fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充 - } - - // 4. 强制同步写入磁盘(解决Android 10+ 异步写入导致的文件无效问题) - fos.flush(); - if (fos != null) { - try { - fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存 - } catch (IOException e) { - LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,用flush()兜底:" + e.getMessage()); - fos.flush(); - } - } - - // 解析成功日志(打印文件信息,便于问题排查) - LogUtils.d(TAG, "【选图解析】Uri解析成功!"); - LogUtils.d(TAG, "→ 原Uri:" + uri.toString()); - } else { - return createAndUpdatePreviewEnvironmentForCropping(true, previewBackgroundBean); + if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundFilePath())) { + FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundFilePath()), mCropSourceFile); + } else { + mCropSourceFile.createNewFile(); + // 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式) + is = mContext.getContentResolver().openInputStream(uri); + if (is == null) { + LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString()); + return false; } + // 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources) + fos = new FileOutputStream(mCropSourceFile); + byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,平衡读写性能与内存占用 + int readLen; // 每次读取的字节长度 + + // 3. 流复制(Java7 标准while循环,避免Java8+语法) + while ((readLen = is.read(buffer)) != -1) { + fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充 + } + + // 4. 强制同步写入磁盘(解决Android 10+ 异步写入导致的文件无效问题) + fos.flush(); + if (fos != null) { + try { + fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存 + } catch (IOException e) { + LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,用flush()兜底:" + e.getMessage()); + fos.flush(); + } + } } // 加载图片数据模型数据 @@ -328,12 +350,13 @@ public class BackgroundSourceUtils { // 保存数据模型数据更改 saveSettings(); - // 解析成功日志(打印文件信息,便于问题排查) + // 6. 解析成功日志(打印文件信息,便于问题排查) + LogUtils.d(TAG, "【选图解析】Uri解析成功!"); + LogUtils.d(TAG, "→ 原Uri:" + uri.toString()); LogUtils.d(TAG, "→ 图片剪裁数据源:" + mCropSourceFile.getAbsolutePath()); LogUtils.d(TAG, "→ 图片剪裁数据源文件大小:" + mCropSourceFile.length() + " bytes"); LogUtils.d(TAG, "→ 剪裁结果数据文件:" + mCropResultFile.getAbsolutePath()); LogUtils.d(TAG, "→ 剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes"); - return true; } catch (Exception e) { @@ -477,6 +500,10 @@ public class BackgroundSourceUtils { return fBackgroundCompressDir.getAbsolutePath(); } + public String getCropCacheDir() { + return fCropCacheDir.getAbsolutePath(); + } + public String getFileProviderAuthority() { return FILE_PROVIDER_AUTHORITY; } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java index aee1d81..ea373f7 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java @@ -18,7 +18,7 @@ import cc.winboll.studio.libappbase.LogUtils; * 特点:1. 单例模式 2. 压缩加载避免OOM 3. 路径- Bitmap 映射 4. 线程安全 */ public class BitmapCacheUtils { - private static final String TAG = "BitmapCacheUtils"; + public static final String TAG = "BitmapCacheUtils"; // 最大图片尺寸(适配1080P屏幕,可根据需求调整) private static final int MAX_WIDTH = 1080; private static final int MAX_HEIGHT = 1920;