@@ -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 ( mfBackground Dir , _mSourceCropTempFileName ) ;
_mSourceCropTempFile = new File ( mfPicture Dir , _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 ( v iew Width < = 0 | | viewHeight < = 0 ) {
viewWidth = getResources ( ) . getDisplayMetrics ( ) . widthPixels ;
viewHeight = getResources ( ) . getDisplayMetrics ( ) . heightPixels ;
LogUtils . d ( TAG , " 控件未测量完成,使用屏幕尺寸作为裁剪比例: " + viewWidth + " x " + viewHeight ) ;
}
int gcd = calculateGCD ( viewWidth , viewHeight ) ;
int simplif ied Width = viewWidth / gcd ;
int simplifiedHeight = viewHeight / gcd ;
// 3. 计算宽高比的最大公约数, 简化比例( 避免过大数值导致裁剪工具不兼容, 如1080:1920→9:16)
int gcd = calculateGCD ( viewWidth , v iew Height) ;
int simplifiedWidth = viewWidth / gcd ;
int simplifiedHeight = viewHeight / gcd ;
intent . putExtra ( " aspectX " , simplifiedWidth ) ;
intent . putExtra ( " aspectY " , simplif ied Height) ;
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 , " 裁剪图片为空,清理临时文件: " + _mSourceCropTempF ile. 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 ( ) + " ,文件路径: " + f ile. 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 fRecivedPict ure = 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 ( Uri Util. getFilePathFromUri ( this , selectedImage ) ) ;
BackgroundSourceUtils utils = BackgroundSourceUtils . getInstance ( this ) ;
if ( selectedImage = = null ) {
Toast Utils . 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 ) extra s. 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 ) {
ToastUtil s. 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 ) ;
ToastUt ils . 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 ( _mSourceCropTempF ile . 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 23, Android 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
}
}
}