20251201_011410_083

This commit is contained in:
2025-12-01 01:14:14 +08:00
parent e408b5cbde
commit 21c712f7b3
4 changed files with 488 additions and 292 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Nov 30 16:39:04 GMT 2025
#Sun Nov 30 17:13:20 GMT 2025
stageCount=13
libraryProject=
baseVersion=15.11
publishVersion=15.11.12
buildCount=4
buildCount=7
baseBetaVersion=15.11.13

View File

@@ -16,6 +16,7 @@ import android.view.View;
import android.widget.RelativeLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.LogUtils;
@@ -48,9 +49,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
public static final int REQUEST_CROP_IMAGE = 2;
private static final int STORAGE_PERMISSION_REQUEST = 100;
// FileProvider 授权必须与AndroidManifest.xml中配置一致
private static final String FILE_PROVIDER_AUTHORITY = "cc.winboll.studio.powerbell.fileprovider";
private AToolbar mAToolbar;
private File mfBackgroundDir; // 背景图片存储文件夹
private File mfPictureDir; // 拍照与剪裁临时文件夹
private File mfPictureDir; // 拍照与剪裁临时文件夹(权限友好)
private File mfTakePhoto; // 拍照文件
// 背景视图预览图片的文件名
@@ -59,16 +63,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
BackgroundView bvPreviewBackground;
boolean isCommitSettings = false;
// 静态变量
// 源文件的临时剪裁图片保存名称
// 静态变量(裁剪临时文件迁移到临时目录,解决权限冲突)
private static String _mSourceCropTempFileName = "SourceCropTemp.jpg";
// 源文件的临时剪裁图片保存文件对象
private static File _mSourceCropTempFile;
// 源文件的剪裁图片保存名称
private static File _mSourceCropTempFile; // 存储在mfPictureDir临时目录
private static String _mSourceCroppedFileName = "SourceCropped.jpg";
// 源文件的剪裁图片保存文件对象
private static File _mSourceCroppedFile;
// 源文件的剪裁图片保存路径
private static String _mSourceCroppedFilePath;
private static String _mszCommonFileType = "jpeg";
private int mnPictureCompress = 100;
@@ -101,12 +100,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
mfPictureDir.mkdirs();
}
// 初始化文件对象
// 初始化文件对象核心修复1裁剪临时文件迁移到临时目录避免私有目录权限冲突
mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg");
_mSourceCropTempFile = new File(mfBackgroundDir, _mSourceCropTempFileName);
_mSourceCropTempFile = new File(mfPictureDir, _mSourceCropTempFileName); // 迁移到mfPictureDir
_mSourceCroppedFile = new File(mfBackgroundDir, _mSourceCroppedFileName);
_mSourceCroppedFilePath = _mSourceCroppedFile.getAbsolutePath().toString();
LogUtils.d(TAG, String.format("_mSourceCroppedFilePath : %s", _mSourceCroppedFilePath));
LogUtils.d(TAG, String.format("裁剪临时文件路径 : %s", _mSourceCropTempFile.getAbsolutePath()));
// 初始化工具栏
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
@@ -235,8 +235,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}
if (checkAndRequestStoragePermission()) {
// 适配Android 7.0+ 拍照Uri
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
try {
Uri photoUri = getUriForFile(BackgroundSettingsActivity.this, mfTakePhoto);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
} catch (Exception e) {
ToastUtils.show(String.format("sharePicture() Exception : %s", e));
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
};
@@ -277,7 +285,15 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
OutputStream outStream = null;
try {
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
File fRecivedPicture = new File(utils.getPreviewBackgroundScaledCompressFilePath());
String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath();
File fRecivedPicture = new File(scaledCompressFilePath); // 直接使用完整路径,避免拼接错误
// 确保父目录存在
File parentDir = fRecivedPicture.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
if (!fRecivedPicture.exists()) {
fRecivedPicture.createNewFile();
}
@@ -303,74 +319,106 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}
}
/**
* 启动图片裁剪活动(核心修复:传递BackgroundView实际宽高比例替代Bean默认值
* @param isCropFree 是否自由裁剪
*/
public void startCropImageActivity(boolean isCropFree) {
LogUtils.d(TAG, "startCropImageActivity");
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
BackgroundBean bean = utils.getPreviewBackgroundBean();
bean.setIsUseScaledCompress(true);
utils.saveSettings();
/**
* 启动图片裁剪活动(修复:FileProvider适配+意图兼容+异常捕获
* @param isCropFree 是否自由裁剪
*/
public void startCropImageActivity(boolean isCropFree) {
LogUtils.d(TAG, "startCropImageActivity");
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
BackgroundBean bean = utils.getPreviewBackgroundBean();
bean.setIsUseScaledCompress(true);
utils.saveSettings();
File fRecivedPicture = new File(utils.getPreviewBackgroundFilePath());
File fRecivedPicture = new File(utils.getPreviewBackgroundFilePath());
if (!fRecivedPicture.exists() || fRecivedPicture.length() <= 0) {
ToastUtils.show("预览图片不存在或损坏");
return;
}
Uri uri = UriUtil.getUriForFile(this, fRecivedPicture);
LogUtils.d(TAG, "uri : " + uri.toString());
// 核心修复1捕获Uri生成异常避免FileProvider配置错误导致崩溃
Uri inputUri = null;
Uri cropOutPutUri = null;
try {
// 适配Android7.0+用FileProvider生成Content Uri输入/输出)
inputUri = getUriForFile(this, fRecivedPicture);
LogUtils.d(TAG, "裁剪输入Uri : " + inputUri.toString());
} catch (Exception e) {
LogUtils.e(TAG, "生成裁剪输入Uri失败" + e.getMessage());
ToastUtils.show("图片裁剪失败:无法获取图片权限");
return;
}
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
// 清理旧临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
try {
_mSourceCropTempFile.createNewFile();
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
ToastUtils.show("剪裁临时文件创建失败");
return;
}
try {
_mSourceCropTempFile.createNewFile();
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
ToastUtils.show("剪裁临时文件创建失败");
return;
}
Uri cropOutPutUri = Uri.fromFile(_mSourceCropTempFile);
LogUtils.d(TAG, "mfTempCropPicture : " + _mSourceCropTempFile.getPath());
// 生成裁剪输出Uri
try {
cropOutPutUri = getUriForFile(this, _mSourceCropTempFile);
LogUtils.d(TAG, "裁剪输出Uri : " + cropOutPutUri.toString());
} catch (Exception e) {
LogUtils.e(TAG, "生成裁剪输出Uri失败" + e.getMessage());
ToastUtils.show("图片裁剪失败:无法创建临时文件");
return;
}
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/" + _mszCommonFileType);
intent.putExtra("crop", "true");
intent.putExtra("noFaceDetection", true);
// 核心修复2裁剪意图兼容适配不同机型
Intent intent = new Intent("com.android.camera.action.CROP");
// 兼容部分机型不支持隐式意图,添加包名过滤(可选,按需添加)
intent.setPackage("com.android.camera");
intent.setDataAndType(inputUri, "image/" + _mszCommonFileType);
intent.putExtra("crop", "true");
intent.putExtra("noFaceDetection", true);
// 修复核心非自由裁剪时传递bvPreviewBackground控件的实际宽高比例确保裁剪与控件匹配
if (!isCropFree) {
// 1. 优先获取BackgroundView的实际宽高控件已填充父视图宽高=父容器尺寸)
int viewWidth = bvPreviewBackground.getWidth();
int viewHeight = bvPreviewBackground.getHeight();
// 裁剪比例逻辑保持不变...
if (!isCropFree) {
int viewWidth = bvPreviewBackground.getWidth();
int viewHeight = bvPreviewBackground.getHeight();
if (viewWidth <= 0 || viewHeight <= 0) {
viewWidth = getResources().getDisplayMetrics().widthPixels;
viewHeight = getResources().getDisplayMetrics().heightPixels;
LogUtils.d(TAG, "控件未测量完成,使用屏幕尺寸作为裁剪比例:" + viewWidth + "x" + viewHeight);
}
// 2. 容错处理:若控件未测量完成(宽/高为0使用屏幕尺寸兜底
if (viewWidth <= 0 || viewHeight <= 0) {
viewWidth = getResources().getDisplayMetrics().widthPixels;
viewHeight = getResources().getDisplayMetrics().heightPixels;
LogUtils.d(TAG, "控件未测量完成,使用屏幕尺寸作为裁剪比例:" + viewWidth + "x" + viewHeight);
}
int gcd = calculateGCD(viewWidth, viewHeight);
int simplifiedWidth = viewWidth / gcd;
int simplifiedHeight = viewHeight / gcd;
// 3. 计算宽高比的最大公约数简化比例避免过大数值导致裁剪工具不兼容如1080:1920→9:16
int gcd = calculateGCD(viewWidth, viewHeight);
int simplifiedWidth = viewWidth / gcd;
int simplifiedHeight = viewHeight / gcd;
intent.putExtra("aspectX", simplifiedWidth);
intent.putExtra("aspectY", simplifiedHeight);
LogUtils.d(TAG, "裁剪比例(控件实际比例/简化后):" + viewWidth + ":" + viewHeight + "" + simplifiedWidth + ":" + simplifiedHeight);
}
// 4. 传递简化后的宽高比例给裁剪意图(关键:确保裁剪比例与控件完全匹配)
intent.putExtra("aspectX", simplifiedWidth);
intent.putExtra("aspectY", simplifiedHeight);
LogUtils.d(TAG, "裁剪比例(控件实际比例/简化后):" + viewWidth + ":" + viewHeight + "" + simplifiedWidth + ":" + simplifiedHeight);
}
intent.putExtra("return-data", false);
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);
intent.putExtra("return-data", true);
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);
startActivityForResult(intent, REQUEST_CROP_IMAGE);
}
// 核心修复3添加意图启动校验避免启动失败无响应
try {
startActivityForResult(intent, REQUEST_CROP_IMAGE);
} catch (Exception e) {
LogUtils.e(TAG, "启动裁剪窗口失败:" + e.getMessage());
ToastUtils.show("无法启动裁剪工具,请安装系统相机");
// 兼容方案:若系统相机不支持,使用第三方裁剪工具(可选)
Intent chooserIntent = Intent.createChooser(intent, "选择裁剪工具");
if (chooserIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(chooserIntent, REQUEST_CROP_IMAGE);
}
}
}
/**
* 工具方法:计算两个数的最大公约数(用于简化宽高比)
@@ -386,134 +434,159 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}
/**
* 保存剪裁后的Bitmap优化版修复裁剪后加载不到图片问题
* 工具方法生成Content Uri适配Android 7.0+需在AndroidManifest.xml中配置FileProvider
*/
private void saveCropBitmap(Bitmap bitmap) {
if (bitmap == null) {
ToastUtils.show("剪裁图片为空");
// 修复:临时文件异常时也清理
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪图片为空,清理临时文件:" + _mSourceCropTempFile.getPath());
}
return;
}
// 内存优化:大图片自动缩放(保持原逻辑)
Bitmap scaledBitmap = bitmap;
if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB
float scale = 1.0f;
while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) {
scale -= 0.2f; // 每次缩小20%
if (scale < 0.2f) break; // 最小缩放到20%
scaledBitmap = scaleBitmap(scaledBitmap, scale);
}
if (scaledBitmap != bitmap) {
bitmap.recycle(); // 回收原Bitmap
}
}
// 优化:创建保存目录(保持原逻辑)
File backgroundDir = new File(mBackgroundSourceUtils.getBackgroundSourceDirPath());
if (!backgroundDir.exists()) {
if (!backgroundDir.mkdirs()) {
ToastUtils.show("无法创建保存目录");
if (scaledBitmap != bitmap) scaledBitmap.recycle();
return;
}
}
// 剪裁的图片的保存地址(保持原逻辑)
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
String scaledCompressFileName = utils.getPreviewBackgroundScaledCompressFilePath();
File fScaledCompressBitmapFile = new File(scaledCompressFileName);
// 优化:检查文件是否可写(保持原逻辑)
if (fScaledCompressBitmapFile.exists() && !fScaledCompressBitmapFile.canWrite()) {
if (!fScaledCompressBitmapFile.delete()) {
ToastUtils.show("无法删除旧文件");
if (scaledBitmap != bitmap) scaledBitmap.recycle();
return;
}
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(fScaledCompressBitmapFile);
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
fos.flush();
if (success) {
ToastUtils.show("图片压缩保存成功");
BackgroundBean previewBean = utils.getPreviewBackgroundBean();
// 修复1同步裁剪后路径到预览Bean关键确保加载路径匹配
// 从压缩文件路径中提取文件名更新到previewBean的backgroundFileName
String cropFileName = fScaledCompressBitmapFile.getName();
previewBean.setBackgroundFileName(cropFileName); // 重点:更新为裁剪后的压缩文件名
// 修复2强制设置isUseBackgroundFile=true确保BackgroundView加载图片而非透明背景
previewBean.setIsUseBackgroundFile(true);
previewBean.setIsUseScaledCompress(true);
utils.saveSettings(); // 持久化保存Bean确保路径同步
// 修复3裁剪成功后立即清理临时文件避免残留冲突
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪成功,清理临时文件:" + _mSourceCropTempFile.getPath());
}
// 修复:裁剪成功后刷新预览视图(保持原逻辑,此时路径已同步)
bvPreviewBackground.reloadPreviewBackground();
} else {
ToastUtils.show("图片压缩保存失败");
BackgroundBean previewBean = utils.getPreviewBackgroundBean();
previewBean.setIsUseScaledCompress(false);
utils.saveSettings();
// 修复:保存失败时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪失败,清理临时文件:" + _mSourceCropTempFile.getPath());
}
// 修复:保存失败时刷新原始预览图
bvPreviewBackground.reloadPreviewBackground();
}
} catch (FileNotFoundException e) {
LogUtils.e(TAG, "文件未找到" + e);
ToastUtils.show("文件未找到:" + e.getMessage());
// 异常时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
} catch (IOException e) {
LogUtils.e(TAG, "写入异常" + e);
ToastUtils.show("写入异常:" + e.getMessage());
// 异常时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
LogUtils.e(TAG, "流关闭异常" + e);
ToastUtils.show("流关闭异常:" + e.getMessage());
}
}
if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
scaledBitmap.recycle(); // 回收缩放后的Bitmap避免内存泄漏
private Uri getUriForFile(Context context, File file) throws Exception {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
// 与AndroidManifest.xml中配置的FileProvider授权一致
return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file);
} catch (Exception e) {
LogUtils.e(TAG, "FileProvider生成Uri失败" + e.getMessage() + ",文件路径:" + file.getPath());
throw e; // 抛出异常,让上层处理
}
} else {
return Uri.fromFile(file); // 低版本兼容
}
}
/**
* 保存剪裁后的Bitmap彻底修复路径拼接+权限+解析异常)
*/
private void saveCropBitmap(Bitmap bitmap) {
if (bitmap == null || bitmap.isRecycled()) {
ToastUtils.show("剪裁图片为空或已回收");
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪图片为空,清理临时文件:" + _mSourceCropTempFile.getPath());
}
return;
}
// 内存优化:大图片自动缩放(保持原逻辑)
Bitmap scaledBitmap = bitmap;
if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB
float scale = 1.0f;
while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) {
scale -= 0.2f; // 每次缩小20%
if (scale < 0.2f) break; // 最小缩放到20%
scaledBitmap = scaleBitmap(scaledBitmap, scale);
}
if (scaledBitmap != bitmap) {
bitmap.recycle(); // 回收原Bitmap
}
}
// 核心修复1正确获取保存路径直接使用完整路径避免重复拼接
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
String scaledCompressFilePath = utils.getPreviewBackgroundScaledCompressFilePath(); // 完整路径(无嵌套)
File fScaledCompressBitmapFile = new File(scaledCompressFilePath);
// 确保保存目录存在(避免路径无效)
File parentDir = fScaledCompressBitmapFile.getParentFile();
if (!parentDir.exists()) {
if (!parentDir.mkdirs()) {
ToastUtils.show("无法创建保存目录:" + parentDir.getAbsolutePath());
if (scaledBitmap != bitmap) scaledBitmap.recycle();
return;
}
}
// 优化:检查文件权限(确保可写)
if (fScaledCompressBitmapFile.exists()) {
if (!fScaledCompressBitmapFile.canWrite()) {
if (!fScaledCompressBitmapFile.delete()) {
ToastUtils.show("无法删除旧文件(权限不足):" + fScaledCompressBitmapFile.getPath());
if (scaledBitmap != bitmap) scaledBitmap.recycle();
return;
}
}
}
FileOutputStream fos = null;
try {
// 修复2设置文件可写权限避免写入失败
fScaledCompressBitmapFile.setWritable(true, false);
fos = new FileOutputStream(fScaledCompressBitmapFile);
// 压缩保存80%质量,平衡清晰度和大小)
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
fos.flush();
if (success) {
ToastUtils.show("图片压缩保存成功");
BackgroundBean previewBean = utils.getPreviewBackgroundBean();
// 修复3同步裁剪后文件名到预览Bean仅传文件名避免路径污染
String cropFileName = fScaledCompressBitmapFile.getName();
previewBean.setBackgroundFileName(cropFileName);
previewBean.setIsUseBackgroundFile(true); // 强制启用背景图
previewBean.setIsUseScaledCompress(true);
utils.saveSettings(); // 持久化配置
// 清理临时文件(双重保障)
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪成功,清理临时文件:" + _mSourceCropTempFile.getPath());
}
// 刷新预览视图(确保裁剪图实时显示)
bvPreviewBackground.reloadPreviewBackground();
} else {
ToastUtils.show("图片压缩保存失败Bitmap压缩异常");
BackgroundBean previewBean = utils.getPreviewBackgroundBean();
previewBean.setIsUseScaledCompress(false);
utils.saveSettings();
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪失败,清理临时文件:" + _mSourceCropTempFile.getPath());
}
// 刷新原始预览图
bvPreviewBackground.reloadPreviewBackground();
}
} catch (FileNotFoundException e) {
LogUtils.e(TAG, "文件未找到:" + e.getMessage() + ",保存路径:" + fScaledCompressBitmapFile.getPath());
ToastUtils.show("保存失败:文件路径无效");
// 异常时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
} catch (IOException e) {
LogUtils.e(TAG, "写入异常:" + e.getMessage());
ToastUtils.show("保存失败:无写入权限或文件损坏");
// 异常时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
} finally {
// 关闭流(避免资源泄漏)
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
LogUtils.e(TAG, "流关闭异常" + e);
ToastUtils.show("流关闭异常:" + e.getMessage());
}
}
// 回收Bitmap避免内存泄漏
if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
scaledBitmap.recycle();
}
}
}
/**
* 缩放Bitmap
*/
private Bitmap scaleBitmap(Bitmap original, float scale) {
if (original == null) {
if (original == null || original.isRecycled()) {
return null;
}
int width = (int) (original.getWidth() * scale);
int height = (int) (original.getHeight() * scale);
// 确保宽高为正(避免缩放异常)
width = Math.max(width, 1);
height = Math.max(height, 1);
return Bitmap.createScaledBitmap(original, width, height, true);
}
@@ -522,124 +595,195 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
*/
void sharePicture() {
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
File fRecivedPicture = new File(utils.getCurrentBackgroundFilePath());
Uri uri = UriUtil.getUriForFile(this, fRecivedPicture);
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, "Share Image"));
}
String currentBgPath = utils.getCurrentBackgroundFilePath();
File fRecivedPicture = new File(currentBgPath);
if (!fRecivedPicture.exists() || fRecivedPicture.length() <= 0) {
ToastUtils.show("分享的背景图片不存在");
return;
}
// 适配Android7.0+ 分享Uri
try {
Uri uri = getUriForFile(this, fRecivedPicture);
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, "Share Image"));
} catch (Exception e) {
ToastUtils.show(String.format("sharePicture() Exception : %s", e));
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_SELECT_PICTURE && resultCode == RESULT_OK) {
try {
Uri selectedImage = data.getData();
LogUtils.d(TAG, "Uri is : " + selectedImage.toString());
File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage));
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
if (selectedImage == null) {
ToastUtils.show("选择的图片Uri为空");
return;
}
LogUtils.d(TAG, "选择图片Uri : " + selectedImage.toString());
// 修复保存图片到预览Bean后立即刷新BackgroundView显示预览图
// 核心修复1替换路径解析方式兼容UriUtil解析失败场景
File fSrcImage = null;
String filePath = UriUtil.getFilePathFromUri(this, selectedImage);
if (!TextUtils.isEmpty(filePath)) {
fSrcImage = new File(filePath);
} else {
// 兼容方案通过ContentResolver读取图片流保存为临时文件
fSrcImage = new File(mfPictureDir, "selected_temp.jpg");
if (fSrcImage.exists()) {
fSrcImage.delete();
}
// 复制图片流到临时文件
FileUtils.copyStreamToFile(getContentResolver().openInputStream(selectedImage), fSrcImage);
LogUtils.d(TAG, "Uri解析失败通过流复制生成临时文件" + fSrcImage.getPath());
}
// 核心修复2增强文件有效性校验
if (fSrcImage == null || !fSrcImage.exists() || fSrcImage.length() <= 0) {
ToastUtils.show("选择的图片文件不存在或损坏");
return;
}
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
// 保存图片到预览Bean并刷新
utils.saveFileToPreviewBean(fSrcImage, selectedImage.toString());
bvPreviewBackground.reloadPreviewBackground();
// 启动裁剪(保持原逻辑,裁剪比例已修复
// 启动裁剪(增加异常捕获
startCropImageActivity(false);
} catch (Exception e) {
LogUtils.e(TAG, "选择图片异常" + e);
ToastUtils.show("选择图片失败:" + e.getMessage());
// 异常时清理临时文件(避免残留)
// 异常时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "选择图片异常,清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
} else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
Bundle extras = data.getExtras();
if (extras != null) {
Bitmap imageBitmap = (Bitmap) extras.get("data");
if (imageBitmap != null) {
compressQualityToRecivedPicture(imageBitmap);
// 修复:拍照压缩后,刷新预览图
bvPreviewBackground.reloadPreviewBackground();
startCropImageActivity(false);
} else {
ToastUtils.show("拍照图片为空");
// 图片为空时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "拍照图片为空,清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
} else {
ToastUtils.show("拍照数据获取失败");
// 数据获取失败时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "拍照数据获取失败,清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
} else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
try {
Bitmap cropBitmap = null;
// 方案1通过Intent获取剪裁后的Bitmap
if (data != null && data.hasExtra("data")) {
cropBitmap = data.getParcelableExtra("data");
} else if (_mSourceCropTempFile.exists()) {
LogUtils.d(TAG, String.format("_mSourceCropTempFile Exists, Path is %s ", _mSourceCropTempFile.getPath()));
cropBitmap = BitmapFactory.decodeFile(_mSourceCropTempFile.getPath());
} else {
ToastUtils.show("剪裁文件不存在");
return;
}
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
// 检查拍照文件是否有效
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
ToastUtils.show("拍照文件不存在或损坏");
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "拍照文件无效,清理临时文件");
}
return;
}
if (cropBitmap != null) {
saveCropBitmap(cropBitmap); // 调用保存方法(内含预览刷新+临时文件清理)
} else {
ToastUtils.show("获取剪裁图片失败");
}
} catch (OutOfMemoryError e) {
LogUtils.e(TAG, "内存溢出" + e);
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
} catch (Exception e) {
LogUtils.e(TAG, "剪裁保存异常" + e);
ToastUtils.show("保存失败:" + e.getMessage());
} finally {
// 修复核心:裁剪流程结束后,强制清理临时文件(双重保障,避免残留冲突)
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪流程结束,强制清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
} else if (resultCode != RESULT_OK) {
LogUtils.d(TAG, "操作取消或失败requestCode: " + requestCode);
ToastUtils.show("操作已取消");
// 修复:操作取消/失败时,强制清理临时文件(避免影响下次裁剪)
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "操作取消/失败,清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
}
Bundle extras = data.getExtras();
if (extras != null) {
Bitmap imageBitmap = (Bitmap) extras.get("data");
if (imageBitmap != null && !imageBitmap.isRecycled()) {
compressQualityToRecivedPicture(imageBitmap);
// 拍照压缩后刷新预览
bvPreviewBackground.reloadPreviewBackground();
// 启动裁剪
startCropImageActivity(false);
} else {
ToastUtils.show("拍照图片为空");
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "拍照图片为空,清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
} else {
ToastUtils.show("拍照数据获取失败");
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "拍照数据获取失败,清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
} else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
try {
Bitmap cropBitmap = null;
// 核心修复优先读取裁剪临时文件放弃data.getParcelableExtra避免缩略图
if (_mSourceCropTempFile.exists() && _mSourceCropTempFile.length() > 0) {
LogUtils.d(TAG, String.format("_mSourceCropTempFile 信息:路径=%s , 大小=%d bytes",
_mSourceCropTempFile.getPath(), _mSourceCropTempFile.length()));
// 优化Bitmap解析选项避免OOM和损坏图片解析失败
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565; // 省内存仅RGB无透明通道
options.inSampleSize = 1; // 不缩放(保证清晰度)
options.inJustDecodeBounds = false;
cropBitmap = BitmapFactory.decodeFile(_mSourceCropTempFile.getPath(), options);
} else {
ToastUtils.show("剪裁文件为空或损坏");
return;
}
// 检查解析后的Bitmap是否有效
if (cropBitmap != null && !cropBitmap.isRecycled()) {
saveCropBitmap(cropBitmap); // 调用保存方法(内含修复逻辑)
} else {
ToastUtils.show("获取剪裁图片失败Bitmap解析异常");
// 清理无效临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "Bitmap解析失败清理无效临时文件");
}
}
} catch (OutOfMemoryError e) {
LogUtils.e(TAG, "内存溢出" + e);
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
} catch (Exception e) {
LogUtils.e(TAG, "剪裁保存异常" + e);
ToastUtils.show("保存失败:" + e.getMessage());
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
} finally {
// 裁剪流程结束,强制清理临时文件(双重保障,避免残留)
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪流程结束,强制清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
} else if (resultCode != RESULT_OK) {
LogUtils.d(TAG, "操作取消或失败requestCode: " + requestCode);
ToastUtils.show("操作已取消");
// 操作取消/失败时,强制清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "操作取消/失败,清理临时文件:" + _mSourceCropTempFile.getPath());
}
}
}
/**
* 检查类型是否为图片
*/
private boolean isImageType(String type) {
if (TextUtils.isEmpty(type)) {
return false;
}
return type.startsWith("image/") || "image/jpeg".equals(type) ||
"image/jpg".equals(type) || "image/png".equals(type) ||
"image/webp".equals(type);
}
/**
* 检查并申请存储权限
/**
* 检查并申请存储权限修复适配低版本API移除Android13+依赖)
*/
private boolean checkAndRequestStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 仅保留Android 6.0+API 23的WRITE_EXTERNAL_STORAGE权限兼容低版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // M = API 23Android 6.0
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
@@ -654,10 +798,18 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERMISSION_REQUEST) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
boolean isGranted = false;
// 检查权限是否授予
for (int result : grantResults) {
if (result == PackageManager.PERMISSION_GRANTED) {
isGranted = true;
break;
}
}
if (isGranted) {
ToastUtils.show("存储权限已获取");
} else {
ToastUtils.show("需要存储权限才能保存图片");
ToastUtils.show("需要存储权限才能保存/选择图片");
}
}
}
@@ -667,13 +819,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
BackgroundBean bean = utils.getCurrentBackgroundBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1);
mainLayout.setBackgroundColor(nPixelColor);
if (mainLayout != null) {
mainLayout.setBackgroundColor(nPixelColor);
}
}
@Override
protected void onResume() {
super.onResume();
setBackgroundColor();
// Resume时刷新预览避免后台切换后视图异常
bvPreviewBackground.reloadPreviewBackground();
}
public void onNetworkBackgroundDialog(View view) {
@@ -709,16 +865,25 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){
@Override
public void onRecivedPicture(String srcFilePath, String srcFileUrl) {
if (TextUtils.isEmpty(srcFilePath)) {
ToastUtils.show("网络图片路径为空");
return;
}
File srcFile = new File(srcFilePath);
if (!srcFile.exists() || srcFile.length() <= 0) {
ToastUtils.show("网络图片文件不存在或损坏");
return;
}
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
utils.saveFileToPreviewBean(new File(srcFilePath), srcFileUrl);
utils.saveFileToPreviewBean(srcFile, srcFileUrl);
// 修复:网络图片下载后刷新预览
bvPreviewBackground.reloadPreviewBackground();
startCropImageActivity(true);
startCropImageActivity(true); // 自由裁剪
}
};
/**
* 重写finish方法确保所有退出场景都触发Toast
* 重写finish方法确保所有退出场景都触发确认提示
*/
@Override
public void finish() {
@@ -730,7 +895,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
isCommitSettings = true;
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
BackgroundBean bean = utils.getCurrentBackgroundBean();
bean.setIsUseBackgroundFile(!preViewFilePath.equals(""));
// 修复:根据预览路径是否为空,设置是否启用背景图
bean.setIsUseBackgroundFile(!TextUtils.isEmpty(preViewFilePath));
utils.saveSettings();
finish();
}
@@ -738,7 +904,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override
public void onYes() {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
utils.commitPreviewSourceToCurrent();
utils.commitPreviewSourceToCurrent(); // 提交预览到正式背景
isCommitSettings = true;
finish();
}
@@ -748,4 +914,3 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}
}
}

