调试到选择图片按钮到剪裁图片阶段
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Sun Nov 30 17:13:20 GMT 2025
|
#Sun Nov 30 18:04:35 GMT 2025
|
||||||
stageCount=13
|
stageCount=13
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.11
|
baseVersion=15.11
|
||||||
publishVersion=15.11.12
|
publishVersion=15.11.12
|
||||||
buildCount=7
|
buildCount=13
|
||||||
baseBetaVersion=15.11.13
|
baseBetaVersion=15.11.13
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import androidx.core.util.Preconditions;
|
import androidx.core.util.Preconditions;
|
||||||
|
import cc.winboll.studio.powerbell.BuildConfig;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
|
||||||
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
||||||
|
|
||||||
@@ -50,7 +55,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
private static final int STORAGE_PERMISSION_REQUEST = 100;
|
private static final int STORAGE_PERMISSION_REQUEST = 100;
|
||||||
|
|
||||||
// FileProvider 授权(必须与AndroidManifest.xml中配置一致)
|
// FileProvider 授权(必须与AndroidManifest.xml中配置一致)
|
||||||
private static final String FILE_PROVIDER_AUTHORITY = "cc.winboll.studio.powerbell.fileprovider";
|
private static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider";
|
||||||
|
|
||||||
private AToolbar mAToolbar;
|
private AToolbar mAToolbar;
|
||||||
private File mfBackgroundDir; // 背景图片存储文件夹
|
private File mfBackgroundDir; // 背景图片存储文件夹
|
||||||
@@ -184,14 +189,79 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
|
|
||||||
// 修复:选择图片后添加视图刷新逻辑
|
// 修复:选择图片后添加视图刷新逻辑
|
||||||
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
|
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (checkAndRequestStoragePermission()) {
|
if (checkAndRequestStoragePermission()) {
|
||||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
// 核心修复:创建多个意图作为兜底
|
||||||
startActivityForResult(intent, REQUEST_SELECT_PICTURE);
|
Intent[] intents = new Intent[3];
|
||||||
}
|
|
||||||
}
|
// 意图1:ACTION_GET_CONTENT(优先)
|
||||||
};
|
Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
getContentIntent.setType("image/*");
|
||||||
|
getContentIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
getContentIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
intents[0] = getContentIntent;
|
||||||
|
|
||||||
|
// 意图2:ACTION_PICK(兜底)
|
||||||
|
Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||||
|
pickIntent.setType("image/*");
|
||||||
|
pickIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
intents[1] = pickIntent;
|
||||||
|
|
||||||
|
// 意图3:ACTION_OPEN_DOCUMENT(Android 4.4+)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
Intent openDocIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
openDocIntent.setType("image/*");
|
||||||
|
openDocIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
openDocIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||||
|
intents[2] = openDocIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历意图,找到第一个能响应的
|
||||||
|
Intent validIntent = null;
|
||||||
|
for (Intent intent : intents) {
|
||||||
|
if (intent != null && intent.resolveActivity(getPackageManager()) != null) {
|
||||||
|
validIntent = intent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建chooser时添加flags
|
||||||
|
if (validIntent != null) {
|
||||||
|
Intent chooser = Intent.createChooser(validIntent, "选择图片");
|
||||||
|
// 核心修复:传递持久化权限flag
|
||||||
|
chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||||
|
startActivityForResult(chooser, REQUEST_SELECT_PICTURE);
|
||||||
|
} else {
|
||||||
|
// 确保对话框能正常显示
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ToastUtils.show("请安装相册应用,或点击确定下载系统相册");
|
||||||
|
new AlertDialog.Builder(BackgroundSettingsActivity.this)
|
||||||
|
.setTitle("无图片选择应用")
|
||||||
|
.setMessage("需要安装相册应用才能选择图片")
|
||||||
|
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
Intent marketIntent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
marketIntent.setData(Uri.parse("market://details?id=com.android.gallery3d"));
|
||||||
|
// 确保市场意图能响应
|
||||||
|
if (marketIntent.resolveActivity(getPackageManager()) != null) {
|
||||||
|
startActivity(marketIntent);
|
||||||
|
} else {
|
||||||
|
ToastUtils.show("无法打开应用商店");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton("取消", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() {
|
private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -353,9 +423,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_mSourceCropTempFile.createNewFile();
|
_mSourceCropTempFile.createNewFile();
|
||||||
|
// 核心优化:设置文件权限
|
||||||
|
_mSourceCropTempFile.setReadable(true, false);
|
||||||
|
_mSourceCropTempFile.setWritable(true, false);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
ToastUtils.show("剪裁临时文件创建失败");
|
ToastUtils.show("剪裁临时文件创建失败");
|
||||||
@@ -375,7 +448,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
// 核心修复2:裁剪意图兼容(适配不同机型)
|
// 核心修复2:裁剪意图兼容(适配不同机型)
|
||||||
Intent intent = new Intent("com.android.camera.action.CROP");
|
Intent intent = new Intent("com.android.camera.action.CROP");
|
||||||
// 兼容部分机型不支持隐式意图,添加包名过滤(可选,按需添加)
|
// 兼容部分机型不支持隐式意图,添加包名过滤(可选,按需添加)
|
||||||
intent.setPackage("com.android.camera");
|
//intent.setPackage("com.android.camera");
|
||||||
intent.setDataAndType(inputUri, "image/" + _mszCommonFileType);
|
intent.setDataAndType(inputUri, "image/" + _mszCommonFileType);
|
||||||
intent.putExtra("crop", "true");
|
intent.putExtra("crop", "true");
|
||||||
intent.putExtra("noFaceDetection", true);
|
intent.putExtra("noFaceDetection", true);
|
||||||
@@ -437,19 +510,25 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
* 工具方法:生成Content Uri(适配Android 7.0+),需在AndroidManifest.xml中配置FileProvider
|
* 工具方法:生成Content Uri(适配Android 7.0+),需在AndroidManifest.xml中配置FileProvider
|
||||||
*/
|
*/
|
||||||
private Uri getUriForFile(Context context, File file) throws Exception {
|
private Uri getUriForFile(Context context, File file) throws Exception {
|
||||||
|
String targetPackage = getPackageName();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
try {
|
try {
|
||||||
// 与AndroidManifest.xml中配置的FileProvider授权一致
|
Uri uri = FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file);
|
||||||
return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file);
|
// 显式授予Uri权限给目标应用(如裁剪工具的包名)
|
||||||
|
if (!TextUtils.isEmpty(targetPackage)) {
|
||||||
|
context.grantUriPermission(targetPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.e(TAG, "FileProvider生成Uri失败:" + e.getMessage() + ",文件路径:" + file.getPath());
|
LogUtils.e(TAG, "FileProvider生成Uri失败:" + e.getMessage() + ",文件路径:" + file.getPath());
|
||||||
throw e; // 抛出异常,让上层处理
|
throw e;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Uri.fromFile(file); // 低版本兼容
|
return Uri.fromFile(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存剪裁后的Bitmap(彻底修复:路径拼接+权限+解析异常)
|
* 保存剪裁后的Bitmap(彻底修复:路径拼接+权限+解析异常)
|
||||||
*/
|
*/
|
||||||
@@ -628,142 +707,143 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
}
|
}
|
||||||
LogUtils.d(TAG, "选择图片Uri : " + selectedImage.toString());
|
LogUtils.d(TAG, "选择图片Uri : " + selectedImage.toString());
|
||||||
|
|
||||||
// 核心修复1:替换路径解析方式,兼容UriUtil解析失败场景
|
// 核心修复:对ACTION_GET_CONTENT返回的Uri,添加持久化权限(Android 4.4+)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
getContentResolver().takePersistableUriPermission(
|
||||||
|
selectedImage,
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路径解析逻辑保持不变...
|
||||||
File fSrcImage = null;
|
File fSrcImage = null;
|
||||||
String filePath = UriUtil.getFilePathFromUri(this, selectedImage);
|
String filePath = UriUtil.getFilePathFromUri(this, selectedImage);
|
||||||
if (!TextUtils.isEmpty(filePath)) {
|
if (!TextUtils.isEmpty(filePath)) {
|
||||||
fSrcImage = new File(filePath);
|
fSrcImage = new File(filePath);
|
||||||
} else {
|
} else {
|
||||||
// 兼容方案:通过ContentResolver读取图片流,保存为临时文件
|
|
||||||
fSrcImage = new File(mfPictureDir, "selected_temp.jpg");
|
fSrcImage = new File(mfPictureDir, "selected_temp.jpg");
|
||||||
if (fSrcImage.exists()) {
|
if (fSrcImage.exists()) {
|
||||||
fSrcImage.delete();
|
fSrcImage.delete();
|
||||||
}
|
}
|
||||||
// 复制图片流到临时文件
|
|
||||||
FileUtils.copyStreamToFile(getContentResolver().openInputStream(selectedImage), fSrcImage);
|
FileUtils.copyStreamToFile(getContentResolver().openInputStream(selectedImage), fSrcImage);
|
||||||
LogUtils.d(TAG, "Uri解析失败,通过流复制生成临时文件:" + fSrcImage.getPath());
|
LogUtils.d(TAG, "Uri解析失败,通过流复制生成临时文件:" + fSrcImage.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 核心修复2:增强文件有效性校验
|
|
||||||
if (fSrcImage == null || !fSrcImage.exists() || fSrcImage.length() <= 0) {
|
if (fSrcImage == null || !fSrcImage.exists() || fSrcImage.length() <= 0) {
|
||||||
ToastUtils.show("选择的图片文件不存在或损坏");
|
ToastUtils.show("选择的图片文件不存在或损坏");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
|
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
|
||||||
// 保存图片到预览Bean并刷新
|
|
||||||
utils.saveFileToPreviewBean(fSrcImage, selectedImage.toString());
|
utils.saveFileToPreviewBean(fSrcImage, selectedImage.toString());
|
||||||
bvPreviewBackground.reloadPreviewBackground();
|
bvPreviewBackground.reloadPreviewBackground();
|
||||||
|
|
||||||
// 启动裁剪(增加异常捕获)
|
|
||||||
startCropImageActivity(false);
|
startCropImageActivity(false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.e(TAG, "选择图片异常" + e);
|
LogUtils.e(TAG, "选择图片异常" + e);
|
||||||
ToastUtils.show("选择图片失败:" + e.getMessage());
|
ToastUtils.show("选择图片失败:" + e.getMessage());
|
||||||
// 异常时清理临时文件
|
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
LogUtils.d(TAG, "选择图片异常,清理临时文件:" + _mSourceCropTempFile.getPath());
|
LogUtils.d(TAG, "选择图片异常,清理临时文件:" + _mSourceCropTempFile.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
|
} else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
|
||||||
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
|
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
|
||||||
// 检查拍照文件是否有效
|
// 检查拍照文件是否有效
|
||||||
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
|
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
|
||||||
ToastUtils.show("拍照文件不存在或损坏");
|
ToastUtils.show("拍照文件不存在或损坏");
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
LogUtils.d(TAG, "拍照文件无效,清理临时文件");
|
LogUtils.d(TAG, "拍照文件无效,清理临时文件");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bundle extras = data.getExtras();
|
Bundle extras = data.getExtras();
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
Bitmap imageBitmap = (Bitmap) extras.get("data");
|
Bitmap imageBitmap = (Bitmap) extras.get("data");
|
||||||
if (imageBitmap != null && !imageBitmap.isRecycled()) {
|
if (imageBitmap != null && !imageBitmap.isRecycled()) {
|
||||||
compressQualityToRecivedPicture(imageBitmap);
|
compressQualityToRecivedPicture(imageBitmap);
|
||||||
// 拍照压缩后刷新预览
|
// 拍照压缩后刷新预览
|
||||||
bvPreviewBackground.reloadPreviewBackground();
|
bvPreviewBackground.reloadPreviewBackground();
|
||||||
// 启动裁剪
|
// 启动裁剪
|
||||||
startCropImageActivity(false);
|
startCropImageActivity(false);
|
||||||
} else {
|
} else {
|
||||||
ToastUtils.show("拍照图片为空");
|
ToastUtils.show("拍照图片为空");
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
LogUtils.d(TAG, "拍照图片为空,清理临时文件:" + _mSourceCropTempFile.getPath());
|
LogUtils.d(TAG, "拍照图片为空,清理临时文件:" + _mSourceCropTempFile.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ToastUtils.show("拍照数据获取失败");
|
ToastUtils.show("拍照数据获取失败");
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
LogUtils.d(TAG, "拍照数据获取失败,清理临时文件:" + _mSourceCropTempFile.getPath());
|
LogUtils.d(TAG, "拍照数据获取失败,清理临时文件:" + _mSourceCropTempFile.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
|
} else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
|
||||||
LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
|
LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
|
||||||
try {
|
try {
|
||||||
Bitmap cropBitmap = null;
|
Bitmap cropBitmap = null;
|
||||||
// 核心修复:优先读取裁剪临时文件(放弃data.getParcelableExtra,避免缩略图)
|
// 核心修复:优先读取裁剪临时文件(放弃data.getParcelableExtra,避免缩略图)
|
||||||
if (_mSourceCropTempFile.exists() && _mSourceCropTempFile.length() > 0) {
|
if (_mSourceCropTempFile.exists() && _mSourceCropTempFile.length() > 0) {
|
||||||
LogUtils.d(TAG, String.format("_mSourceCropTempFile 信息:路径=%s , 大小=%d bytes",
|
LogUtils.d(TAG, String.format("_mSourceCropTempFile 信息:路径=%s , 大小=%d bytes",
|
||||||
_mSourceCropTempFile.getPath(), _mSourceCropTempFile.length()));
|
_mSourceCropTempFile.getPath(), _mSourceCropTempFile.length()));
|
||||||
// 优化Bitmap解析选项(避免OOM和损坏图片解析失败)
|
// 优化Bitmap解析选项(避免OOM和损坏图片解析失败)
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565; // 省内存(仅RGB,无透明通道)
|
options.inPreferredConfig = Bitmap.Config.RGB_565; // 省内存(仅RGB,无透明通道)
|
||||||
options.inSampleSize = 1; // 不缩放(保证清晰度)
|
options.inSampleSize = 1; // 不缩放(保证清晰度)
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
cropBitmap = BitmapFactory.decodeFile(_mSourceCropTempFile.getPath(), options);
|
cropBitmap = BitmapFactory.decodeFile(_mSourceCropTempFile.getPath(), options);
|
||||||
} else {
|
} else {
|
||||||
ToastUtils.show("剪裁文件为空或损坏");
|
ToastUtils.show("剪裁文件为空或损坏");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查解析后的Bitmap是否有效
|
// 检查解析后的Bitmap是否有效
|
||||||
if (cropBitmap != null && !cropBitmap.isRecycled()) {
|
if (cropBitmap != null && !cropBitmap.isRecycled()) {
|
||||||
saveCropBitmap(cropBitmap); // 调用保存方法(内含修复逻辑)
|
saveCropBitmap(cropBitmap); // 调用保存方法(内含修复逻辑)
|
||||||
} else {
|
} else {
|
||||||
ToastUtils.show("获取剪裁图片失败(Bitmap解析异常)");
|
ToastUtils.show("获取剪裁图片失败(Bitmap解析异常)");
|
||||||
// 清理无效临时文件
|
// 清理无效临时文件
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
LogUtils.d(TAG, "Bitmap解析失败,清理无效临时文件");
|
LogUtils.d(TAG, "Bitmap解析失败,清理无效临时文件");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
LogUtils.e(TAG, "内存溢出" + e);
|
LogUtils.e(TAG, "内存溢出" + e);
|
||||||
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
|
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.e(TAG, "剪裁保存异常" + e);
|
LogUtils.e(TAG, "剪裁保存异常" + e);
|
||||||
ToastUtils.show("保存失败:" + e.getMessage());
|
ToastUtils.show("保存失败:" + e.getMessage());
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// 裁剪流程结束,强制清理临时文件(双重保障,避免残留)
|
// 裁剪流程结束,强制清理临时文件(双重保障,避免残留)
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
LogUtils.d(TAG, "裁剪流程结束,强制清理临时文件:" + _mSourceCropTempFile.getPath());
|
LogUtils.d(TAG, "裁剪流程结束,强制清理临时文件:" + _mSourceCropTempFile.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (resultCode != RESULT_OK) {
|
} else if (resultCode != RESULT_OK) {
|
||||||
LogUtils.d(TAG, "操作取消或失败,requestCode: " + requestCode);
|
LogUtils.d(TAG, "操作取消或失败,requestCode: " + requestCode);
|
||||||
ToastUtils.show("操作已取消");
|
ToastUtils.show("操作已取消");
|
||||||
// 操作取消/失败时,强制清理临时文件
|
// 操作取消/失败时,强制清理临时文件
|
||||||
if (_mSourceCropTempFile.exists()) {
|
if (_mSourceCropTempFile.exists()) {
|
||||||
_mSourceCropTempFile.delete();
|
_mSourceCropTempFile.delete();
|
||||||
LogUtils.d(TAG, "操作取消/失败,清理临时文件:" + _mSourceCropTempFile.getPath());
|
LogUtils.d(TAG, "操作取消/失败,清理临时文件:" + _mSourceCropTempFile.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -782,37 +862,47 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
* 检查并申请存储权限(修复:适配低版本API,移除Android13+依赖)
|
* 检查并申请存储权限(修复:适配低版本API,移除Android13+依赖)
|
||||||
*/
|
*/
|
||||||
private boolean checkAndRequestStoragePermission() {
|
private boolean checkAndRequestStoragePermission() {
|
||||||
// 仅保留Android 6.0+(API 23)的WRITE_EXTERNAL_STORAGE权限,兼容低版本
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // M = API 23,Android 6.0
|
if (!Environment.isExternalStorageManager()) {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
|
||||||
ActivityCompat.requestPermissions(this,
|
startActivity(intent);
|
||||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
return false;
|
||||||
STORAGE_PERMISSION_REQUEST);
|
}
|
||||||
return false;
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
}
|
// 核心修复:同时申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE
|
||||||
}
|
String[] permissions = new String[]{
|
||||||
return true;
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
}
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
};
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
||||||
|
|| ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(this, permissions, STORAGE_PERMISSION_REQUEST);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (requestCode == STORAGE_PERMISSION_REQUEST) {
|
if (requestCode == STORAGE_PERMISSION_REQUEST) {
|
||||||
boolean isGranted = false;
|
boolean isGranted = false;
|
||||||
// 检查权限是否授予
|
for (int result : grantResults) {
|
||||||
for (int result : grantResults) {
|
if (result == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (result == PackageManager.PERMISSION_GRANTED) {
|
isGranted = true;
|
||||||
isGranted = true;
|
break;
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
if (isGranted) {
|
||||||
if (isGranted) {
|
ToastUtils.show("存储权限已获取,正在打开图片选择器");
|
||||||
ToastUtils.show("存储权限已获取");
|
// 核心优化:自动重试图片选择
|
||||||
} else {
|
onSelectPictureClickListener.onClick(findViewById(R.id.activitybackgroundpictureAButton2));
|
||||||
ToastUtils.show("需要存储权限才能保存/选择图片");
|
} else {
|
||||||
}
|
ToastUtils.show("需要存储权限才能保存/选择图片");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setBackgroundColor() {
|
void setBackgroundColor() {
|
||||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
|
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
|
||||||
|
|||||||
@@ -238,22 +238,28 @@ public class FileUtils {
|
|||||||
*/
|
*/
|
||||||
public static void copyStreamToFile(InputStream inputStream, File file) throws IOException {
|
public static void copyStreamToFile(InputStream inputStream, File file) throws IOException {
|
||||||
if (inputStream == null || file == null) {
|
if (inputStream == null || file == null) {
|
||||||
return;
|
throw new IllegalArgumentException("InputStream或File不能为空");
|
||||||
}
|
}
|
||||||
// 确保父目录存在
|
|
||||||
File parentDir = file.getParentFile();
|
File parentDir = file.getParentFile();
|
||||||
if (!parentDir.exists()) {
|
if (!parentDir.exists() && !parentDir.mkdirs()) {
|
||||||
parentDir.mkdirs();
|
throw new IOException("无法创建父目录:" + parentDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
FileOutputStream fos = new FileOutputStream(file);
|
try {
|
||||||
byte[] buffer = new byte[1024];
|
OutputStream outputStream = new FileOutputStream(file);
|
||||||
int len;
|
byte[] buffer = new byte[1024];
|
||||||
while ((len = inputStream.read(buffer)) != -1) {
|
int length;
|
||||||
fos.write(buffer, 0, len);
|
while ((length = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
outputStream.flush();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e("FileUtils", "关闭输入流失败:" + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fos.flush();
|
|
||||||
fos.close();
|
|
||||||
inputStream.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user