20251211_130438_907

This commit is contained in:
2025-12-11 13:04:44 +08:00
parent 220aa9dbfb
commit fd556fd06f
6 changed files with 376 additions and 242 deletions

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

View File

@@ -47,6 +47,7 @@ import cc.winboll.studio.powerbell.utils.ImageCropUtils;
import cc.winboll.studio.powerbell.utils.PermissionUtils;
import cc.winboll.studio.powerbell.utils.UriUtil;
import cc.winboll.studio.powerbell.views.BackgroundView;
import cc.winboll.studio.powerbell.utils.AssetsCopyUtils;
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
@@ -55,8 +56,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private PermissionUtils mPermissionUtils;
private BitmapCacheUtils mBitmapCache;
// 新增:手动定义 Android 13 对应的 SDK 版本号
private static final int SDK_VERSION_TIRAMISU = 33;
// 新增:手动定义 Android 13 对应的 SDK 版本号
private static final int SDK_VERSION_TIRAMISU = 33;
public static final int REQUEST_SELECT_PICTURE = 0;
public static final int REQUEST_TAKE_PHOTO = 1;
@@ -111,10 +112,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
mBgSourceUtils.setCurrentSourceToPreview();
}
//Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()));
File fTestSourceExist = new File(mBgSourceUtils.getCurrentBackgroundBean().getBackgroundFilePath());
boolean isCreateNewPreviewBean = !fTestSourceExist.exists();
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(isCreateNewPreviewBean, mBgSourceUtils.getPreviewBackgroundBean());
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean());
doubleRefreshPreview();
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成");
}
@@ -191,21 +189,21 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
};
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
@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();
}
}
}
};
@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;
}
}
}

View File

@@ -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&豆包大模型<zhangsken@qq.com>
* @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());
}
}
}
}

View File

@@ -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;
}

View File

@@ -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;