20251201_042831_062
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Nov 30 20:02:18 GMT 2025
|
||||
#Sun Nov 30 20:24:08 GMT 2025
|
||||
stageCount=13
|
||||
libraryProject=
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.12
|
||||
buildCount=20
|
||||
buildCount=23
|
||||
baseBetaVersion=15.11.13
|
||||
|
||||
@@ -58,7 +58,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
|
||||
private AToolbar mAToolbar;
|
||||
private File mfBackgroundDir; // 背景图片存储文件夹
|
||||
private File mfPictureDir; // 拍照与剪裁临时文件夹(权限友好)
|
||||
private File mfPictureDir; // 拍照临时文件夹(权限友好)
|
||||
private File mfTakePhoto; // 拍照文件
|
||||
|
||||
// 背景视图预览图片的文件名
|
||||
@@ -67,9 +67,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
BackgroundView bvPreviewBackground;
|
||||
boolean isCommitSettings = false;
|
||||
|
||||
// 静态变量(裁剪临时文件迁移到临时目录,解决权限冲突)
|
||||
// 静态变量(核心修改:临时裁剪文件迁移到应用缓存目录)
|
||||
private static String _mSourceCropTempFileName = "SourceCropTemp.jpg";
|
||||
private static File _mSourceCropTempFile; // 存储在mfPictureDir(临时目录)
|
||||
private static File _mSourceCropTempFile; // 存储在【应用缓存目录】(getCacheDir())
|
||||
private static String _mSourceCroppedFileName = "SourceCropped.jpg";
|
||||
private static File _mSourceCroppedFile;
|
||||
private static String _mSourceCroppedFilePath;
|
||||
@@ -114,15 +114,22 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
_mSourceCroppedFile = new File(mfBackgroundDir, _mSourceCroppedFileName); // 迁移到私有目录
|
||||
_mSourceCroppedFilePath = _mSourceCroppedFile.getAbsolutePath().toString();
|
||||
|
||||
// 初始化文件对象(裁剪临时文件迁移到临时目录,解决权限冲突)
|
||||
// 核心修改1:初始化临时裁剪文件到【应用缓存目录】(getCacheDir())- 替代外部存储路径
|
||||
File appCacheDir = getCacheDir(); // 应用私有缓存目录:/data/data/包名/cache(无需外部存储权限)
|
||||
File cropTempDir = new File(appCacheDir, "CropTemp"); // 缓存目录下创建裁剪临时子目录,便于管理
|
||||
if (!cropTempDir.exists()) {
|
||||
cropTempDir.mkdirs(); // 确保裁剪临时目录存在
|
||||
LogUtils.d(TAG, "【缓存目录初始化】创建应用缓存裁剪目录:" + cropTempDir.getAbsolutePath());
|
||||
}
|
||||
_mSourceCropTempFile = new File(cropTempDir, _mSourceCropTempFileName); // 临时裁剪文件放在缓存目录下
|
||||
// 初始化拍照文件(保留原路径,若需迁移可参考裁剪文件修改)
|
||||
mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg");
|
||||
_mSourceCropTempFile = new File(mfPictureDir, _mSourceCropTempFileName); // 迁移到mfPictureDir
|
||||
|
||||
// ====================================== 初始化调试日志(关键路径校验)======================================
|
||||
LogUtils.d(TAG, "【初始化】mfBackgroundDir 路径:" + mfBackgroundDir.getAbsolutePath() + ",是否存在:" + mfBackgroundDir.exists());
|
||||
LogUtils.d(TAG, "【初始化】mfPictureDir 路径:" + mfPictureDir.getAbsolutePath() + ",是否存在:" + mfPictureDir.exists());
|
||||
LogUtils.d(TAG, "【初始化】_mSourceCroppedFilePath : " + _mSourceCroppedFilePath + ",父目录是否存在:" + _mSourceCroppedFile.getParentFile().exists());
|
||||
LogUtils.d(TAG, "【初始化】裁剪临时文件路径 : " + _mSourceCropTempFile.getAbsolutePath() + ",是否可写:" + _mSourceCropTempFile.canWrite());
|
||||
LogUtils.d(TAG, "【初始化】裁剪临时文件路径(应用缓存目录): " + _mSourceCropTempFile.getAbsolutePath() + ",是否可写:" + _mSourceCropTempFile.canWrite());
|
||||
LogUtils.d(TAG, "【初始化】拍照文件路径 : " + mfTakePhoto.getAbsolutePath() + ",是否可写:" + mfTakePhoto.canWrite());
|
||||
// ====================================== 初始化调试日志(关键路径校验)======================================
|
||||
|
||||
@@ -312,53 +319,53 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
|
||||
// 点击事件监听器:自由裁剪
|
||||
private View.OnClickListener onCropFreePictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发自由裁剪功能");
|
||||
File fCheck = new File(mfBackgroundDir, getBackgroundFileName());
|
||||
if (fCheck.exists()) {
|
||||
startCropImageActivity(true);
|
||||
LogUtils.d(TAG, "【裁剪启动】自由裁剪已启动,目标文件:" + fCheck.getAbsolutePath());
|
||||
} else {
|
||||
ToastUtils.show("无可用裁剪图片,请先选择/拍照");
|
||||
LogUtils.e(TAG, "【裁剪失败】无可用裁剪图片,文件路径:" + fCheck.getAbsolutePath() + ",是否存在:" + fCheck.exists());
|
||||
}
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发自由裁剪功能");
|
||||
File fCheck = new File(mfBackgroundDir, getBackgroundFileName());
|
||||
if (fCheck.exists()) {
|
||||
startCropImageActivity(true);
|
||||
LogUtils.d(TAG, "【裁剪启动】自由裁剪已启动,目标文件:" + fCheck.getAbsolutePath());
|
||||
} else {
|
||||
ToastUtils.show("无可用裁剪图片,请先选择/拍照");
|
||||
LogUtils.e(TAG, "【裁剪失败】无可用裁剪图片,文件路径:" + fCheck.getAbsolutePath() + ",是否存在:" + fCheck.exists());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 点击事件监听器:拍照
|
||||
private View.OnClickListener onTakePhotoClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发拍照功能");
|
||||
LogUtils.d(TAG, "【拍照准备】mfTakePhoto 初始路径 : " + mfTakePhoto.getPath());
|
||||
// 点击事件监听器:拍照
|
||||
private View.OnClickListener onTakePhotoClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发拍照功能");
|
||||
LogUtils.d(TAG, "【拍照准备】mfTakePhoto 初始路径 : " + mfTakePhoto.getPath());
|
||||
|
||||
// 清理旧拍照文件
|
||||
if (mfTakePhoto.exists()) {
|
||||
boolean deleteSuccess = mfTakePhoto.delete();
|
||||
LogUtils.d(TAG, "【拍照准备】旧拍照文件清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
}
|
||||
// 创建新拍照文件
|
||||
try {
|
||||
boolean createSuccess = mfTakePhoto.createNewFile();
|
||||
LogUtils.d(TAG, "【拍照准备】新拍照文件创建:" + (createSuccess ? "成功" : "失败") + ",路径:" + mfTakePhoto.getAbsolutePath());
|
||||
if (!createSuccess) {
|
||||
ToastUtils.show("拍照文件创建失败");
|
||||
LogUtils.e(TAG, "【拍照失败】新拍照文件创建失败,无写入权限");
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "【拍照异常】文件创建抛出异常:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("拍照文件创建失败:" + e.getMessage().substring(0, 20));
|
||||
return;
|
||||
}
|
||||
// 清理旧拍照文件
|
||||
if (mfTakePhoto.exists()) {
|
||||
boolean deleteSuccess = mfTakePhoto.delete();
|
||||
LogUtils.d(TAG, "【拍照准备】旧拍照文件清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
}
|
||||
// 创建新拍照文件
|
||||
try {
|
||||
boolean createSuccess = mfTakePhoto.createNewFile();
|
||||
LogUtils.d(TAG, "【拍照准备】新拍照文件创建:" + (createSuccess ? "成功" : "失败") + ",路径:" + mfTakePhoto.getAbsolutePath());
|
||||
if (!createSuccess) {
|
||||
ToastUtils.show("拍照文件创建失败");
|
||||
LogUtils.e(TAG, "【拍照失败】新拍照文件创建失败,无写入权限");
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "【拍照异常】文件创建抛出异常:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("拍照文件创建失败:" + e.getMessage().substring(0, 20));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查存储权限后启动相机
|
||||
if (checkAndRequestStoragePermission()) {
|
||||
LogUtils.d(TAG, "【拍照权限】存储权限已获取,开始生成拍照Uri");
|
||||
// 适配Android 7.0+ 拍照Uri(多包名兼容)
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
try {
|
||||
// 检查存储权限后启动相机
|
||||
if (checkAndRequestStoragePermission()) {
|
||||
LogUtils.d(TAG, "【拍照权限】存储权限已获取,开始生成拍照Uri");
|
||||
// 适配Android 7.0+ 拍照Uri(多包名兼容)
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
try {
|
||||
Uri photoUri = getUriForFile(BackgroundSettingsActivity.this, mfTakePhoto);
|
||||
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
|
||||
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
|
||||
@@ -369,128 +376,131 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
LogUtils.d(TAG, errMsg, Thread.currentThread().getStackTrace());
|
||||
LogUtils.e(TAG, "【拍照失败】相机启动失败,FileProvider配置异常");
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "【拍照权限】存储权限未获取,已触发权限申请");
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
LogUtils.d(TAG, "【拍照权限】存储权限未获取,已触发权限申请");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ToastUtils.show("图片接收功能暂未实现");
|
||||
LogUtils.d(TAG, "【按钮点击】触发onReceivedPictureClickListener(暂未实现)");
|
||||
}
|
||||
};
|
||||
private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ToastUtils.show("图片接收功能暂未实现");
|
||||
LogUtils.d(TAG, "【按钮点击】触发onReceivedPictureClickListener(暂未实现)");
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发像素拾取功能");
|
||||
// 校验裁剪后文件是否有效
|
||||
if (_mSourceCroppedFile == null || !_mSourceCroppedFile.exists() || _mSourceCroppedFile.length() <= 0) {
|
||||
ToastUtils.show("无有效图片可拾取像素");
|
||||
LogUtils.e(TAG, "【像素拾取失败】目标图片无效:路径=" + (_mSourceCroppedFile != null ? _mSourceCroppedFile.getAbsolutePath() : "null") + ",是否存在:" + (_mSourceCroppedFile != null && _mSourceCroppedFile.exists()));
|
||||
return;
|
||||
}
|
||||
// 从文件路径启动像素拾取活动
|
||||
String imagePath = _mSourceCroppedFile.toString();
|
||||
Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class);
|
||||
intent.putExtra("imagePath", imagePath);
|
||||
startActivity(intent);
|
||||
LogUtils.d(TAG, "【像素拾取启动】已跳转至PixelPickerActivity,图片路径:" + imagePath);
|
||||
}
|
||||
};
|
||||
private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发像素拾取功能");
|
||||
// 核心修改:通过BackgroundSourceUtils获取正式背景路径,替代旧的_mSourceCroppedFile(避免缓存目录文件依赖)
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
|
||||
String targetImagePath = utils.getCurrentBackgroundFilePath();
|
||||
File targetFile = new File(targetImagePath);
|
||||
// 校验目标图片有效性
|
||||
if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) {
|
||||
ToastUtils.show("无有效图片可拾取像素");
|
||||
LogUtils.e(TAG, "【像素拾取失败】目标图片无效:路径=" + (targetFile != null ? targetFile.getAbsolutePath() : "null") + ",是否存在:" + (targetFile != null && targetFile.exists()));
|
||||
return;
|
||||
}
|
||||
// 从文件路径启动像素拾取活动
|
||||
Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class);
|
||||
intent.putExtra("imagePath", targetImagePath);
|
||||
startActivity(intent);
|
||||
LogUtils.d(TAG, "【像素拾取启动】已跳转至PixelPickerActivity,图片路径:" + targetImagePath);
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发像素颜色清空功能");
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
|
||||
BackgroundBean bean = utils.getCurrentBackgroundBean();
|
||||
int oldColor = bean.getPixelColor();
|
||||
bean.setPixelColor(0);
|
||||
utils.saveSettings();
|
||||
setBackgroundColor();
|
||||
ToastUtils.show("像素颜色已清空");
|
||||
LogUtils.d(TAG, "【像素清空】操作完成:旧颜色值=" + oldColor + ",新颜色值=0,配置已保存");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 压缩图片并保存到接收文件(拍照/选图后压缩)
|
||||
*/
|
||||
void compressQualityToRecivedPicture(Bitmap bitmap) {
|
||||
LogUtils.d(TAG, "【压缩启动】开始压缩图片,Bitmap是否有效:" + (bitmap != null && !bitmap.isRecycled()));
|
||||
if (bitmap == null || bitmap.isRecycled()) {
|
||||
ToastUtils.show("压缩失败:图片为空");
|
||||
LogUtils.e(TAG, "【压缩失败】Bitmap为空或已回收,无法压缩");
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream outStream = null;
|
||||
try {
|
||||
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
|
||||
String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath();
|
||||
File fRecivedPicture = new File(scaledCompressFilePath); // 直接使用完整路径,避免拼接错误
|
||||
LogUtils.d(TAG, "【压缩配置】目标压缩路径:" + scaledCompressFilePath + ",Bitmap原始大小:" + bitmap.getByteCount() / 1024 + "KB");
|
||||
|
||||
// 确保父目录存在
|
||||
File parentDir = fRecivedPicture.getParentFile();
|
||||
if (!parentDir.exists()) {
|
||||
boolean mkdirSuccess = parentDir.mkdirs();
|
||||
LogUtils.d(TAG, "【压缩准备】目标目录创建:" + (mkdirSuccess ? "成功" : "失败") + ",目录路径:" + parentDir.getAbsolutePath());
|
||||
if (!mkdirSuccess) {
|
||||
ToastUtils.show("压缩目录创建失败");
|
||||
LogUtils.e(TAG, "【压缩失败】目标目录创建失败,无写入权限");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建目标文件(若不存在)
|
||||
if (!fRecivedPicture.exists()) {
|
||||
boolean createSuccess = fRecivedPicture.createNewFile();
|
||||
LogUtils.d(TAG, "【压缩准备】目标文件创建:" + (createSuccess ? "成功" : "失败"));
|
||||
if (!createSuccess) {
|
||||
ToastUtils.show("压缩文件创建失败");
|
||||
LogUtils.e(TAG, "【压缩失败】目标文件创建失败,无写入权限");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 压缩并保存
|
||||
FileOutputStream fos = new FileOutputStream(fRecivedPicture);
|
||||
outStream = new BufferedOutputStream(fos);
|
||||
boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
|
||||
outStream.flush();
|
||||
LogUtils.d(TAG, "【压缩结果】图片压缩:" + (compressSuccess ? "成功" : "失败") + ",压缩后大小:" + fRecivedPicture.length() / 1024 + "KB");
|
||||
if (!compressSuccess) {
|
||||
ToastUtils.show("图片压缩失败");
|
||||
LogUtils.e(TAG, "【压缩失败】Bitmap压缩返回false,图片损坏或格式不支持");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "【压缩异常】IO异常:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("图片压缩失败:" + e.getMessage().substring(0, 20));
|
||||
} finally {
|
||||
// 关闭流资源
|
||||
if (outStream != null) {
|
||||
try {
|
||||
outStream.close();
|
||||
LogUtils.d(TAG, "【压缩清理】输出流已关闭");
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "【压缩异常】流关闭失败:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
// 回收Bitmap(避免内存泄漏)
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
bitmap.recycle();
|
||||
LogUtils.d(TAG, "【压缩清理】Bitmap已回收");
|
||||
}
|
||||
}
|
||||
}
|
||||
private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】触发像素颜色清空功能");
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
|
||||
BackgroundBean bean = utils.getCurrentBackgroundBean();
|
||||
int oldColor = bean.getPixelColor();
|
||||
bean.setPixelColor(0);
|
||||
utils.saveSettings();
|
||||
setBackgroundColor();
|
||||
ToastUtils.show("像素颜色已清空");
|
||||
LogUtils.d(TAG, "【像素清空】操作完成:旧颜色值=" + oldColor + ",新颜色值=0,配置已保存");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 启动图片裁剪活动(修复:FileProvider适配+意图兼容+异常捕获)
|
||||
* 压缩图片并保存到接收文件(拍照/选图后压缩)
|
||||
*/
|
||||
void compressQualityToRecivedPicture(Bitmap bitmap) {
|
||||
LogUtils.d(TAG, "【压缩启动】开始压缩图片,Bitmap是否有效:" + (bitmap != null && !bitmap.isRecycled()));
|
||||
if (bitmap == null || bitmap.isRecycled()) {
|
||||
ToastUtils.show("压缩失败:图片为空");
|
||||
LogUtils.e(TAG, "【压缩失败】Bitmap为空或已回收,无法压缩");
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream outStream = null;
|
||||
try {
|
||||
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
|
||||
String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath();
|
||||
File fRecivedPicture = new File(scaledCompressFilePath); // 直接使用完整路径,避免拼接错误
|
||||
LogUtils.d(TAG, "【压缩配置】目标压缩路径:" + scaledCompressFilePath + ",Bitmap原始大小:" + bitmap.getByteCount() / 1024 + "KB");
|
||||
|
||||
// 确保父目录存在
|
||||
File parentDir = fRecivedPicture.getParentFile();
|
||||
if (!parentDir.exists()) {
|
||||
boolean mkdirSuccess = parentDir.mkdirs();
|
||||
LogUtils.d(TAG, "【压缩准备】目标目录创建:" + (mkdirSuccess ? "成功" : "失败") + ",目录路径:" + parentDir.getAbsolutePath());
|
||||
if (!mkdirSuccess) {
|
||||
ToastUtils.show("压缩目录创建失败");
|
||||
LogUtils.e(TAG, "【压缩失败】目标目录创建失败,无写入权限");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建目标文件(若不存在)
|
||||
if (!fRecivedPicture.exists()) {
|
||||
boolean createSuccess = fRecivedPicture.createNewFile();
|
||||
LogUtils.d(TAG, "【压缩准备】目标文件创建:" + (createSuccess ? "成功" : "失败"));
|
||||
if (!createSuccess) {
|
||||
ToastUtils.show("压缩文件创建失败");
|
||||
LogUtils.e(TAG, "【压缩失败】目标文件创建失败,无写入权限");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 压缩并保存
|
||||
FileOutputStream fos = new FileOutputStream(fRecivedPicture);
|
||||
outStream = new BufferedOutputStream(fos);
|
||||
boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
|
||||
outStream.flush();
|
||||
LogUtils.d(TAG, "【压缩结果】图片压缩:" + (compressSuccess ? "成功" : "失败") + ",压缩后大小:" + fRecivedPicture.length() / 1024 + "KB");
|
||||
if (!compressSuccess) {
|
||||
ToastUtils.show("图片压缩失败");
|
||||
LogUtils.e(TAG, "【压缩失败】Bitmap压缩返回false,图片损坏或格式不支持");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "【压缩异常】IO异常:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("图片压缩失败:" + e.getMessage().substring(0, 20));
|
||||
} finally {
|
||||
// 关闭流资源
|
||||
if (outStream != null) {
|
||||
try {
|
||||
outStream.close();
|
||||
LogUtils.d(TAG, "【压缩清理】输出流已关闭");
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "【压缩异常】流关闭失败:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
// 回收Bitmap(避免内存泄漏)
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
bitmap.recycle();
|
||||
LogUtils.d(TAG, "【压缩清理】Bitmap已回收");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动图片裁剪活动(修复:FileProvider适配+意图兼容+异常捕获,适配缓存目录临时文件)
|
||||
* @param isCropFree 是否自由裁剪
|
||||
*/
|
||||
public void startCropImageActivity(boolean isCropFree) {
|
||||
@@ -523,29 +533,29 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
return;
|
||||
}
|
||||
|
||||
// 清理旧裁剪临时文件
|
||||
// 清理旧裁剪临时文件(应用缓存目录下)
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
boolean deleteSuccess = _mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【裁剪准备】旧裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
LogUtils.d(TAG, "【裁剪准备】旧裁剪临时文件(缓存目录)清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
}
|
||||
// 创建新裁剪临时文件并设置权限
|
||||
// 创建新裁剪临时文件并设置权限(应用缓存目录下,无需外部存储权限)
|
||||
try {
|
||||
_mSourceCropTempFile.createNewFile();
|
||||
// 核心优化:设置文件权限(确保裁剪工具可读写)
|
||||
// 核心优化:设置文件权限(确保裁剪工具可读写,适配缓存目录权限)
|
||||
_mSourceCropTempFile.setReadable(true, false);
|
||||
_mSourceCropTempFile.setWritable(true, false);
|
||||
_mSourceCropTempFile.setExecutable(false, false); // 关闭执行权限,提升安全性
|
||||
LogUtils.d(TAG, "【裁剪准备】新裁剪临时文件创建成功,路径:" + _mSourceCropTempFile.getAbsolutePath() + ",读写权限:" + _mSourceCropTempFile.canRead() + "/" + _mSourceCropTempFile.canWrite());
|
||||
LogUtils.d(TAG, "【裁剪准备】新裁剪临时文件(缓存目录)创建成功,路径:" + _mSourceCropTempFile.getAbsolutePath() + ",读写权限:" + _mSourceCropTempFile.canRead() + "/" + _mSourceCropTempFile.canWrite());
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "【裁剪异常】临时文件创建失败:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
LogUtils.d(TAG, "【裁剪异常】缓存目录临时文件创建失败:" + e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("剪裁临时文件创建失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成裁剪输出Uri
|
||||
// 生成裁剪输出Uri(适配缓存目录文件)
|
||||
try {
|
||||
cropOutPutUri = getUriForFile(this, _mSourceCropTempFile);
|
||||
LogUtils.d(TAG, "【裁剪Uri】裁剪输出Uri生成成功 : " + cropOutPutUri.toString());
|
||||
LogUtils.d(TAG, "【裁剪Uri】裁剪输出Uri(缓存目录)生成成功 : " + cropOutPutUri.toString());
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【裁剪异常】生成裁剪输出Uri失败:" + e.getMessage());
|
||||
ToastUtils.show("图片裁剪失败:无法创建临时文件");
|
||||
@@ -583,16 +593,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
|
||||
// 裁剪参数配置
|
||||
intent.putExtra("return-data", false); // 不返回Bitmap,避免OOM
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri); // 输出到临时文件
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri); // 输出到缓存目录临时文件
|
||||
intent.putExtra("scale", true); // 允许缩放
|
||||
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); // 输出格式
|
||||
// 授予裁剪工具读写权限(关键,避免权限不足)
|
||||
// 授予裁剪工具读写权限(关键,避免缓存目录权限不足)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
// 核心修复3:添加意图启动校验(避免启动失败无响应)
|
||||
try {
|
||||
startActivityForResult(intent, REQUEST_CROP_IMAGE);
|
||||
LogUtils.d(TAG, "【裁剪启动】裁剪意图已启动,请求码:" + REQUEST_CROP_IMAGE + ",目标输出Uri:" + cropOutPutUri.toString());
|
||||
LogUtils.d(TAG, "【裁剪启动】裁剪意图已启动,请求码:" + REQUEST_CROP_IMAGE + ",目标输出Uri(缓存目录):" + cropOutPutUri.toString());
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【裁剪异常】启动裁剪窗口失败:" + e.getMessage());
|
||||
ToastUtils.show("无法启动裁剪工具,请安装系统相机");
|
||||
@@ -608,66 +618,66 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:计算两个数的最大公约数(用于简化宽高比)
|
||||
* @param a 第一个数(宽)
|
||||
* @param b 第二个数(高)
|
||||
* @return 最大公约数
|
||||
*/
|
||||
private int calculateGCD(int a, int b) {
|
||||
if (b == 0) {
|
||||
return a;
|
||||
}
|
||||
return calculateGCD(b, a % b);
|
||||
}
|
||||
/**
|
||||
* 工具方法:计算两个数的最大公约数(用于简化宽高比)
|
||||
* @param a 第一个数(宽)
|
||||
* @param b 第二个数(高)
|
||||
* @return 最大公约数
|
||||
*/
|
||||
private int calculateGCD(int a, int b) {
|
||||
if (b == 0) {
|
||||
return a;
|
||||
}
|
||||
return calculateGCD(b, a % b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:生成Content Uri(适配Android 7.0+,多包名兼容)
|
||||
* 工具方法:生成Content Uri(适配Android 7.0+,多包名兼容,适配缓存目录文件)
|
||||
*/
|
||||
private Uri getUriForFile(Context context, File file) throws Exception {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
try {
|
||||
// 核心:使用BuildConfig.APPLICATION_ID + ".fileprovider",与Manifest一致,适配多包名
|
||||
//.N 核心:使用BuildConfig.APPLICATION_ID + ".fileprovider",与Manifest一致,适配多包名
|
||||
Uri uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||
file
|
||||
);
|
||||
// 增强:授予所有应用临时读写权限(适配多包名场景下的第三方裁剪工具/相机)
|
||||
// 增强:授予所有应用临时读写权限(适配缓存目录文件+第三方裁剪工具/相机)
|
||||
context.grantUriPermission(
|
||||
"*", // 临时授权所有应用,退出后失效,安全性无影响
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
);
|
||||
LogUtils.d(TAG, "【FileProvider】Uri生成成功:包名=" + BuildConfig.APPLICATION_ID + ",文件路径=" + file.getPath() + ",Uri=" + uri.toString());
|
||||
LogUtils.d(TAG, "【FileProvider】Uri生成成功:包名=" + BuildConfig.APPLICATION_ID + ",文件路径=" + file.getPath() + "(是否缓存目录:" + file.getAbsolutePath().contains(getCacheDir().getAbsolutePath()) + "),Uri=" + uri.toString());
|
||||
return uri;
|
||||
} catch (Exception e) {
|
||||
// 打印详细错误信息,便于多包名/FileProvider配置问题排查
|
||||
// 打印详细错误信息,便于多包名/FileProvider/缓存目录权限问题排查
|
||||
String errMsg = "FileProvider生成Uri失败:包名=" + BuildConfig.APPLICATION_ID
|
||||
+ ",文件路径=" + file.getPath() + ",错误信息:" + e.getMessage();
|
||||
+ ",文件路径=" + file.getPath() + "(是否缓存目录:" + file.getAbsolutePath().contains(getCacheDir().getAbsolutePath()) + "),错误信息:" + e.getMessage();
|
||||
LogUtils.e(TAG, errMsg);
|
||||
throw new Exception(errMsg); // 抛出异常,让上层处理
|
||||
}
|
||||
} else {
|
||||
// Android 7.0以下,直接使用Uri.fromFile(兼容旧机型)
|
||||
// Android 7.0以下,直接使用Uri.fromFile(兼容旧机型,缓存目录文件同样支持)
|
||||
Uri uri = Uri.fromFile(file);
|
||||
LogUtils.d(TAG, "【兼容旧机型】Android7.0以下,直接生成Uri:" + uri.toString() + ",文件路径:" + file.getPath());
|
||||
LogUtils.d(TAG, "【兼容旧机型】Android7.0以下,直接生成Uri:" + uri.toString() + ",文件路径:" + file.getPath() + "(是否缓存目录:" + file.getAbsolutePath().contains(getCacheDir().getAbsolutePath()) + ")");
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存剪裁后的Bitmap(彻底修复:路径由BackgroundSourceUtils统一管理,解决scaledCompressFilePath无法识别问题)
|
||||
* 保存剪裁后的Bitmap(彻底修复:路径由BackgroundSourceUtils统一管理,适配缓存目录临时文件)
|
||||
*/
|
||||
private void saveCropBitmap(Bitmap bitmap) {
|
||||
LogUtils.d(TAG, "【保存启动】saveCropBitmap 触发,开始处理裁剪图片");
|
||||
LogUtils.d(TAG, "【保存启动】saveCropBitmap 触发,开始处理裁剪图片(临时文件位于缓存目录)");
|
||||
if (bitmap == null || bitmap.isRecycled()) {
|
||||
ToastUtils.show("裁剪图片为空,请重新裁剪"); // 优化吐司,简洁明确
|
||||
LogUtils.e(TAG, "【保存失败】裁剪图片为空或已回收,无法保存");
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
boolean deleteSuccess = _mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【临时文件清理】裁剪图片无效,临时文件清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
LogUtils.d(TAG, "【临时文件清理】裁剪图片无效,缓存目录临时文件清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -695,7 +705,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
|
||||
String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath(); // 统一路径:工具类拼接(背景目录+预览Bean压缩文件名)
|
||||
File fScaledCompressBitmapFile = new File(scaledCompressFilePath); // 基于工具类路径创建文件对象
|
||||
LogUtils.d(TAG, "【保存准备】通过BackgroundSourceUtils获取统一保存路径:" + scaledCompressFilePath);
|
||||
LogUtils.d(TAG, "【保存准备】通过BackgroundSourceUtils获取统一保存路径:" + scaledCompressFilePath + ",裁剪临时文件(缓存目录):" + _mSourceCropTempFile.getAbsolutePath());
|
||||
|
||||
// 确保保存目录存在(避免路径无效导致保存失败)
|
||||
File parentDir = fScaledCompressBitmapFile.getParentFile();
|
||||
@@ -737,8 +747,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
// 核心修复3:根据工具类路径中的文件名自动适配压缩格式(兼容JPEG/PNG)
|
||||
String fileName = fScaledCompressBitmapFile.getName(); // 从工具类管理的路径中获取文件名
|
||||
Bitmap.CompressFormat compressFormat = fileName.endsWith(".png")
|
||||
? Bitmap.CompressFormat.PNG
|
||||
: Bitmap.CompressFormat.JPEG;
|
||||
? Bitmap.CompressFormat.PNG
|
||||
: Bitmap.CompressFormat.JPEG;
|
||||
LogUtils.d(TAG, "【保存配置】压缩格式:" + compressFormat + ",压缩质量:80%");
|
||||
// 压缩保存(80%质量,平衡清晰度和文件大小)
|
||||
boolean success = scaledBitmap.compress(compressFormat, 80, fos);
|
||||
@@ -759,10 +769,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
utils.saveFileToPreviewBean(fScaledCompressBitmapFile, scaledCompressFilePath);
|
||||
LogUtils.d(TAG, "【文件同步】已调用saveFileToPreviewBean,同步裁剪图到预览目录(路径由工具类统一管理)");
|
||||
|
||||
// 保存成功后清理裁剪临时文件(避免占用存储空间)
|
||||
// 保存成功后清理缓存目录下的裁剪临时文件(避免占用缓存空间)
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
boolean deleteSuccess = _mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【临时文件清理】裁剪成功,临时文件清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
LogUtils.d(TAG, "【临时文件清理】裁剪成功,缓存目录临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
// 刷新预览视图(双重保障,确保裁剪图实时显示)
|
||||
@@ -783,10 +793,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
previewBean.setIsUseScaledCompress(false);
|
||||
utils.saveSettings(); // 回滚配置
|
||||
LogUtils.d(TAG, "【配置回滚】保存失败,预览Bean禁用压缩图");
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
boolean deleteSuccess = _mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【临时文件清理】裁剪失败,临时文件清理:" + (deleteSuccess ? "成功" : "失败"));
|
||||
LogUtils.d(TAG, "【临时文件清理】裁剪失败,缓存目录临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getAbsolutePath());
|
||||
}
|
||||
// 刷新预览,显示最新状态
|
||||
bvPreviewBackground.reloadPreviewBackground();
|
||||
@@ -795,7 +805,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
String errMsg = "文件未找到:" + e.getMessage() + ",保存路径:" + scaledCompressFilePath;
|
||||
LogUtils.e(TAG, "【保存异常-文件未找到】" + errMsg);
|
||||
ToastUtils.show("保存时发生错误:" + errMsg);
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
}
|
||||
@@ -803,7 +813,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
String errMsg = "写入失败:" + e.getMessage() + "(可能是权限不足)";
|
||||
LogUtils.e(TAG, "【保存异常-IO错误】" + errMsg);
|
||||
ToastUtils.show("保存时发生错误:" + errMsg);
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
}
|
||||
@@ -811,7 +821,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
String errMsg = "未知错误:" + e.getMessage();
|
||||
LogUtils.e(TAG, "【保存异常-未知错误】" + errMsg, e);
|
||||
ToastUtils.show("保存时发生错误:" + errMsg);
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
}
|
||||
@@ -868,22 +878,35 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
return;
|
||||
}
|
||||
|
||||
// 适配Android7.0+ 分享Uri(多包名兼容)
|
||||
// 适配Android7.0+ 分享Uri(多包名兼容,支持缓存目录/私有目录文件)
|
||||
try {
|
||||
Uri uri = getUriForFile(this, fRecivedPicture);
|
||||
LogUtils.d(TAG, "【分享Uri】分享Uri生成成功:" + uri.toString());
|
||||
LogUtils.d(TAG, "【分享Uri】分享Uri生成成功:" + uri.toString() + ",文件是否来自缓存目录:" + fRecivedPicture.getAbsolutePath().contains(getCacheDir().getAbsolutePath()));
|
||||
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
shareIntent.setType("image/" + _mszCommonFileType);
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予接收方读取权限
|
||||
startActivity(Intent.createChooser(shareIntent, "分享图片"));
|
||||
LogUtils.d(TAG, "【分享启动】分享意图已启动,等待用户选择分享方式");
|
||||
// 授予分享目标应用读取权限(适配缓存目录文件权限)
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// 兼容部分机型分享无响应,添加类别和flags
|
||||
shareIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
// 启动分享选择器,避免直接启动无响应
|
||||
Intent chooser = Intent.createChooser(shareIntent, "选择分享方式");
|
||||
chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 传递权限给选择器
|
||||
if (chooser.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(chooser);
|
||||
LogUtils.d(TAG, "【分享启动】分享意图已启动,等待用户选择分享方式");
|
||||
} else {
|
||||
ToastUtils.show("无可用分享应用");
|
||||
LogUtils.e(TAG, "【分享失败】未找到可响应分享的应用");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String errMsg = "分享异常:" + e.getMessage();
|
||||
ToastUtils.show(errMsg.substring(0, 20)); // 截取前20字,避免吐司过长
|
||||
LogUtils.d(TAG, errMsg, Thread.currentThread().getStackTrace());
|
||||
LogUtils.e(TAG, "【分享失败】分享意图启动失败,FileProvider配置异常");
|
||||
LogUtils.e(TAG, "【分享失败】分享意图启动失败,FileProvider配置或权限异常");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,15 +942,20 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
if (!TextUtils.isEmpty(filePath)) {
|
||||
fSrcImage = new File(filePath);
|
||||
} else {
|
||||
// Uri解析失败,通过流复制生成临时文件(兜底方案)
|
||||
fSrcImage = new File(mfPictureDir, "selected_temp.jpg");
|
||||
// Uri解析失败,通过流复制生成临时文件(兜底方案,生成到应用缓存目录)
|
||||
File cacheTempDir = new File(getCacheDir(), "SelectTemp");
|
||||
if (!cacheTempDir.exists()) {
|
||||
cacheTempDir.mkdirs();
|
||||
LogUtils.d(TAG, "【选图解析】创建缓存目录临时子目录:" + cacheTempDir.getAbsolutePath());
|
||||
}
|
||||
fSrcImage = new File(cacheTempDir, "selected_temp.jpg");
|
||||
if (fSrcImage.exists()) {
|
||||
fSrcImage.delete();
|
||||
LogUtils.d(TAG, "【选图解析】旧临时文件已清理,准备生成新临时文件");
|
||||
LogUtils.d(TAG, "【选图解析】旧缓存临时文件已清理,准备生成新临时文件");
|
||||
}
|
||||
// 流复制生成临时文件(适配ContentProvider Uri)
|
||||
// 流复制生成临时文件(适配ContentProvider Uri,无需外部存储权限)
|
||||
FileUtils.copyStreamToFile(getContentResolver().openInputStream(selectedImage), fSrcImage);
|
||||
LogUtils.d(TAG, "【选图解析】Uri解析失败,通过流复制生成临时文件:" + fSrcImage.getPath());
|
||||
LogUtils.d(TAG, "【选图解析】Uri解析失败,通过流复制生成缓存临时文件:" + fSrcImage.getPath());
|
||||
}
|
||||
|
||||
// 校验解析后的图片文件有效性
|
||||
@@ -949,10 +977,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
String errMsg = "选择图片异常:" + e.getMessage();
|
||||
LogUtils.e(TAG, errMsg, e);
|
||||
ToastUtils.show("选择图片失败:" + errMsg.substring(0, 20));
|
||||
// 异常时清理裁剪临时文件
|
||||
// 异常时清理缓存目录下的裁剪临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
boolean deleteSuccess = _mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【选图异常清理】裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath());
|
||||
LogUtils.d(TAG, "【选图异常清理】缓存目录裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -964,10 +992,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
|
||||
ToastUtils.show("拍照文件不存在或损坏");
|
||||
LogUtils.e(TAG, "【拍照回调失败】拍照文件无效,可能是相机未正常保存");
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的裁剪临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【拍照异常清理】拍照文件无效,清理裁剪临时文件");
|
||||
LogUtils.d(TAG, "【拍照异常清理】拍照文件无效,清理缓存目录裁剪临时文件");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -989,31 +1017,31 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
} else {
|
||||
ToastUtils.show("拍照图片为空");
|
||||
LogUtils.e(TAG, "【拍照回调失败】拍照Bitmap为空或已回收");
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的裁剪临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【拍照异常清理】拍照图片为空,清理裁剪临时文件:" + _mSourceCropTempFile.getPath());
|
||||
LogUtils.d(TAG, "【拍照异常清理】拍照图片为空,清理缓存目录裁剪临时文件:" + _mSourceCropTempFile.getPath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ToastUtils.show("拍照数据获取失败");
|
||||
LogUtils.e(TAG, "【拍照回调失败】拍照数据Bundle为空,无法获取图片");
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的裁剪临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【拍照异常清理】拍照数据获取失败,清理裁剪临时文件:" + _mSourceCropTempFile.getPath());
|
||||
LogUtils.d(TAG, "【拍照异常清理】拍照数据获取失败,清理缓存目录裁剪临时文件:" + _mSourceCropTempFile.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理裁剪回调(REQUEST_CROP_IMAGE)
|
||||
else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
|
||||
LogUtils.d(TAG, "【裁剪回调】CROP_IMAGE_REQUEST_CODE 回调触发,开始处理裁剪结果");
|
||||
LogUtils.d(TAG, "【裁剪回调】CROP_IMAGE_REQUEST_CODE 回调触发,开始处理裁剪结果(临时文件位于缓存目录)");
|
||||
try {
|
||||
Bitmap cropBitmap = null;
|
||||
// 核心修复:优先读取裁剪临时文件(放弃data.getParcelableExtra,避免缩略图/空Bitmap)
|
||||
LogUtils.d(TAG, "【裁剪回调】裁剪临时文件校验:路径=" + _mSourceCropTempFile.getPath() + ",是否存在=" + _mSourceCropTempFile.exists() + ",文件大小=" + _mSourceCropTempFile.length() + " bytes");
|
||||
// 核心修复:优先读取缓存目录下的裁剪临时文件(放弃data.getParcelableExtra,避免缩略图/空Bitmap)
|
||||
LogUtils.d(TAG, "【裁剪回调】裁剪临时文件(缓存目录)校验:路径=" + _mSourceCropTempFile.getPath() + ",是否存在=" + _mSourceCropTempFile.exists() + ",文件大小=" + _mSourceCropTempFile.length() + " bytes");
|
||||
if (_mSourceCropTempFile.exists() && _mSourceCropTempFile.length() > 0) {
|
||||
LogUtils.d(TAG, "【裁剪回调】裁剪临时文件有效,开始解析Bitmap");
|
||||
LogUtils.d(TAG, "【裁剪回调】缓存目录裁剪临时文件有效,开始解析Bitmap");
|
||||
// 核心修复:优化Bitmap解析选项(自动适配格式+防止OOM+避免损坏图片解析失败)
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
// 第一步:仅获取图片信息,不加载Bitmap(避免OOM,尤其是大图片)
|
||||
@@ -1024,8 +1052,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
// 自动适配图片格式(PNG用ARGB_8888保留透明,JPEG用RGB_565省内存)
|
||||
String imageMimeType = options.outMimeType;
|
||||
options.inPreferredConfig = (imageMimeType != null && imageMimeType.contains("png"))
|
||||
? Bitmap.Config.ARGB_8888
|
||||
: Bitmap.Config.RGB_565;
|
||||
? Bitmap.Config.ARGB_8888
|
||||
: Bitmap.Config.RGB_565;
|
||||
LogUtils.d(TAG, "【Bitmap解析】自动适配配置:" + options.inPreferredConfig);
|
||||
|
||||
// 自动计算采样率(防止大图片OOM,最大边长限制为2048)
|
||||
@@ -1043,7 +1071,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
LogUtils.d(TAG, "【Bitmap解析】裁剪Bitmap加载:" + (cropBitmap != null ? "成功" : "失败") + ",加载后大小:" + (cropBitmap != null ? cropBitmap.getByteCount()/1024 + "KB" : "0"));
|
||||
} else {
|
||||
ToastUtils.show("剪裁文件为空或损坏");
|
||||
LogUtils.e(TAG, "【裁剪回调失败】裁剪临时文件无效,无法解析");
|
||||
LogUtils.e(TAG, "【裁剪回调失败】缓存目录裁剪临时文件无效,无法解析");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1062,39 +1090,39 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
} else {
|
||||
ToastUtils.show("获取剪裁图片失败(Bitmap解析异常)");
|
||||
LogUtils.e(TAG, "【裁剪回调失败】裁剪Bitmap解析失败或已回收");
|
||||
// 清理无效临时文件
|
||||
// 清理缓存目录下的无效临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【裁剪异常清理】Bitmap解析失败,清理无效临时文件");
|
||||
LogUtils.d(TAG, "【裁剪异常清理】Bitmap解析失败,清理缓存目录无效临时文件");
|
||||
}
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
LogUtils.e(TAG, "【裁剪异常-OOM】内存溢出:" + e.getMessage());
|
||||
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【裁剪异常-未知】剪裁保存异常:" + e.getMessage(), e);
|
||||
ToastUtils.show("保存时发生错误:" + e.getMessage().substring(0, 20));
|
||||
// 清理临时文件
|
||||
// 清理缓存目录下的临时文件
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
}
|
||||
} finally {
|
||||
// 核心修复:移除临时文件清理代码(已移至saveCropBitmap成功后清理,避免提前删除导致保存失败)
|
||||
LogUtils.d(TAG, "【裁剪回调】裁剪流程结束(临时文件清理由saveCropBitmap负责)");
|
||||
LogUtils.d(TAG, "【裁剪回调】裁剪流程结束(缓存目录临时文件清理由saveCropBitmap负责)");
|
||||
}
|
||||
}
|
||||
// 处理操作取消/失败(resultCode != RESULT_OK)
|
||||
else if (resultCode != RESULT_OK) {
|
||||
LogUtils.d(TAG, "【操作回调】操作取消或失败,requestCode: " + requestCode + ",resultCode: " + resultCode);
|
||||
ToastUtils.show("操作已取消");
|
||||
// 操作取消/失败时,强制清理裁剪临时文件(避免占用空间+下次操作异常)
|
||||
// 操作取消/失败时,强制清理缓存目录下的裁剪临时文件(避免占用缓存空间+下次操作异常)
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
boolean deleteSuccess = _mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【操作取消清理】操作取消/失败,裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath());
|
||||
LogUtils.d(TAG, "【操作取消清理】操作取消/失败,缓存目录裁剪临时文件清理:" + (deleteSuccess ? "成功" : "失败") + ",路径:" + _mSourceCropTempFile.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1116,39 +1144,43 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并申请存储权限(修复:适配低版本API,移除Android13+依赖,多机型兼容)
|
||||
* 检查并申请存储权限(修复:适配低版本API,移除Android13+依赖,多机型兼容,适配缓存目录权限)
|
||||
*/
|
||||
private boolean checkAndRequestStoragePermission() {
|
||||
LogUtils.d(TAG, "【权限校验】checkAndRequestStoragePermission 触发,Android版本:" + Build.VERSION.SDK_INT);
|
||||
// Android 11+(R):使用所有文件访问权限
|
||||
// 核心优化:应用缓存目录(getCacheDir())无需外部存储权限,直接返回true
|
||||
LogUtils.d(TAG, "【权限校验】应用缓存目录操作无需外部存储权限,跳过权限申请");
|
||||
|
||||
// Android 11+(R):使用所有文件访问权限(仅针对外部存储操作,缓存目录无需)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
boolean hasPermission = Environment.isExternalStorageManager();
|
||||
LogUtils.d(TAG, "【权限校验】Android11+ 所有文件访问权限:" + (hasPermission ? "已获取" : "未获取"));
|
||||
LogUtils.d(TAG, "【权限校验】Android11+ 所有文件访问权限:" + (hasPermission ? "已获取" : "未获取") + "(仅影响外部存储操作)");
|
||||
if (!hasPermission) {
|
||||
// 仅在需要操作外部存储时提示授权(缓存目录操作不受影响)
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
|
||||
startActivity(intent);
|
||||
ToastUtils.show("请开启「所有文件访问权限」");
|
||||
ToastUtils.show("请开启「所有文件访问权限」(仅用于外部图片选择/保存)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Android 6.0+(M):申请读写外部存储权限
|
||||
// Android 6.0+(M):申请读写外部存储权限(仅针对外部存储操作)
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
boolean hasReadPerm = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
boolean hasWritePerm = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
LogUtils.d(TAG, "【权限校验】Android6.0+ 存储权限:读=" + hasReadPerm + ",写=" + hasWritePerm);
|
||||
LogUtils.d(TAG, "【权限校验】Android6.0+ 存储权限:读=" + hasReadPerm + ",写=" + hasWritePerm + "(仅影响外部存储操作)");
|
||||
if (!hasReadPerm || !hasWritePerm) {
|
||||
// 同时申请读写权限(避免只申请一个导致功能异常)
|
||||
// 同时申请读写权限(仅用于外部存储操作,缓存目录无需)
|
||||
String[] permissions = new String[]{
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
};
|
||||
ActivityCompat.requestPermissions(this, permissions, STORAGE_PERMISSION_REQUEST);
|
||||
LogUtils.d(TAG, "【权限申请】已触发存储权限申请,请求码:" + STORAGE_PERMISSION_REQUEST);
|
||||
LogUtils.d(TAG, "【权限申请】已触发存储权限申请(仅用于外部图片操作),请求码:" + STORAGE_PERMISSION_REQUEST);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Android 6.0以下:权限默认授予
|
||||
LogUtils.d(TAG, "【权限校验】存储权限已获取,可正常操作");
|
||||
// Android 6.0以下:权限默认授予(缓存目录/外部存储均无需额外操作)
|
||||
LogUtils.d(TAG, "【权限校验】存储权限校验完成(缓存目录操作不受限),可正常操作");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1156,14 +1188,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
LogUtils.d(TAG, "【权限回调】onRequestPermissionsResult 触发,requestCode:" + requestCode);
|
||||
// 处理存储权限申请结果
|
||||
// 处理存储权限申请结果(仅针对外部存储权限,缓存目录无需)
|
||||
if (requestCode == STORAGE_PERMISSION_REQUEST) {
|
||||
boolean isGranted = false;
|
||||
// 校验所有申请的权限是否都通过(读写权限需同时授予)
|
||||
for (int i = 0; i < grantResults.length; i++) {
|
||||
String perm = permissions[i];
|
||||
int result = grantResults[i];
|
||||
LogUtils.d(TAG, "【权限回调】权限:" + perm + ",申请结果:" + (result == PackageManager.PERMISSION_GRANTED ? "通过" : "拒绝"));
|
||||
LogUtils.d(TAG, "【权限回调】权限:" + perm + ",申请结果:" + (result == PackageManager.PERMISSION_GRANTED ? "通过" : "拒绝") + "(仅影响外部存储操作)");
|
||||
if (result == PackageManager.PERMISSION_GRANTED) {
|
||||
isGranted = true;
|
||||
} else {
|
||||
@@ -1174,12 +1206,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
if (isGranted) {
|
||||
ToastUtils.show("存储权限已获取,正在打开图片选择器");
|
||||
LogUtils.d(TAG, "【权限回调】存储权限全部通过,自动重试图片选择");
|
||||
LogUtils.d(TAG, "【权限回调】存储权限全部通过(外部存储可用),自动重试图片选择");
|
||||
// 核心优化:自动重试图片选择(无需用户再次点击按钮)
|
||||
onSelectPictureClickListener.onClick(findViewById(R.id.activitybackgroundpictureAButton2));
|
||||
} else {
|
||||
ToastUtils.show("需要存储权限才能保存/选择图片");
|
||||
LogUtils.e(TAG, "【权限回调】存储权限申请被拒绝,无法正常使用图片功能");
|
||||
ToastUtils.show("需要存储权限才能选择/保存外部图片(缓存目录操作不受影响)");
|
||||
LogUtils.e(TAG, "【权限回调】存储权限申请被拒绝,外部图片功能受限(缓存目录操作正常)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1255,7 +1287,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
void onRecivedPicture(String srcFilePath, String srcFileUrl);
|
||||
}
|
||||
|
||||
// 图片接收监听器实现(网络图片下载/分享图片后的后续处理)
|
||||
// 图片接收监听器实现(网络图片下载/分享图片后的后续处理,适配缓存目录)
|
||||
OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){
|
||||
@Override
|
||||
public void onRecivedPicture(String srcFilePath, String srcFileUrl) {
|
||||
@@ -1268,7 +1300,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
}
|
||||
// 校验文件是否存在且有效
|
||||
File srcFile = new File(srcFilePath);
|
||||
LogUtils.d(TAG, "【图片接收校验】图片文件:路径=" + srcFile.getAbsolutePath() + ",是否存在=" + srcFile.exists() + ",文件大小=" + srcFile.length() + " bytes");
|
||||
LogUtils.d(TAG, "【图片接收校验】图片文件:路径=" + srcFile.getAbsolutePath() + ",是否存在=" + srcFile.exists() + ",文件大小=" + srcFile.length() + " bytes,是否缓存目录:" + srcFile.getAbsolutePath().contains(getCacheDir().getAbsolutePath()));
|
||||
if (!srcFile.exists() || srcFile.length() <= 0) {
|
||||
ToastUtils.show("网络图片文件不存在或损坏");
|
||||
LogUtils.e(TAG, "【图片接收失败】图片文件无效,无法加载");
|
||||
@@ -1280,9 +1312,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
LogUtils.d(TAG, "【图片接收】图片已同步到预览Bean,刷新预览视图");
|
||||
// 修复:网络图片下载后刷新预览(确保图片正常显示)
|
||||
bvPreviewBackground.reloadPreviewBackground();
|
||||
// 启动自由裁剪(网络图片适配自由比例调整)
|
||||
// 启动自由裁剪(网络图片适配自由比例调整,裁剪临时文件存入缓存目录)
|
||||
startCropImageActivity(true);
|
||||
LogUtils.d(TAG, "【图片接收】已启动自由裁剪,等待用户调整图片比例");
|
||||
LogUtils.d(TAG, "【图片接收】已启动自由裁剪,临时文件将存入应用缓存目录");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1308,7 +1340,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
currentBean.setIsUseBackgroundFile(!TextUtils.isEmpty(preViewFilePath));
|
||||
utils.saveSettings(); // 持久化原配置(仅更新启用状态,不修改图片路径)
|
||||
LogUtils.d(TAG, "【退出配置】原背景配置保存完成,正式背景启用状态:" + currentBean.isUseBackgroundFile());
|
||||
finish(); // 执行真正的退出
|
||||
// 退出前清理缓存目录下的所有临时文件(释放缓存空间)
|
||||
clearCacheDirTempFiles();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1320,13 +1354,60 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
||||
isCommitSettings = true; // 标记为已提交
|
||||
LogUtils.d(TAG, "【退出配置】预览背景提交完成,正式背景已更新");
|
||||
ToastUtils.show("背景图片应用成功"); // 补充用户反馈,明确操作结果
|
||||
finish(); // 执行真正的退出
|
||||
// 退出前清理缓存目录下的所有临时文件(释放缓存空间)
|
||||
clearCacheDirTempFiles();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 已提交配置(或用户已选择弹窗选项),直接执行退出,避免循环
|
||||
LogUtils.d(TAG, "【生命周期】已提交配置,执行super.finish()正常退出");
|
||||
// 退出前清理缓存目录下的所有临时文件(释放缓存空间)
|
||||
clearCacheDirTempFiles();
|
||||
super.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理应用缓存目录下的所有临时文件(裁剪/选择图片产生的临时文件)
|
||||
*/
|
||||
private void clearCacheDirTempFiles() {
|
||||
LogUtils.d(TAG, "【缓存清理】clearCacheDirTempFiles 触发,清理应用缓存目录下的临时文件");
|
||||
// 清理裁剪临时目录
|
||||
File cropTempDir = new File(getCacheDir(), "CropTemp");
|
||||
deleteDirFiles(cropTempDir);
|
||||
// 清理选择图片临时目录
|
||||
File selectTempDir = new File(getCacheDir(), "SelectTemp");
|
||||
deleteDirFiles(selectTempDir);
|
||||
// 清理单独的裁剪临时文件(兼容可能的零散文件)
|
||||
if (_mSourceCropTempFile.exists()) {
|
||||
_mSourceCropTempFile.delete();
|
||||
LogUtils.d(TAG, "【缓存清理】单独裁剪临时文件清理完成:" + _mSourceCropTempFile.getAbsolutePath());
|
||||
}
|
||||
LogUtils.d(TAG, "【缓存清理】应用缓存目录临时文件清理完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归删除目录下的所有文件(保留目录本身,避免误删目录)
|
||||
* @param dir 要清理的目录
|
||||
*/
|
||||
private void deleteDirFiles(File dir) {
|
||||
if (dir == null || !dir.exists() || !dir.isDirectory()) {
|
||||
LogUtils.d(TAG, "【缓存清理】目录无效,无需清理:" + (dir != null ? dir.getAbsolutePath() : "null"));
|
||||
return;
|
||||
}
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
LogUtils.d(TAG, "【缓存清理】目录为空,无需清理:" + dir.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isFile()) {
|
||||
boolean deleteSuccess = file.delete();
|
||||
LogUtils.d(TAG, "【缓存清理】删除缓存文件:" + file.getAbsolutePath() + ",结果:" + (deleteSuccess ? "成功" : "失败"));
|
||||
} else if (file.isDirectory()) {
|
||||
deleteDirFiles(file); // 递归清理子目录
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user