20251211_130438_907
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#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
|
stageCount=15
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.12
|
baseVersion=15.12
|
||||||
publishVersion=15.12.14
|
publishVersion=15.12.14
|
||||||
buildCount=13
|
buildCount=21
|
||||||
baseBetaVersion=15.12.15
|
baseBetaVersion=15.12.15
|
||||||
|
|||||||
BIN
powerbell/src/main/assets/images/blank10x10.png
Normal file
BIN
powerbell/src/main/assets/images/blank10x10.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 B |
@@ -47,6 +47,7 @@ import cc.winboll.studio.powerbell.utils.ImageCropUtils;
|
|||||||
import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
||||||
import cc.winboll.studio.powerbell.utils.UriUtil;
|
import cc.winboll.studio.powerbell.utils.UriUtil;
|
||||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||||
|
import cc.winboll.studio.powerbell.utils.AssetsCopyUtils;
|
||||||
|
|
||||||
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
||||||
|
|
||||||
@@ -111,10 +112,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
mBgSourceUtils.setCurrentSourceToPreview();
|
mBgSourceUtils.setCurrentSourceToPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()));
|
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean());
|
||||||
File fTestSourceExist = new File(mBgSourceUtils.getCurrentBackgroundBean().getBackgroundFilePath());
|
|
||||||
boolean isCreateNewPreviewBean = !fTestSourceExist.exists();
|
|
||||||
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(isCreateNewPreviewBean, mBgSourceUtils.getPreviewBackgroundBean());
|
|
||||||
doubleRefreshPreview();
|
doubleRefreshPreview();
|
||||||
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成");
|
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成");
|
||||||
}
|
}
|
||||||
@@ -437,7 +435,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bitmap photoBitmap = getTakePhotoBitmap(data);
|
Bitmap photoBitmap = getTakePhotoBitmap(data);
|
||||||
if (photoBitmap != null && !photoBitmap.isRecycled()) {
|
if (isBitmapValid(photoBitmap)) {
|
||||||
mBgSourceUtils.compressQualityToRecivedPicture(photoBitmap);
|
mBgSourceUtils.compressQualityToRecivedPicture(photoBitmap);
|
||||||
} else {
|
} else {
|
||||||
ToastUtils.show("拍照图片为空");
|
ToastUtils.show("拍照图片为空");
|
||||||
@@ -462,7 +460,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
if (mfTakePhoto != null && mfTakePhoto.exists()) {
|
if (mfTakePhoto != null && mfTakePhoto.exists()) {
|
||||||
LogUtils.d(TAG, "【拍照Bitmap解析】从文件解析");
|
LogUtils.d(TAG, "【拍照Bitmap解析】从文件解析");
|
||||||
Bitmap photoBitmap = parseCropTempFileToBitmap(mfTakePhoto);
|
Bitmap photoBitmap = parseCropTempFileToBitmap(mfTakePhoto);
|
||||||
if (photoBitmap != null && !photoBitmap.isRecycled()) {
|
if (isBitmapValid(photoBitmap)) {
|
||||||
LogUtils.d(TAG, "【拍照Bitmap解析】成功");
|
LogUtils.d(TAG, "【拍照Bitmap解析】成功");
|
||||||
return photoBitmap;
|
return photoBitmap;
|
||||||
} else {
|
} else {
|
||||||
@@ -475,7 +473,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
try {
|
try {
|
||||||
Bitmap thumbnailBitmap = (Bitmap) data.getParcelableExtra("data");
|
Bitmap thumbnailBitmap = (Bitmap) data.getParcelableExtra("data");
|
||||||
if (thumbnailBitmap != null && !thumbnailBitmap.isRecycled()) {
|
if (isBitmapValid(thumbnailBitmap)) {
|
||||||
LogUtils.d(TAG, "【拍照Bitmap解析】从Intent获取成功");
|
LogUtils.d(TAG, "【拍照Bitmap解析】从Intent获取成功");
|
||||||
return thumbnailBitmap;
|
return thumbnailBitmap;
|
||||||
} else {
|
} else {
|
||||||
@@ -515,32 +513,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
LogUtils.d(TAG, "【选图权限】已添加持久化权限");
|
LogUtils.d(TAG, "【选图权限】已添加持久化权限");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 强制拷贝到应用私有目录(彻底规避系统虚拟路径问题)
|
// 2. 同步到预览 Bean 并启动裁剪
|
||||||
// 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)) {
|
if (putUriFileToPreviewSource(selectedImage)) {
|
||||||
LogUtils.d(TAG, "【选图同步】路径绑定完成");
|
LogUtils.d(TAG, "【选图同步】路径绑定完成");
|
||||||
mBitmapCache.removeCachedBitmap(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
|
mBitmapCache.removeCachedBitmap(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
|
||||||
@@ -558,7 +531,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean putUriFileToPreviewSource(Uri srcUriFile) {
|
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);
|
return putUriFileToPreviewSource(srcFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,16 +567,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
float systemFileRatio = getRatioFromSystemCropFile(cropTempFile);
|
float systemFileRatio = getRatioFromSystemCropFile(cropTempFile);
|
||||||
if (systemFileRatio > 0) {
|
if (systemFileRatio > 0) {
|
||||||
Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile);
|
Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile);
|
||||||
if (cropBitmap != null && !cropBitmap.isRecycled()) {
|
// 校验解析后的Bitmap是否有效
|
||||||
|
if (isBitmapValid(cropBitmap)) {
|
||||||
Bitmap scaledCropBitmap = adjustBitmapToFinalRatio(cropBitmap, systemFileRatio);
|
Bitmap scaledCropBitmap = adjustBitmapToFinalRatio(cropBitmap, systemFileRatio);
|
||||||
|
// 仅当新Bitmap生成成功时,才保存并回收
|
||||||
|
if (isBitmapValid(scaledCropBitmap)) {
|
||||||
saveScaledBitmapToFile(scaledCropBitmap, cropTempFile);
|
saveScaledBitmapToFile(scaledCropBitmap, cropTempFile);
|
||||||
|
|
||||||
if (scaledCropBitmap != cropBitmap && !scaledCropBitmap.isRecycled()) {
|
|
||||||
scaledCropBitmap.recycle();
|
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;
|
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) {
|
private Bitmap adjustBitmapToFinalRatio(Bitmap originalBitmap, float finalCropRatio) {
|
||||||
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整比例");
|
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整比例");
|
||||||
if (originalBitmap == null || originalBitmap.isRecycled() || finalCropRatio <= 0) {
|
// 增加 Bitmap 有效性校验
|
||||||
|
if (!isBitmapValid(originalBitmap) || finalCropRatio <= 0) {
|
||||||
LogUtils.e(TAG, "adjustBitmapToFinalRatio: 参数无效");
|
LogUtils.e(TAG, "adjustBitmapToFinalRatio: 参数无效");
|
||||||
return originalBitmap;
|
return null; // 直接返回null,避免传递无效Bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
int originalWidth = originalBitmap.getWidth();
|
int originalWidth = originalBitmap.getWidth();
|
||||||
@@ -650,8 +640,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
float originalRatio = (float) originalWidth / originalHeight;
|
float originalRatio = (float) originalWidth / originalHeight;
|
||||||
|
|
||||||
if (Math.abs(originalRatio - finalCropRatio) < 0.001f) {
|
if (Math.abs(originalRatio - finalCropRatio) < 0.001f) {
|
||||||
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 比例一致");
|
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 比例一致,生成副本");
|
||||||
return originalBitmap;
|
// 关键修复:不返回原Bitmap,而是生成副本,避免原Bitmap被回收后失效
|
||||||
|
return originalBitmap.copy(originalBitmap.getConfig(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
int targetWidth, targetHeight;
|
int targetWidth, targetHeight;
|
||||||
@@ -665,6 +656,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
targetWidth = Math.round(targetHeight * finalCropRatio);
|
targetWidth = Math.round(targetHeight * finalCropRatio);
|
||||||
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight);
|
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight);
|
||||||
|
|
||||||
|
try {
|
||||||
Bitmap adjustedBitmap = Bitmap.createBitmap(
|
Bitmap adjustedBitmap = Bitmap.createBitmap(
|
||||||
originalBitmap,
|
originalBitmap,
|
||||||
(originalWidth - targetWidth) / 2,
|
(originalWidth - targetWidth) / 2,
|
||||||
@@ -672,13 +664,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
targetWidth,
|
targetWidth,
|
||||||
targetHeight
|
targetHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
return adjustedBitmap;
|
return adjustedBitmap;
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
LogUtils.e(TAG, "adjustBitmapToFinalRatio: OOM异常");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) {
|
private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) {
|
||||||
LogUtils.d(TAG, "saveScaledBitmapToFile: 保存图片");
|
LogUtils.d(TAG, "saveScaledBitmapToFile: 保存图片");
|
||||||
if (bitmap == null || bitmap.isRecycled() || targetFile == null) {
|
if (!isBitmapValid(bitmap) || targetFile == null) {
|
||||||
LogUtils.e(TAG, "saveScaledBitmapToFile: 参数无效");
|
LogUtils.e(TAG, "saveScaledBitmapToFile: 参数无效");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -735,7 +730,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
Bitmap cropBitmap = null;
|
Bitmap cropBitmap = null;
|
||||||
try {
|
try {
|
||||||
cropBitmap = BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options);
|
cropBitmap = BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options);
|
||||||
if (cropBitmap == null || cropBitmap.isRecycled()) {
|
if (!isBitmapValid(cropBitmap)) {
|
||||||
LogUtils.e(TAG, "parseCropTempFileToBitmap: 解析失败");
|
LogUtils.e(TAG, "parseCropTempFileToBitmap: 解析失败");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -754,8 +749,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
|
|
||||||
private void doubleRefreshPreview() {
|
private void doubleRefreshPreview() {
|
||||||
LogUtils.d(TAG, "【双重刷新】开始");
|
LogUtils.d(TAG, "【双重刷新】开始");
|
||||||
|
// 增加非空校验
|
||||||
|
if (mBgSourceUtils == null || mBackgroundView == null || isFinishing()) {
|
||||||
|
LogUtils.w(TAG, "【双重刷新】跳过:对象为空或Activity已结束");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 清空缓存工具类的旧数据
|
// 清空缓存工具类的旧数据
|
||||||
if (mBgSourceUtils != null) {
|
|
||||||
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
|
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
|
||||||
if (previewBean != null && previewBean.isUseBackgroundFile()) {
|
if (previewBean != null && previewBean.isUseBackgroundFile()) {
|
||||||
String bgPath = previewBean.isUseBackgroundScaledCompressFile()
|
String bgPath = previewBean.isUseBackgroundScaledCompressFile()
|
||||||
@@ -764,28 +764,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
mBitmapCache.removeCachedBitmap(bgPath);
|
mBitmapCache.removeCachedBitmap(bgPath);
|
||||||
LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath);
|
LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 清空 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 引用");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 重新加载最新数据
|
// 重新加载最新数据
|
||||||
if (mBackgroundView != null && !isFinishing()) {
|
try {
|
||||||
mBgSourceUtils.loadSettings();
|
mBgSourceUtils.loadSettings();
|
||||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||||
LogUtils.d(TAG, "【双重刷新】第一重完成");
|
LogUtils.d(TAG, "【双重刷新】第一重完成");
|
||||||
} else {
|
} catch (Exception e) {
|
||||||
LogUtils.w(TAG, "【双重刷新】跳过");
|
LogUtils.e(TAG, "【双重刷新】第一重异常:" + e.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,10 +779,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (mBackgroundView != null && !isFinishing()) {
|
if (mBackgroundView != null && !isFinishing() && mBgSourceUtils != null) {
|
||||||
|
try {
|
||||||
mBgSourceUtils.loadSettings();
|
mBgSourceUtils.loadSettings();
|
||||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||||
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "【双重刷新】第二重异常:" + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
@@ -863,62 +853,57 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析Uri到真实文件路径,兼容所有Android版本
|
* 解析Uri到真实文件路径,兼容所有Android版本
|
||||||
* @param uri 图片的Content Uri
|
|
||||||
* @return 真实文件路径 / null
|
|
||||||
*/
|
*/
|
||||||
// private String getRealPathFromUri(Uri uri) {
|
private String getRealPathFromUri(Uri uri) {
|
||||||
// String path = null;
|
String path = null;
|
||||||
// String[] projection = {MediaStore.Images.Media.DATA};
|
String[] projection = {MediaStore.Images.Media.DATA};
|
||||||
// Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
|
Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
|
||||||
// if (cursor != null) {
|
if (cursor != null) {
|
||||||
// if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
// int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||||
// path = cursor.getString(columnIndex);
|
path = cursor.getString(columnIndex);
|
||||||
// }
|
}
|
||||||
// cursor.close();
|
cursor.close();
|
||||||
// }
|
}
|
||||||
// // 针对Android 10+沙箱限制,拷贝到私有目录
|
// 针对Android 10+沙箱限制,拷贝到私有目录
|
||||||
// if (TextUtils.isEmpty(path)) {
|
if (TextUtils.isEmpty(path)) {
|
||||||
//
|
path = copyUriToPrivateDir(uri);
|
||||||
// path = copyUriToPrivateDir(uri);
|
}
|
||||||
// }
|
return path;
|
||||||
// return path;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将Uri对应的图片拷贝到应用私有临时目录
|
* 将Uri对应的图片拷贝到应用私有临时目录
|
||||||
* @param uri 图片Uri
|
|
||||||
* @return 拷贝后的文件路径 / null
|
|
||||||
*/
|
*/
|
||||||
// private String copyUriToPrivateDir(Uri uri) {
|
private String copyUriToPrivateDir(Uri uri) {
|
||||||
// try {
|
try {
|
||||||
// InputStream inputStream = getContentResolver().openInputStream(uri);
|
InputStream inputStream = getContentResolver().openInputStream(uri);
|
||||||
// if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
// return null;
|
return null;
|
||||||
// }
|
}
|
||||||
// File tempDir = new File(App.getTempDirPath());
|
File tempDir = new File(App.getTempDirPath());
|
||||||
// if (!tempDir.exists()) {
|
if (!tempDir.exists()) {
|
||||||
// tempDir.mkdirs();
|
tempDir.mkdirs();
|
||||||
// }
|
}
|
||||||
// String fileName = "select_temp_" + System.currentTimeMillis() + ".jpg";
|
String fileName = "select_temp_" + System.currentTimeMillis() + ".jpg";
|
||||||
// File destFile = new File(tempDir, fileName);
|
File destFile = new File(tempDir, fileName);
|
||||||
//
|
|
||||||
// OutputStream outputStream = new FileOutputStream(destFile);
|
OutputStream outputStream = new FileOutputStream(destFile);
|
||||||
// byte[] buffer = new byte[1024 * 4];
|
byte[] buffer = new byte[1024 * 4];
|
||||||
// int len;
|
int len;
|
||||||
// while ((len = inputStream.read(buffer)) != -1) {
|
while ((len = inputStream.read(buffer)) != -1) {
|
||||||
// outputStream.write(buffer, 0, len);
|
outputStream.write(buffer, 0, len);
|
||||||
// }
|
}
|
||||||
// outputStream.flush();
|
outputStream.flush();
|
||||||
// inputStream.close();
|
inputStream.close();
|
||||||
// outputStream.close();
|
outputStream.close();
|
||||||
//
|
|
||||||
// LogUtils.d(TAG, "【选图拷贝】文件已拷贝到私有目录:" + destFile.getAbsolutePath());
|
LogUtils.d(TAG, "【选图拷贝】文件已拷贝到私有目录:" + destFile.getAbsolutePath());
|
||||||
// return destFile.getAbsolutePath();
|
return destFile.getAbsolutePath();
|
||||||
// } catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// LogUtils.e(TAG, "【选图拷贝】异常:" + e.getMessage());
|
LogUtils.e(TAG, "【选图拷贝】异常:" + e.getMessage());
|
||||||
// return null;
|
return null;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -246,39 +246,69 @@ public class BackgroundSourceUtils {
|
|||||||
return contentUri;
|
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;
|
InputStream is = null;
|
||||||
FileOutputStream fos = null;
|
FileOutputStream fos = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clearCropTempFiles();
|
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 newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis();
|
||||||
String 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();
|
|
||||||
} 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);
|
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
|
||||||
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
||||||
|
|
||||||
FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
|
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath())) {
|
||||||
if (FileUtils.isFileExists(previewBackgroundBean.getBackgroundScaledCompressFilePath())) {
|
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile);
|
||||||
FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile);
|
|
||||||
} else {
|
} else {
|
||||||
mCropResultFile.createNewFile();
|
mCropResultFile.createNewFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundFilePath())) {
|
||||||
|
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
|
||||||
|
} else {
|
||||||
mCropSourceFile.createNewFile();
|
mCropSourceFile.createNewFile();
|
||||||
// 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式)
|
// 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式)
|
||||||
is = mContext.getContentResolver().openInputStream(uri);
|
is = mContext.getContentResolver().openInputStream(uri);
|
||||||
@@ -307,14 +337,6 @@ public class BackgroundSourceUtils {
|
|||||||
fos.flush();
|
fos.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析成功日志(打印文件信息,便于问题排查)
|
|
||||||
LogUtils.d(TAG, "【选图解析】Uri解析成功!");
|
|
||||||
LogUtils.d(TAG, "→ 原Uri:" + uri.toString());
|
|
||||||
} else {
|
|
||||||
return createAndUpdatePreviewEnvironmentForCropping(true, previewBackgroundBean);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载图片数据模型数据
|
// 加载图片数据模型数据
|
||||||
@@ -328,12 +350,13 @@ public class BackgroundSourceUtils {
|
|||||||
// 保存数据模型数据更改
|
// 保存数据模型数据更改
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
|
||||||
// 解析成功日志(打印文件信息,便于问题排查)
|
// 6. 解析成功日志(打印文件信息,便于问题排查)
|
||||||
|
LogUtils.d(TAG, "【选图解析】Uri解析成功!");
|
||||||
|
LogUtils.d(TAG, "→ 原Uri:" + uri.toString());
|
||||||
LogUtils.d(TAG, "→ 图片剪裁数据源:" + mCropSourceFile.getAbsolutePath());
|
LogUtils.d(TAG, "→ 图片剪裁数据源:" + mCropSourceFile.getAbsolutePath());
|
||||||
LogUtils.d(TAG, "→ 图片剪裁数据源文件大小:" + mCropSourceFile.length() + " bytes");
|
LogUtils.d(TAG, "→ 图片剪裁数据源文件大小:" + mCropSourceFile.length() + " bytes");
|
||||||
LogUtils.d(TAG, "→ 剪裁结果数据文件:" + mCropResultFile.getAbsolutePath());
|
LogUtils.d(TAG, "→ 剪裁结果数据文件:" + mCropResultFile.getAbsolutePath());
|
||||||
LogUtils.d(TAG, "→ 剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes");
|
LogUtils.d(TAG, "→ 剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -477,6 +500,10 @@ public class BackgroundSourceUtils {
|
|||||||
return fBackgroundCompressDir.getAbsolutePath();
|
return fBackgroundCompressDir.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCropCacheDir() {
|
||||||
|
return fCropCacheDir.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
public String getFileProviderAuthority() {
|
public String getFileProviderAuthority() {
|
||||||
return FILE_PROVIDER_AUTHORITY;
|
return FILE_PROVIDER_AUTHORITY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import cc.winboll.studio.libappbase.LogUtils;
|
|||||||
* 特点:1. 单例模式 2. 压缩加载避免OOM 3. 路径- Bitmap 映射 4. 线程安全
|
* 特点:1. 单例模式 2. 压缩加载避免OOM 3. 路径- Bitmap 映射 4. 线程安全
|
||||||
*/
|
*/
|
||||||
public class BitmapCacheUtils {
|
public class BitmapCacheUtils {
|
||||||
private static final String TAG = "BitmapCacheUtils";
|
public static final String TAG = "BitmapCacheUtils";
|
||||||
// 最大图片尺寸(适配1080P屏幕,可根据需求调整)
|
// 最大图片尺寸(适配1080P屏幕,可根据需求调整)
|
||||||
private static final int MAX_WIDTH = 1080;
|
private static final int MAX_WIDTH = 1080;
|
||||||
private static final int MAX_HEIGHT = 1920;
|
private static final int MAX_HEIGHT = 1920;
|
||||||
|
|||||||
Reference in New Issue
Block a user