调试到选择图片按钮到剪裁图片阶段

This commit is contained in:
2025-12-01 02:07:29 +08:00
parent 21c712f7b3
commit 3c3bcc4ee4
3 changed files with 257 additions and 161 deletions

View File

@@ -1,8 +1,8 @@
#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
libraryProject=
baseVersion=15.11
publishVersion=15.11.12
buildCount=7
buildCount=13
baseBetaVersion=15.11.13

View File

@@ -37,6 +37,11 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
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 {
@@ -50,7 +55,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private static final int STORAGE_PERMISSION_REQUEST = 100;
// 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 File mfBackgroundDir; // 背景图片存储文件夹
@@ -184,14 +189,79 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 修复:选择图片后添加视图刷新逻辑
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (checkAndRequestStoragePermission()) {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_SELECT_PICTURE);
}
}
};
@Override
public void onClick(View v) {
if (checkAndRequestStoragePermission()) {
// 核心修复:创建多个意图作为兜底
Intent[] intents = new Intent[3];
// 意图1ACTION_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;
// 意图2ACTION_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;
// 意图3ACTION_OPEN_DOCUMENTAndroid 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() {
@Override
@@ -353,9 +423,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
}
try {
_mSourceCropTempFile.createNewFile();
// 核心优化:设置文件权限
_mSourceCropTempFile.setReadable(true, false);
_mSourceCropTempFile.setWritable(true, false);
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
ToastUtils.show("剪裁临时文件创建失败");
@@ -375,7 +448,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 核心修复2裁剪意图兼容适配不同机型
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.putExtra("crop", "true");
intent.putExtra("noFaceDetection", true);
@@ -437,19 +510,25 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
* 工具方法生成Content Uri适配Android 7.0+需在AndroidManifest.xml中配置FileProvider
*/
private Uri getUriForFile(Context context, File file) throws Exception {
String targetPackage = getPackageName();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
// 与AndroidManifest.xml中配置的FileProvider授权一致
return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file);
Uri uri = 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) {
LogUtils.e(TAG, "FileProvider生成Uri失败" + e.getMessage() + ",文件路径:" + file.getPath());
throw e; // 抛出异常,让上层处理
throw e;
}
} else {
return Uri.fromFile(file); // 低版本兼容
return Uri.fromFile(file);
}
}
/**
* 保存剪裁后的Bitmap彻底修复路径拼接+权限+解析异常)
*/
@@ -628,142 +707,143 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}
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;
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");
// 检查拍照文件是否有效
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
ToastUtils.show("拍照文件不存在或损坏");
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "拍照文件无效,清理临时文件");
}
return;
}
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
// 检查拍照文件是否有效
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
ToastUtils.show("拍照文件不存在或损坏");
// 清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "拍照文件无效,清理临时文件");
}
return;
}
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",
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解析选项避免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());
}
}
// 检查解析后的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());
}
}
}
/**
@@ -782,37 +862,47 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
* 检查并申请存储权限修复适配低版本API移除Android13+依赖)
*/
private boolean checkAndRequestStoragePermission() {
// 仅保留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},
STORAGE_PERMISSION_REQUEST);
return false;
}
}
return true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivity(intent);
return false;
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 核心修复同时申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE
String[] permissions = new String[]{
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
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERMISSION_REQUEST) {
boolean isGranted = false;
// 检查权限是否授予
for (int result : grantResults) {
if (result == PackageManager.PERMISSION_GRANTED) {
isGranted = true;
break;
}
}
if (isGranted) {
ToastUtils.show("存储权限已获取");
} else {
ToastUtils.show("需要存储权限才能保存/选择图片");
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERMISSION_REQUEST) {
boolean isGranted = false;
for (int result : grantResults) {
if (result == PackageManager.PERMISSION_GRANTED) {
isGranted = true;
break;
}
}
if (isGranted) {
ToastUtils.show("存储权限已获取,正在打开图片选择器");
// 核心优化:自动重试图片选择
onSelectPictureClickListener.onClick(findViewById(R.id.activitybackgroundpictureAButton2));
} else {
ToastUtils.show("需要存储权限才能保存/选择图片");
}
}
}
void setBackgroundColor() {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);

View File

@@ -238,22 +238,28 @@ public class FileUtils {
*/
public static void copyStreamToFile(InputStream inputStream, File file) throws IOException {
if (inputStream == null || file == null) {
return;
throw new IllegalArgumentException("InputStream或File不能为空");
}
// 确保父目录存在
File parentDir = file.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
if (!parentDir.exists() && !parentDir.mkdirs()) {
throw new IOException("无法创建父目录:" + parentDir.getAbsolutePath());
}
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len);
try {
OutputStream outputStream = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int length;
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();
}
}