View File

@@ -232,5 +232,28 @@ public class FileUtils {
return String.format("%s_%d%s", uniqueId, timeStamp, suffix);
}
}
/**
* 复制输入流到文件兼容Uri解析失败场景
*/
public static void copyStreamToFile(InputStream inputStream, File file) throws IOException {
if (inputStream == null || file == null) {
return;
}
// 确保父目录存在
File parentDir = file.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.flush();
fos.close();
inputStream.close();
}
}

View File

@@ -1,28 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!-- 保留root-path适配特殊机型如微信分身 -->
<root-path
name="root_path"
path="" />
<!-- 核心修复1替换files-path为external-files-path对应外部存储应用目录 -->
<!-- 适配mfBackgroundDirBackgroundSource目录对应BackgroundSourceUtils.getBackgroundSourceDirPath() -->
<external-files-path
name="background_source"
path="BackgroundPictureUtils/BackgroundSource/" /> <!-- 与代码中目录完全一致 -->
<!-- 适配mfPictureDir临时目录对应App.getTempDirPath() -->
<external-files-path
name="temp"
path="temp/" /> <!-- 与代码中App.getTempDirPath()的路径后缀一致 -->
<!-- 保留其他配置适配分享/拍照等场景 -->
<external-path
name="external_storage_root"
path="." />
<files-path
name="files_path"
path="." />
<cache-path
name="cache_path"
path="." />
<!--/storage/emulated/0/Android/data/...-->
<external-files-path
name="external_file_path"
path="." />
<external-files-path
name="files_root"
path="mimoDownload" />
<!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的目录-->
<external-files-path
name="files_root"
path="mimoDownload" />
<external-cache-path
name="external_cache_path"
path="." />
<!--配置root-path。这样子可以读取到sd卡和一些应用分身的目录否则微信分身保存的图片就会导致 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/export1544062754693.jpg在小米6的手机上微信分身有这个crash华为没有
-->
<root-path
name="root_path"
path="" />
</paths>