20251201_042831_062

This commit is contained in:
2025-12-01 04:29:06 +08:00
parent 66e3e602e5
commit 80363c6b4c
2 changed files with 350 additions and 269 deletions

View File

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

View File

@@ -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); // 递归清理子目录
}
}
}
}