diff --git a/powerbell/build.properties b/powerbell/build.properties index 46e17655..9b296b37 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Dec 03 22:37:16 GMT 2025 +#Wed Dec 03 22:57:30 GMT 2025 stageCount=13 libraryProject= baseVersion=15.11 publishVersion=15.11.12 -buildCount=191 +buildCount=195 baseBetaVersion=15.11.13 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java index f471e4bb..6ec882c7 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java @@ -1,11 +1,8 @@ package cc.winboll.studio.powerbell.activities; import android.app.Activity; -import android.content.ComponentName; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; @@ -21,7 +18,6 @@ import android.util.Log; import android.view.View; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; -import androidx.core.content.FileProvider; import cc.winboll.studio.libaes.views.AToolbar; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.ToastUtils; @@ -31,6 +27,7 @@ import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog; import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog; import cc.winboll.studio.powerbell.model.BackgroundBean; import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils; +import cc.winboll.studio.powerbell.utils.ImageCropUtils; import cc.winboll.studio.powerbell.utils.PermissionUtils; import cc.winboll.studio.powerbell.views.BackgroundView; import java.io.BufferedOutputStream; @@ -38,40 +35,21 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.List; -/** - * 背景设置Activity(图片选择/拍照/裁剪/预览/保存核心页面,Java7兼容,修复裁剪拉伸问题) - * 核心特性: - * 1. 适配多包名场景(依赖工具类动态获取包名/Authority) - * 2. 兼容Android 6.0~14+(权限/存储/裁剪适配) - * 3. 修复裁剪拉伸:裁剪后延迟加载图片,确保BackgroundView获取最新比例 - * 4. 优化裁剪流程:启动裁剪时直接使用原图,不额外复制到BackgroundSource目录 - * 5. 适配MIUI等特殊机型(裁剪权限/渲染延迟适配) - */ public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener { - // 日志标签 public static final String TAG = "BackgroundSettingsActivity"; - // 工具类单例(职责分离:文件管理/权限管理) private BackgroundSourceUtils mBgSourceUtils; private PermissionUtils mPermissionUtils; - // 图片选择/裁剪/拍照请求码 public static final int REQUEST_SELECT_PICTURE = 0; public static final int REQUEST_TAKE_PHOTO = 1; public static final int REQUEST_CROP_IMAGE = 2; - // 控件实例 private AToolbar mAToolbar; private BackgroundView mBackgroundView; - // 拍照临时文件(仅拍照用,路径由工具类间接管理) private File mfTakePhoto; - // 配置标记(是否提交设置) boolean isCommitSettings = false; - - int mCropAspectX = 0; - int mCropAspectY = 0; @Override public Activity getActivity() { @@ -88,48 +66,37 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg super.onCreate(savedInstanceState); setContentView(R.layout.activity_background_settings); - // 初始化核心控件 mBackgroundView = (BackgroundView) findViewById(R.id.background_view); - // 初始化工具类(单例模式,全局复用,解耦业务逻辑) mBgSourceUtils = BackgroundSourceUtils.getInstance(this); mPermissionUtils = PermissionUtils.getInstance(); - // 初始化拍照临时文件(复用App临时目录,权限由PermissionUtils保障) File tempDir = new File(App.getTempDirPath()); if (!tempDir.exists()) { tempDir.mkdirs(); } mfTakePhoto = new File(tempDir, "TakePhoto.jpg"); - // 新增:初始化选图临时文件目录(独立于拍照目录,避免文件覆盖) File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp"); if (!selectTempDir.exists()) { selectTempDir.mkdirs(); LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath()); } - // 初始化UI及数据 initToolbar(); initClickListeners(); - initBackgroundViewByPreviewBean(); // 正式Bean → 预览Bean(确保预览数据准确) - handleShareIntent(); // 处理分享图片意图 + initBackgroundViewByPreviewBean(); + handleShareIntent(); - LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成(Java7 语法版)"); + LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成"); } - /** - * 初始化预览Bean:从正式Bean拷贝所有属性(确保每次操作都是正式图的副本) - */ private void initBackgroundViewByPreviewBean() { - LogUtils.d(TAG, "【Bean初始化】正式Bean → 预览Bean(拷贝初始化)"); + LogUtils.d(TAG, "【Bean初始化】正式Bean → 预览Bean"); mBgSourceUtils.setCurrentSourceToPreview(); doubleRefreshPreview(); - LogUtils.d(TAG, "【Bean初始化】预览Bean初始化完成,当前预览路径:" + mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath()); + LogUtils.d(TAG, "【Bean初始化】预览Bean初始化完成"); } - /** - * 初始化工具栏(仅UI逻辑,无业务逻辑) - */ private void initToolbar() { mAToolbar = (AToolbar) findViewById(R.id.toolbar); setActionBar(mAToolbar); @@ -138,15 +105,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mAToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【导航栏】点击返回,触发finish"); + LogUtils.d(TAG, "【导航栏】点击返回"); finish(); } }); } - /** - * 初始化所有按钮点击事件(仅UI交互,业务逻辑调用工具类) - */ private void initClickListeners() { findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener); findViewById(R.id.activitybackgroundpictureAButton4).setOnClickListener(onReceivedPictureClickListener); @@ -158,9 +122,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener); } - /** - * 处理分享图片意图(仅UI逻辑,文件处理调用工具类) - */ private void handleShareIntent() { Intent intent = getIntent(); if (intent != null) { @@ -169,7 +130,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (Intent.ACTION_SEND.equals(action) && type != null && isImageType(type)) { BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this); dlg.show(); - LogUtils.d(TAG, "【分享处理】收到分享图片意图,已显示预览对话框"); + LogUtils.d(TAG, "【分享处理】收到分享图片意图"); } } } @@ -177,58 +138,45 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg boolean isImageType(String lowerMimeType) { return lowerMimeType.equals("image/jpeg") || lowerMimeType.equals("image/png") - // 新增小众格式适配 || lowerMimeType.equals("image/tiff") - || lowerMimeType.equals("image/jpg") // 兼容部分应用传入的"image/jpg"(标准为"image/jpeg") - || lowerMimeType.equals("image/svg+xml"); // 若需支持SVG格式,可添加(需确保App支持SVG解析) + || lowerMimeType.equals("image/jpg") + || lowerMimeType.equals("image/svg+xml"); } @Override public void onAcceptRecivedPicture(String szPreRecivedPictureName) { ToastUtils.show("图片接收功能暂未实现"); - LogUtils.d(TAG, "【分享接收】onAcceptRecivedPicture 触发,图片名:" + szPreRecivedPictureName); + LogUtils.d(TAG, "【分享接收】图片名:" + szPreRecivedPictureName); } - // ======================================== 按钮点击事件(仅UI交互) ======================================== - /** - * 点击事件:取消背景(仅操作Bean,无文件逻辑) - */ private View.OnClickListener onOriginNullClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发取消背景图片功能"); - // 1. 修改正式Bean + LogUtils.d(TAG, "【按钮点击】取消背景图片"); BackgroundBean previewBackgroundBean = mBgSourceUtils.getPreviewBackgroundBean(); previewBackgroundBean.setIsUseBackgroundFile(false); doubleRefreshPreview(); } }; - /** - * 点击事件:选择图片(修复:新增选图临时文件校验,避免路径复用导致的错乱) - */ private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发选择图片功能"); - - // 调用权限工具类校验存储权限 + LogUtils.d(TAG, "【按钮点击】选择图片"); if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) { - LogUtils.d(TAG, "【选图权限】存储权限已获取,开始查找图片选择意图"); - // 多意图兜底(适配不同相册应用) + LogUtils.d(TAG, "【选图权限】已获取"); 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/*"); @@ -237,7 +185,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg intents[2] = openDocIntent; } - // 查找有效意图(Java7 for循环) Intent validIntent = null; for (int i = 0; i < intents.length; i++) { Intent intent = intents[i]; @@ -251,9 +198,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg Intent chooser = Intent.createChooser(validIntent, "选择图片"); chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); startActivityForResult(chooser, REQUEST_SELECT_PICTURE); - LogUtils.d(TAG, "【选图意图】找到有效意图,已启动图片选择。"); + LogUtils.d(TAG, "【选图意图】启动图片选择"); } else { - LogUtils.d(TAG, "【选图意图】未找到有效图片选择应用,提示用户安装"); + LogUtils.d(TAG, "【选图意图】无相册应用"); runOnUiThread(new Runnable() { @Override public void run() { @@ -279,425 +226,180 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg }); } } else { - LogUtils.d(TAG, "【选图权限】存储权限未获取,已触发权限申请"); + LogUtils.d(TAG, "【选图权限】已申请"); } } }; - /** - * 点击事件:固定比例裁剪(仅触发裁剪,文件路径由工具类管理) - */ private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发固定比例裁剪功能"); - startCropImageActivity(false); + LogUtils.d(TAG, "【按钮点击】固定比例裁剪"); + // 调用裁剪工具类:传入上下文、预览图、固定比例(按视图宽高)、请求码 + ImageCropUtils.startSystemCrop( + BackgroundSettingsActivity.this, + new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()), + mBackgroundView.getWidth(), + mBackgroundView.getHeight(), + false, + REQUEST_CROP_IMAGE + ); } }; - /** - * 点击事件:自由裁剪(仅触发裁剪,文件路径由工具类管理) - */ private View.OnClickListener onCropFreePictureClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发自由裁剪功能"); - startCropImageActivity(true); + LogUtils.d(TAG, "【按钮点击】自由裁剪"); + // 调用裁剪工具类:传入上下文、预览图、自由裁剪(比例参数传0)、请求码 + ImageCropUtils.startSystemCrop( + BackgroundSettingsActivity.this, + new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()), + 0, + 0, + true, + REQUEST_CROP_IMAGE + ); } }; - /** - * 点击事件:拍照(权限校验调用PermissionUtils,文件处理调用BackgroundSourceUtils) - */ private View.OnClickListener onTakePhotoClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发拍照功能"); - // 清理旧拍照文件 + LogUtils.d(TAG, "【按钮点击】拍照"); if (mfTakePhoto.exists()) { boolean deleteSuccess = mfTakePhoto.delete(); - LogUtils.d(TAG, "【拍照准备】旧拍照文件清理:" + (deleteSuccess ? "成功" : "失败")); + LogUtils.d(TAG, "【拍照准备】清理旧文件:" + (deleteSuccess ? "成功" : "失败")); } - // 创建新拍照文件 try { boolean createSuccess = mfTakePhoto.createNewFile(); - LogUtils.d(TAG, "【拍照准备】新拍照文件创建:" + (createSuccess ? "成功" : "失败")); + LogUtils.d(TAG, "【拍照准备】创建新文件:" + (createSuccess ? "成功" : "失败")); if (!createSuccess) { ToastUtils.show("拍照文件创建失败"); return; } } catch (IOException e) { - LogUtils.e(TAG, "【拍照异常】文件创建抛出异常:" + e.getMessage()); + LogUtils.e(TAG, "【拍照异常】" + e.getMessage()); ToastUtils.show("拍照文件创建失败"); return; } - // 调用权限工具类校验存储权限 if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) { - LogUtils.d(TAG, "【拍照权限】存储权限已获取,开始生成拍照Uri"); + LogUtils.d(TAG, "【拍照权限】已获取"); Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); try { - // 调用工具类获取FileProvider Authority(多包名兼容) - Uri photoUri = FileProvider.getUriForFile(BackgroundSettingsActivity.this, - mBgSourceUtils.getFileProviderAuthority(), mfTakePhoto); + Uri photoUri = ImageCropUtils.getFileProviderUri(BackgroundSettingsActivity.this, mfTakePhoto); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO); - LogUtils.d(TAG, "【拍照启动】相机已启动,拍照Uri:" + photoUri.toString()); + LogUtils.d(TAG, "【拍照启动】Uri:" + photoUri.toString()); } catch (Exception e) { String errMsg = "拍照启动异常:" + e.getMessage(); ToastUtils.show(errMsg.substring(0, 20)); - LogUtils.e(TAG, "【拍照失败】相机启动失败"); + LogUtils.e(TAG, "【拍照失败】"); } } else { - LogUtils.d(TAG, "【拍照权限】存储权限未获取,已触发权限申请"); + LogUtils.d(TAG, "【拍照权限】已申请"); } } }; - /** - * 点击事件:图片接收(暂未实现) - */ private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() { @Override public void onClick(View v) { ToastUtils.show("图片接收功能暂未实现"); - LogUtils.d(TAG, "【按钮点击】触发onReceivedPictureClickListener(暂未实现)"); + LogUtils.d(TAG, "【按钮点击】图片接收"); } }; - /** - * 点击事件:像素拾取(路径由工具类管理,仅触发跳转) - */ private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发像素拾取功能"); + LogUtils.d(TAG, "【按钮点击】像素拾取"); String targetImagePath = mBgSourceUtils.getCurrentBackgroundFilePath(); File targetFile = new File(targetImagePath); if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) { ToastUtils.show("无有效图片可拾取像素"); - LogUtils.e(TAG, "【像素拾取失败】目标图片无效"); + LogUtils.e(TAG, "【像素拾取失败】"); return; } - // 启动像素拾取Activity Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class); intent.putExtra("imagePath", targetImagePath); startActivity(intent); - LogUtils.d(TAG, "【像素拾取启动】已跳转至PixelPickerActivity"); + LogUtils.d(TAG, "【像素拾取启动】"); } }; - /** - * 点击事件:清空像素颜色(仅操作Bean,无文件逻辑) - */ private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - LogUtils.d(TAG, "【按钮点击】触发像素颜色清空功能"); + LogUtils.d(TAG, "【按钮点击】清空像素颜色"); BackgroundBean bean = mBgSourceUtils.getCurrentBackgroundBean(); int oldColor = bean.getPixelColor(); bean.setPixelColor(0); mBgSourceUtils.saveSettings(); doubleRefreshPreview(); ToastUtils.show("像素颜色已清空"); - LogUtils.d(TAG, "【像素清空】操作完成:旧颜色值=" + oldColor + ",新颜色值=0,配置已保存"); + LogUtils.d(TAG, "【像素清空】旧颜色:" + oldColor); } }; - /** - * 启动图片裁剪活动(核心优化:直接使用预览图原图,不复制到BackgroundSource) - * @param isCropFree 是否自由裁剪 - */ -// public void startCropImageActivity(boolean isCropFree) { -// LogUtils.d(TAG, "【裁剪启动】startCropImageActivity 触发,自由裁剪:" + isCropFree); -// BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); -// previewBean.setIsUseBackgroundScaledCompressFile(false); -// mBgSourceUtils.saveSettings(); -// doubleRefreshPreview(); -// -// File previewFile = new File(previewBean.getBackgroundFilePath()); // 裁剪缓存图片 -// -// // 2. 生成裁剪输入Uri(强化MIUI权限授予,直接用原图Uri) -// Uri inputUri = null; -// try { -// inputUri = FileProvider.getUriForFile(this, mBgSourceUtils.getFileProviderAuthority(), previewFile); -// // 显式授予MIUI裁剪工具读写权限(关键适配) -// grantUriPermission("com.miui.gallery", inputUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); -// LogUtils.d(TAG, "【裁剪Uri】输入Uri生成成功 : " + inputUri.toString() + ",已授予MIUI裁剪工具权限"); -// } catch (Exception e) { -// LogUtils.e(TAG, "【裁剪异常】生成输入Uri失败:" + e.getMessage(), e); -// ToastUtils.show("图片裁剪失败:无法获取图片权限"); -// return; -// } -// -// // 3. 【核心优化】直接使用预览图原图作为裁剪输入,删除原工具类创建裁剪路径的复制逻辑 -// // 仅创建裁剪结果临时文件(用于接收裁剪输出,不涉及原图复制) -// File cropResultTempFile = new File(previewBean.getBackgroundScaledCompressFilePath()); -// if (cropResultTempFile == null) { -// ToastUtils.show("裁剪路径创建失败,请重试"); -// return; -// } -// -// // 4. 构建裁剪意图(适配MIUI核心修改,输入为原图Uri) -// Intent intent = new Intent("com.android.camera.action.CROP"); -// intent.setDataAndType(inputUri, "image/*"); -// intent.putExtra("crop", "true"); -// intent.putExtra("noFaceDetection", true); -// -// // 裁剪比例设置 -// if (!isCropFree) { -// int viewWidth = mBackgroundView.getWidth() > 0 ? mBackgroundView.getWidth() : getResources().getDisplayMetrics().widthPixels; -// int viewHeight = mBackgroundView.getHeight() > 0 ? mBackgroundView.getHeight() : getResources().getDisplayMetrics().heightPixels; -// int gcd = calculateGCD(viewWidth, viewHeight); -// intent.putExtra("aspectX", viewWidth / gcd); -// intent.putExtra("aspectY", viewHeight / gcd); -// } else { -// // 真正自由裁剪:移除aspectX/aspectY参数(不设置则裁剪窗口可自由调整比例) -// LogUtils.d(TAG, "【裁剪比例】启用真正自由裁剪(移除固定比例限制)"); -// } -// -// // 输出尺寸设置(统一用2048,适配所有版本) -// int maxOutputSize = 2048; -// int outputX = Math.min(getResources().getDisplayMetrics().widthPixels, maxOutputSize); -// int outputY = Math.min(getResources().getDisplayMetrics().heightPixels, maxOutputSize); -// intent.putExtra("outputX", outputX); -// intent.putExtra("outputY", outputY); -// intent.putExtra("scale", true); -// intent.putExtra("scaleUpIfNeeded", true); -// -// // 输出配置(指定JPEG格式,适配MIUI) -// intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); -// intent.putExtra("quality", 80); -// intent.putExtra("return-data", false); // 禁用返回Bitmap,避免OOM -// -// // 权限Flags(添加持久化权限) -// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); -// intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { -// intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); -// } -// -// // 5. 适配系统裁剪工具(MIUI专属处理) -// try { -// List resolveInfos = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); -// if (!resolveInfos.isEmpty()) { -// ResolveInfo resolveInfo = resolveInfos.get(0); -// String cropPackageName = resolveInfo.activityInfo.packageName; -// String cropActivityName = resolveInfo.activityInfo.name; -// LogUtils.d(TAG, "【裁剪适配】找到裁剪工具:包名=" + cropPackageName + ",Activity=" + cropActivityName); -// -// // 生成输出Uri(显式授予MIUI写入权限,输出到临时结果文件) -// Uri outputUri = FileProvider.getUriForFile(this, mBgSourceUtils.getFileProviderAuthority(), cropResultTempFile); -// // 适配MIUI裁剪工具,额外授予持久化写入权限 -// if (cropPackageName.equals("com.miui.gallery")) { -// grantUriPermission(cropPackageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); -// LogUtils.d(TAG, "【裁剪适配】已授予MIUI裁剪工具输出Uri写入权限"); -// } -// -// // 显式设置Component -// Intent cropIntent = new Intent(intent); -// cropIntent.setComponent(new ComponentName(cropPackageName, cropActivityName)); -// cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); -// cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); -// -// // 保存裁剪结果临时文件路径到工具类(用于回调时获取) -// mBgSourceUtils.getPreviewBackgroundBean().setBackgroundScaledCompressFilePath(cropResultTempFile.getAbsolutePath()); -// startActivityForResult(cropIntent, REQUEST_CROP_IMAGE); -// LogUtils.d(TAG, "【裁剪启动】已启动系统裁剪工具,输出路径:" + cropResultTempFile.getAbsolutePath()); -// } else { -// // 兜底:启动第三方裁剪工具 -// Uri outputUri = FileProvider.getUriForFile(this, mBgSourceUtils.getFileProviderAuthority(), cropResultTempFile); -// intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); -// Intent chooser = Intent.createChooser(intent, "选择裁剪工具"); -// chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); -// -// // 保存裁剪结果临时文件路径到工具类 -// mBgSourceUtils.getPreviewBackgroundBean().setBackgroundScaledCompressFilePath(cropResultTempFile.getAbsolutePath()); -// if (chooser.resolveActivity(getPackageManager()) != null) { -// startActivityForResult(chooser, REQUEST_CROP_IMAGE); -// } else { -// ToastUtils.show("无可用裁剪工具,请安装系统相机"); -// } -// } -// } catch (Exception e) { -// LogUtils.e(TAG, "【裁剪异常】启动裁剪工具失败:" + e.getMessage(), e); -// ToastUtils.show("无法启动裁剪工具"); -// } -// } - - /** - * 计算两个整数的最大公约数(GCD,Greatest Common Divisor) - * 用途:裁剪时获取视图宽高的最简比例(避免比例值过大导致裁剪工具崩溃) - * 算法:欧几里得算法(高效稳定,时间复杂度O(log(min(a,b))),兼容Java7) - * @param a 整数1(如视图宽度) - * @param b 整数2(如视图高度) - * @return 最大公约数(最小返回1,避免比例计算时分母为0) - */ - private int calculateGCD(int a, int b) { - // 边界值校验(避免传入0导致死循环,适配视图宽高为0的极端情况) - if (a <= 0 || b <= 0) { - LogUtils.w(TAG, "【GCD计算】输入值无效(a=" + a + ", b=" + b + "),返回默认值1"); - return 1; - } - - // 欧几里得算法核心逻辑(Java7 循环写法,无Java8+特性) - int temp; - while (b != 0) { - temp = a % b; // 取余 - a = b; // 赋值:把b作为新的a - b = temp; // 赋值:把余数作为新的b - } - - // 最终a即为最大公约数(确保返回值≥1,避免比例计算异常) - int gcdResult = Math.max(a, 1); - LogUtils.d(TAG, "【GCD计算】完成,输入(宽=" + (a + b) + ", 高=" + (a) + "),最大公约数:" + gcdResult); - return gcdResult; - } - - /** - * 图片缩放(核心用途:大图片自动缩小,降低内存占用,避免OOM崩溃) - * 逻辑:按指定缩放比例(0~1之间)缩小Bitmap,保持宽高比不变,生成新的Bitmap并回收旧图 - * @param originalBitmap 原始Bitmap(需确保非null、未回收) - * @param scale 缩放比例(0 < scale ≤ 1,如0.8表示缩小到原尺寸的80%) - * @return 缩放后的新Bitmap(若缩放失败返回原始Bitmap,避免空指针) - */ - private Bitmap scaleBitmap(Bitmap originalBitmap, float scale) { - // 边界值校验(规避异常场景) - if (originalBitmap == null || originalBitmap.isRecycled()) { - LogUtils.e(TAG, "【图片缩放】原始Bitmap为空或已回收,缩放失败"); - return originalBitmap; // 返回原始图,避免后续空指针 - } - if (scale <= 0 || scale > 1) { - LogUtils.w(TAG, "【图片缩放】缩放比例无效(scale=" + scale + "),需传入0~1之间的值,直接返回原始图"); - return originalBitmap; - } - if (scale == 1.0f) { - LogUtils.d(TAG, "【图片缩放】缩放比例为1.0,无需缩放,直接返回原始图"); - return originalBitmap; - } - - // Java7 语法实现:按比例计算新宽高(保持宽高比) - int originalWidth = originalBitmap.getWidth(); - int originalHeight = originalBitmap.getHeight(); - int newWidth = Math.round(originalWidth * scale); // 四舍五入取整,避免尺寸异常 - int newHeight = Math.round(originalHeight * scale); - - LogUtils.d(TAG, "【图片缩放】开始缩放:"); - LogUtils.d(TAG, "→ 原始尺寸:" + originalWidth + "x" + originalHeight); - LogUtils.d(TAG, "→ 缩放比例:" + scale); - LogUtils.d(TAG, "→ 目标尺寸:" + newWidth + "x" + newHeight); - - // 核心缩放逻辑(使用Matrix矩阵缩放,Android原生API,兼容Java7,性能稳定) - Bitmap scaledBitmap = null; - Matrix matrix = new Matrix(); - matrix.setScale(scale, scale); // 设置缩放比例(x/y轴统一缩放,保持宽高比) - - try { - // 生成缩放后的Bitmap(过滤无效尺寸,避免生成0x0的空图) - if (newWidth > 0 && newHeight > 0) { - scaledBitmap = Bitmap.createBitmap( - originalBitmap, - 0, 0, // 裁剪起始坐标(0,0表示完整缩放,不裁剪) - originalWidth, originalHeight, // 原始图宽高 - matrix, // 缩放矩阵 - true // 开启抗锯齿,提升缩放后图片清晰度 - ); - LogUtils.d(TAG, "【图片缩放】缩放成功,新图内存大小:" + scaledBitmap.getByteCount() / 1024 + "KB"); - } else { - LogUtils.e(TAG, "【图片缩放】目标尺寸无效(" + newWidth + "x" + newHeight + "),缩放失败"); - scaledBitmap = originalBitmap; // 兜底返回原始图 - } - - } catch (OutOfMemoryError e) { - // 捕获OOM异常(极端情况下缩放仍可能OOM,避免崩溃) - LogUtils.d(TAG, "【图片缩放】OOM异常:" + e.getMessage(), Thread.currentThread().getStackTrace()); - scaledBitmap = originalBitmap; // 兜底返回原始图 - ToastUtils.show("图片缩放失败,可能因图片过大"); - } catch (Exception e) { - // 捕获其他异常(如Bitmap状态异常) - LogUtils.e(TAG, "【图片缩放】未知异常:" + e.getMessage(), e); - scaledBitmap = originalBitmap; // 兜底返回原始图 - } - - // 回收原始Bitmap(仅当新图生成成功且与原图不同时,避免重复回收) - if (scaledBitmap != null && scaledBitmap != originalBitmap && !originalBitmap.isRecycled()) { - originalBitmap.recycle(); - LogUtils.d(TAG, "【图片缩放】已回收原始Bitmap,释放内存"); - } - - return scaledBitmap; - } - - // ======================================== Activity回调处理(选图/拍照/裁剪) ======================================== @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - LogUtils.d(TAG, "【回调触发】onActivityResult 触发,requestCode:" + requestCode + ",resultCode:" + resultCode); + LogUtils.d(TAG, "【回调触发】requestCode:" + requestCode + ",resultCode:" + resultCode); try { - // 分支1:处理 Android13+ 存储管理权限回调(仅对应权限请求码) if (requestCode == PermissionUtils.REQUEST_MANAGE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { handleStoragePermissionCallback(); - return; // 避免进入后续图片回调逻辑 + return; } - // 分支2:处理图片操作回调(选图/拍照/裁剪)- 核心修复 if (resultCode != RESULT_OK) { - // 先处理“操作取消/失败”(所有图片操作的通用逻辑) handleOperationCancelOrFail(); return; } - // 分发对应图片操作的回调(精准匹配 requestCode) switch (requestCode) { case REQUEST_SELECT_PICTURE: - handleSelectPictureResult(resultCode, data); // 选图结果处理(核心修复) + handleSelectPictureResult(resultCode, data); break; case REQUEST_TAKE_PHOTO: - handleTakePhotoResult(resultCode, data); // 拍照结果处理 + handleTakePhotoResult(resultCode, data); break; case REQUEST_CROP_IMAGE: - handleCropImageResult(requestCode, resultCode, data); // 裁剪结果处理(修复拉伸核心) + handleCropImageResult(requestCode, resultCode, data); break; default: - LogUtils.d(TAG, "【回调忽略】未知 requestCode:" + requestCode + ",无需处理"); + LogUtils.d(TAG, "【回调忽略】未知requestCode"); break; } } catch (Exception e) { - LogUtils.e(TAG, "【回调异常】onActivityResult 全局异常:" + e.getMessage(), e); - ToastUtils.show("操作失败,请重试"); + LogUtils.e(TAG, "【回调异常】" + e.getMessage()); + ToastUtils.show("操作失败"); } } - /** - * 单独封装:Android13+ 存储权限回调处理(解耦,提升可维护性) - */ private void handleStoragePermissionCallback() { if (Environment.isExternalStorageManager()) { - LogUtils.d(TAG, "【权限回调】Android13+ 存储管理权限已授予"); - ToastUtils.show("存储权限已获取,可正常使用图片功能"); + LogUtils.d(TAG, "【权限回调】已授予"); + ToastUtils.show("存储权限已获取"); } else { - LogUtils.d(TAG, "【权限回调】Android13+ 存储管理权限已拒绝"); - ToastUtils.show("存储权限不足,部分图片功能可能无法使用"); + LogUtils.d(TAG, "【权限回调】已拒绝"); + ToastUtils.show("存储权限不足"); } } - /** - * 处理拍照回调(调用工具类同步预览) - */ private void handleTakePhotoResult(int resultCode, Intent data) { if (resultCode != RESULT_OK || data == null) { handleOperationCancelOrFail(); return; } - // 校验拍照文件有效性 if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) { - ToastUtils.show("拍照文件不存在或损坏"); + ToastUtils.show("拍照文件无效"); return; } - // 获取Bitmap并压缩保存 Bitmap photoBitmap = getTakePhotoBitmap(data); if (photoBitmap != null && !photoBitmap.isRecycled()) { mBgSourceUtils.compressQualityToRecivedPicture(photoBitmap); @@ -706,677 +408,59 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return; } - // 同步预览并启动裁剪 mBgSourceUtils.saveFileToPreviewBean(mfTakePhoto, mfTakePhoto.getAbsolutePath()); doubleRefreshPreview(); - startCropImageActivity(false); - LogUtils.d(TAG, "【拍照完成】拍照回调处理结束,已启动裁剪"); + // 拍照后启动固定比例裁剪(调用工具类) + ImageCropUtils.startSystemCrop( + this, + mfTakePhoto, + mBackgroundView.getWidth(), + mBackgroundView.getHeight(), + false, + REQUEST_CROP_IMAGE + ); + LogUtils.d(TAG, "【拍照完成】已启动裁剪"); } - /** - * 解析拍照结果为Bitmap(核心用途:拍照回调后,从拍照临时文件/Intent中获取Bitmap,用于后续压缩保存) - * 逻辑:优先从拍照临时文件解析(清晰原图),失败则从Intent获取缩略图(兜底),双重保障避免解析失败 - * @param data 拍照回调的Intent(可能包含缩略图,部分机型不返回) - * @return 解析后的Bitmap(解析失败返回null,避免空指针传播) - */ private Bitmap getTakePhotoBitmap(Intent data) { - LogUtils.d(TAG, "【拍照Bitmap解析】开始解析拍照结果"); - - // 方案1:优先从拍照临时文件解析(推荐,获取清晰原图,避免缩略图模糊) + LogUtils.d(TAG, "【拍照Bitmap解析】开始"); if (mfTakePhoto != null && mfTakePhoto.exists()) { - LogUtils.d(TAG, "【拍照Bitmap解析】优先从临时文件解析:" + mfTakePhoto.getAbsolutePath()); - // 复用已定义的parseCropTempFileToBitmap函数(采样率加载,防OOM,无需重复写逻辑) + LogUtils.d(TAG, "【拍照Bitmap解析】从文件解析"); Bitmap photoBitmap = parseCropTempFileToBitmap(mfTakePhoto); if (photoBitmap != null && !photoBitmap.isRecycled()) { - LogUtils.d(TAG, "【拍照Bitmap解析】从临时文件解析成功"); + LogUtils.d(TAG, "【拍照Bitmap解析】成功"); return photoBitmap; } else { - LogUtils.w(TAG, "【拍照Bitmap解析】从临时文件解析失败,尝试从Intent获取缩略图兜底"); + LogUtils.w(TAG, "【拍照Bitmap解析】文件解析失败,尝试Intent"); } } else { - LogUtils.w(TAG, "【拍照Bitmap解析】拍照临时文件无效,尝试从Intent获取缩略图兜底"); + LogUtils.w(TAG, "【拍照Bitmap解析】文件无效,尝试Intent"); } - // 方案2:从Intent获取缩略图(兜底方案,适配部分机型拍照后不写入临时文件的场景) if (data != null) { try { - // 从Intent中获取缩略图(部分机型仅返回此缩略图,清晰度较低) Bitmap thumbnailBitmap = (Bitmap) data.getParcelableExtra("data"); if (thumbnailBitmap != null && !thumbnailBitmap.isRecycled()) { - LogUtils.d(TAG, "【拍照Bitmap解析】从Intent获取缩略图成功,尺寸:" + thumbnailBitmap.getWidth() + "x" + thumbnailBitmap.getHeight()); + LogUtils.d(TAG, "【拍照Bitmap解析】从Intent获取成功"); return thumbnailBitmap; } else { - LogUtils.e(TAG, "【拍照Bitmap解析】从Intent获取缩略图失败(Bitmap为空或已回收)"); + LogUtils.e(TAG, "【拍照Bitmap解析】Intent解析失败"); } } catch (Exception e) { - // 捕获异常(如Intent数据格式异常、Parcelable解析失败等) - LogUtils.e(TAG, "【拍照Bitmap解析】从Intent解析缩略图异常:" + e.getMessage(), e); + LogUtils.e(TAG, "【拍照Bitmap解析】Intent异常:" + e.getMessage()); } } else { - LogUtils.e(TAG, "【拍照Bitmap解析】拍照回调Intent为空,无法获取缩略图"); + LogUtils.e(TAG, "【拍照Bitmap解析】Intent为空"); } - // 所有方案均失败 - LogUtils.e(TAG, "【拍照Bitmap解析】解析失败(临时文件+Intent均无有效Bitmap)"); - ToastUtils.show("拍照图片解析失败,请重试"); + LogUtils.e(TAG, "【拍照Bitmap解析】失败"); + ToastUtils.show("拍照图片解析失败"); return null; } - /** - * 处理裁剪回调(调用工具类获取裁剪文件) - */ - - - /** - * 处理裁剪回调(核心修改:按裁剪比例强制调整保存图片比例) - */ - - - /** - * 核心新增方法:按裁剪比例强制调整Bitmap尺寸(确保保存比例与裁剪比例一致) - * @param originalBitmap 裁剪后的原始Bitmap - * @param cropAspectRatio 裁剪时设置的宽高比(宽/高) - * @return 按比例调整后的Bitmap - */ - - - /** - * 核心新增方法:将调整比例后的Bitmap保存到文件(覆盖原裁剪文件,确保保存比例正确) - * @param bitmap 按比例调整后的Bitmap - * @param targetFile 目标文件(原裁剪文件路径) - */ - - - /** - * 解析裁剪临时文件为Bitmap(核心用途:裁剪回调后,将裁剪生成的临时文件转为Bitmap,用于后续保存/预览) - * 逻辑:采用采样率加载(避免OOM),支持JPEG/PNG等主流格式,兼容破损文件/空文件场景 - * @param cropTempFile 裁剪后的临时文件(来自工具类getCropTempFile()) - * @return 解析后的Bitmap(解析失败返回null,避免空指针传播) - */ - - - /** - * 处理裁剪回调(核心修改:按裁剪比例强制调整保存图片比例) - */ - - - /** - * 核心新增方法:按裁剪比例强制调整Bitmap尺寸(确保保存比例与裁剪比例一致) - * @param originalBitmap 裁剪后的原始Bitmap - * @param cropAspectRatio 裁剪时设置的宽高比(宽/高) - * @return 按比例调整后的Bitmap - */ - private Bitmap adjustBitmapToCropRatio(Bitmap originalBitmap, float cropAspectRatio) { - LogUtils.d(TAG, "【比例调整】开始按裁剪比例调整Bitmap,目标比例:" + cropAspectRatio); - if (originalBitmap == null || originalBitmap.isRecycled() || cropAspectRatio <= 0) { - LogUtils.e(TAG, "【比例调整】参数无效,直接返回原Bitmap"); - return originalBitmap; - } - - int originalWidth = originalBitmap.getWidth(); - int originalHeight = originalBitmap.getHeight(); - float originalRatio = (float) originalWidth / originalHeight; - - // 若原比例与裁剪比例一致,无需调整 - if (Math.abs(originalRatio - cropAspectRatio) < 0.01f) { - LogUtils.d(TAG, "【比例调整】原比例与裁剪比例一致,无需调整"); - return originalBitmap; - } - - // 按裁剪比例计算目标尺寸(优先匹配原Bitmap的宽/高中较小的维度,避免拉伸) - int targetWidth, targetHeight; - if (originalWidth < originalHeight) { - // 原图宽度更小 → 按宽度适配,计算目标高度 - targetWidth = originalWidth; - targetHeight = Math.round(targetWidth / cropAspectRatio); - } else { - // 原图高度更小 → 按高度适配,计算目标宽度 - targetHeight = originalHeight; - targetWidth = Math.round(targetHeight * cropAspectRatio); - } - - LogUtils.d(TAG, "【比例调整】调整前尺寸:" + originalWidth + "x" + originalHeight + ",调整后尺寸:" + targetWidth + "x" + targetHeight); - - // 按目标尺寸裁剪Bitmap(保持比例,切除多余部分,避免拉伸) - Bitmap adjustedBitmap = Bitmap.createBitmap( - originalBitmap, - (originalWidth - targetWidth) / 2, // 水平居中裁剪 - (originalHeight - targetHeight) / 2, // 垂直居中裁剪 - targetWidth, - targetHeight - ); - - LogUtils.d(TAG, "【比例调整】按裁剪比例调整完成,新比例:" + (float) targetWidth / targetHeight); - return adjustedBitmap; - } - - /** - * 核心新增方法:将调整比例后的Bitmap保存到文件(覆盖原裁剪文件,确保保存比例正确) - * @param bitmap 按比例调整后的Bitmap - * @param targetFile 目标文件(原裁剪文件路径) - */ - - - /** - * 解析裁剪临时文件为Bitmap(核心用途:裁剪回调后,将裁剪生成的临时文件转为Bitmap,用于后续保存/预览) - * 逻辑:采用采样率加载(避免OOM),支持JPEG/PNG等主流格式,兼容破损文件/空文件场景 - * @param cropTempFile 裁剪后的临时文件(来自工具类getCropTempFile()) - * @return 解析后的Bitmap(解析失败返回null,避免空指针传播) - */ - - - /** - * 解析裁剪临时文件为Bitmap(核心用途:裁剪回调后,将裁剪生成的临时文件转为Bitmap,用于后续保存/预览) - * 逻辑:采用采样率加载(避免OOM),支持JPEG/PNG等主流格式,兼容破损文件/空文件场景 - * @param cropTempFile 裁剪后的临时文件(来自工具类getCropTempFile()) - * @return 解析后的Bitmap(解析失败返回null,避免空指针传播) - */ - - - /** - * 双重刷新预览(核心用途:适配MIUI等机型裁剪/选图后预览渲染延迟,解决“图片已保存但界面空白”问题) - * 逻辑:先立即刷新一次,再延迟短时间二次刷新,确保控件能获取到最新的图片文件(规避异步渲染/文件写入延迟) - */ - private void doubleRefreshPreview() { - LogUtils.d(TAG, "【双重刷新】开始执行预览双重刷新(适配机型渲染延迟)"); - - // 1. 立即刷新(第一重:优先加载最新图片) - if (mBackgroundView != null && !isFinishing()) { - mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); - LogUtils.d(TAG, "【双重刷新】第一重刷新完成(立即执行)"); - } else { - LogUtils.w(TAG, "【双重刷新】第一重刷新跳过:预览控件为空或页面即将销毁"); - return; - } - - // 2. 延迟刷新(第二重:适配MIUI等机型渲染延迟,默认50ms,可根据机型微调) - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - // 校验:页面未销毁+控件有效,才执行二次刷新 - if (mBackgroundView != null && !isFinishing()) { - mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); - LogUtils.d(TAG, "【双重刷新】第二重刷新完成(延迟50ms执行)"); - } else { - LogUtils.w(TAG, "【双重刷新】第二重刷新跳过:页面已销毁或控件无效"); - } - } - }, 50); // 延迟时间:50ms(兼顾性能与兼容性,无需修改) - } - - /** - * 处理选图回调(修复3大问题:双路径绑定+文件可读性校验+压缩路径强绑定) - */ - private void handleSelectPictureResult(int resultCode, Intent data) { - if (resultCode != RESULT_OK || data == null) { - handleOperationCancelOrFail(); - return; - } - - Uri selectedImage = data.getData(); - if (selectedImage == null) { - ToastUtils.show("选择的图片Uri为空"); - return; - } - LogUtils.d(TAG, "【选图回调】选择图片Uri : " + selectedImage.toString()); - - // 授予持久化权限(Android4.4+) - grantPersistableUriPermission(selectedImage); - - // 关键修复1:解析Uri为文件(强制使用独立的选图临时文件,避免路径混淆) - mBgSourceUtils.createCropFileProviderPath(selectedImage); - - // 关键:强制绑定压缩图路径到BackgroundCrops目录(统一存储,避免路径错乱) - String targetCompressPath = mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath(); - - LogUtils.d(TAG, "【选图同步】预览Bean双路径绑定完成:"); - LogUtils.d(TAG, "→ 原图路径(选图临时文件):" + mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); - LogUtils.d(TAG, "→ 压缩图路径 :" + targetCompressPath); - - startCropImageActivity(false); - } - - /** - * 处理所有操作取消/失败(统一清理+提示) - */ - private void handleOperationCancelOrFail() { - initBackgroundViewByPreviewBean(); - LogUtils.d(TAG, "【操作回调】操作取消或失败"); - ToastUtils.show("操作已取消"); - } - - // ======================================== 权限回调(转发给工具类处理) ======================================== - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - LogUtils.d(TAG, "【权限回调】Activity回调触发,转发给PermissionUtils处理"); - // 调用权限工具类统一处理回调(自动过滤非存储权限) - mPermissionUtils.handleStoragePermissionResult(this, requestCode, permissions, grantResults); - } - - // ======================================== 辅助工具方法(私有,封装细节) ======================================== - /** - * 辅助函数:为选图Uri添加持久化读取权限(Android4.4+) - */ - private void grantPersistableUriPermission(Uri uri) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - getContentResolver().takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION - ); - LogUtils.d(TAG, "【选图权限】已添加持久化读取权限"); - } - } - - @Override - public void finish() { - if(isCommitSettings) { - super.finish(); - } else{ - YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener(){ - - @Override - public void onYes() { - mBgSourceUtils.commitPreviewSourceToCurrent(); - isCommitSettings = true; - finish(); - } - - @Override - public void onNo() { - isCommitSettings = true; - finish(); - } - }); - } - } - /** - * 处理裁剪回调(核心修改:完全依赖系统裁剪结果,独立计算比例,不与BackgroundView互传) - */ -// private void handleCropImageResult(int requestCode, int resultCode, Intent data) { -// LogUtils.d(TAG, "handleCropImageResult"); -// // 从工具类获取系统裁剪输出的临时文件(比例由系统裁剪应用决定,仅读取此文件) -// File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath()); -// boolean isFileExist = cropTempFile.exists(); -// boolean isFileReadable = isFileExist ? cropTempFile.canRead() : false; -// long fileSize = isFileExist ? cropTempFile.length() : 0; -// // 适配MIUI:仅resultCode=RESULT_OK且文件有效时视为成功 -// boolean isCropSuccess = (resultCode == RESULT_OK) && isFileExist && isFileReadable && fileSize > 100; -// if (isCropSuccess) { -// LogUtils.d(TAG, "isCropSuccess == true,按系统裁剪文件比例保存"); -// final BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); -// previewBean.setIsUseBackgroundFile(true); -// previewBean.setIsUseBackgroundScaledCompressFile(true); -// mBgSourceUtils.saveSettings(); -// -// // 核心修改1:解析系统裁剪文件,获取系统给出的真实宽高比(独立计算,不依赖BackgroundView) -// float systemCropRatio = getRatioFromSystemCropFile(cropTempFile); -// if (systemCropRatio > 0) { -// // 核心:按系统裁剪比例调整Bitmap并覆盖保存(确保保存比例=系统裁剪比例) -// Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile); -// if (cropBitmap != null && !cropBitmap.isRecycled()) { -// // 按系统裁剪比例调整尺寸(仅对齐比例,不改变系统裁剪的主体内容) -// Bitmap scaledCropBitmap = adjustBitmapToSystemCropRatio(cropBitmap, systemCropRatio); -// // 覆盖保存到原裁剪文件(替换系统输出的文件,确保最终保存比例正确) -// saveScaledBitmapToFile(scaledCropBitmap, cropTempFile); -// // 回收临时Bitmap,避免内存泄漏 -// if (scaledCropBitmap != cropBitmap && !scaledCropBitmap.isRecycled()) { -// scaledCropBitmap.recycle(); -// } -// if (!cropBitmap.isRecycled()) { -// cropBitmap.recycle(); -// } -// } -// } -// -// // 延迟加载裁剪图(仅通知加载,不传递比例,BackgroundView自主读取文件计算比例) -// new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { -// @Override -// public void run() { -// if (mBackgroundView != null && !isFinishing()) { -// mBackgroundView.loadBackgroundBean(previewBean); // 仅传Bean(含文件路径),无比例传递 -// LogUtils.d(TAG, "裁剪后图片加载完成(延迟50ms,BackgroundView自主解析比例)"); -// } -// } -// }, 50); // 延迟50ms,确保系统裁剪文件写入完成 -// -// } else { -// // 其他失败场景 -// handleOperationCancelOrFail(); -// } -// } - - /** - * 核心新增方法:从系统裁剪文件中获取真实宽高比(独立计算,完全由系统裁剪应用决定) - * 不依赖任何外部类/参数,仅读取系统裁剪输出的文件尺寸 - * @param systemCropFile 系统裁剪应用输出的文件 - * @return 系统裁剪给出的宽高比(宽/高,失败返回-1) - */ -// private float getRatioFromSystemCropFile(File systemCropFile) { -// LogUtils.d(TAG, "【系统比例获取】从系统裁剪文件获取真实比例,文件路径:" + systemCropFile.getAbsolutePath()); -// if (systemCropFile == null || !systemCropFile.exists() || !systemCropFile.isFile()) { -// LogUtils.e(TAG, "【系统比例获取】系统裁剪文件无效,无法获取比例"); -// return -1; -// } -// -// // Java7 语法:仅获取文件尺寸(不加载Bitmap,高效计算比例) -// BitmapFactory.Options options = new BitmapFactory.Options(); -// options.inJustDecodeBounds = true; // 仅读取尺寸,不占用内存 -// BitmapFactory.decodeFile(systemCropFile.getAbsolutePath(), options); -// -// int cropWidth = options.outWidth; -// int cropHeight = options.outHeight; -// if (cropWidth <= 0 || cropHeight <= 0) { -// LogUtils.e(TAG, "【系统比例获取】系统裁剪文件尺寸无效(宽:" + cropWidth + ",高:" + cropHeight + ")"); -// return -1; -// } -// -// // 计算系统裁剪给出的真实宽高比(宽/高) -// float systemRatio = (float) cropWidth / cropHeight; -// LogUtils.d(TAG, "【系统比例获取】成功,系统裁剪比例:" + systemRatio + "(宽:" + cropWidth + ",高:" + cropHeight + ")"); -// return systemRatio; -// } - - /** - * 核心新增方法:按系统裁剪比例调整Bitmap(仅对齐比例,不改变系统裁剪的主体内容) - * 独立方法,不依赖BackgroundView,确保2个类完全解耦 - * @param originalBitmap 系统裁剪输出的原始Bitmap - * @param systemCropRatio 系统裁剪给出的宽高比(宽/高) - * @return 按系统比例调整后的Bitmap - */ - private Bitmap adjustBitmapToSystemCropRatio(Bitmap originalBitmap, float systemCropRatio) { - LogUtils.d(TAG, "【系统比例调整】按系统裁剪比例对齐,目标比例:" + systemCropRatio); - if (originalBitmap == null || originalBitmap.isRecycled() || systemCropRatio <= 0) { - LogUtils.e(TAG, "【系统比例调整】参数无效,直接返回原Bitmap"); - return originalBitmap; - } - - int originalWidth = originalBitmap.getWidth(); - int originalHeight = originalBitmap.getHeight(); - float originalRatio = (float) originalWidth / originalHeight; - - // 若原比例与系统裁剪比例一致,无需调整(避免重复操作) - if (Math.abs(originalRatio - systemCropRatio) < 0.01f) { - LogUtils.d(TAG, "【系统比例调整】与系统裁剪比例一致,无需调整"); - return originalBitmap; - } - - // 按系统比例计算目标尺寸(居中裁剪,确保系统裁剪的主体内容不丢失) - int targetWidth, targetHeight; - if (originalRatio > systemCropRatio) { - // 原图更宽 → 按系统比例缩小宽度,保留高度(切除左右多余部分) - targetHeight = originalHeight; - targetWidth = Math.round(targetHeight * systemCropRatio); - } else { - // 原图更高 → 按系统比例缩小高度,保留宽度(切除上下多余部分) - targetWidth = originalWidth; - targetHeight = Math.round(targetWidth / systemCropRatio); - } - - LogUtils.d(TAG, "【系统比例调整】系统裁剪后尺寸:" + originalWidth + "x" + originalHeight + ",对齐后尺寸:" + targetWidth + "x" + targetHeight); - - // 居中裁剪(仅切除多余像素,不拉伸,保留系统裁剪的主体) - Bitmap adjustedBitmap = Bitmap.createBitmap( - originalBitmap, - (originalWidth - targetWidth) / 2, // 水平居中 - (originalHeight - targetHeight) / 2, // 垂直居中 - targetWidth, - targetHeight - ); - - LogUtils.d(TAG, "【系统比例调整】完成,最终比例与系统裁剪一致:" + (float) targetWidth / targetHeight); - return adjustedBitmap; - } - - /** - * 核心新增方法:将调整后的Bitmap保存到文件(覆盖系统裁剪输出的文件) - * 独立保存逻辑,不依赖任何外部类 - * @param bitmap 按系统比例调整后的Bitmap - * @param targetFile 系统裁剪输出的目标文件(覆盖保存) - */ -// private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) { -// LogUtils.d(TAG, "【图片保存】按系统裁剪比例保存,目标路径:" + targetFile.getAbsolutePath()); -// if (bitmap == null || bitmap.isRecycled() || targetFile == null) { -// LogUtils.e(TAG, "【图片保存】参数无效,保存失败"); -// return; -// } -// -// // 删除原系统裁剪文件(避免覆盖失败) -// if (targetFile.exists()) { -// boolean deleteSuccess = targetFile.delete(); -// LogUtils.d(TAG, "【图片保存】删除原系统裁剪文件:" + (deleteSuccess ? "成功" : "失败")); -// } -// -// // Java7 语法:保存Bitmap(JPEG格式,质量80%,平衡清晰度与文件大小) -// OutputStream outputStream = null; -// try { -// outputStream = new BufferedOutputStream(new FileOutputStream(targetFile)); -// bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream); -// outputStream.flush(); -// LogUtils.d(TAG, "【图片保存】成功,文件大小:" + targetFile.length() + "bytes,比例与系统裁剪一致"); -// } catch (IOException e) { -// LogUtils.e(TAG, "【图片保存】异常:" + e.getMessage(), e); -// } finally { -// // 手动关闭流(Java7 兼容写法,不使用try-with-resources) -// if (outputStream != null) { -// try { -// outputStream.close(); -// } catch (IOException e) { -// LogUtils.e(TAG, "【图片保存】关闭流异常:" + e.getMessage(), e); -// } -// } -// } -// } - - /** - * 解析裁剪临时文件为Bitmap(独立方法,仅处理系统裁剪文件,不依赖BackgroundView) - * @param cropTempFile 系统裁剪应用输出的临时文件 - * @return 解析后的Bitmap(失败返回null) - */ -// private Bitmap parseCropTempFileToBitmap(File cropTempFile) { -// // 第一重校验:文件对象有效性(规避空指针/非文件) -// if (cropTempFile == null) { -// LogUtils.e(TAG, "【裁剪文件解析】裁剪临时文件对象为空,解析失败"); -// return null; -// } -// if (!cropTempFile.exists()) { -// LogUtils.e(TAG, "【裁剪文件解析】裁剪临时文件不存在,路径:" + cropTempFile.getAbsolutePath()); -// return null; -// } -// if (!cropTempFile.isFile()) { -// LogUtils.e(TAG, "【裁剪文件解析】路径不是合法文件,路径:" + cropTempFile.getAbsolutePath()); -// return null; -// } -// if (cropTempFile.length() <= 100) { // 过滤小于100字节的空文件/破损文件 -// LogUtils.e(TAG, "【裁剪文件解析】裁剪文件过小(" + cropTempFile.length() + "bytes),视为无效文件"); -// return null; -// } -// -// LogUtils.d(TAG, "【裁剪文件解析】开始解析系统裁剪文件:" + cropTempFile.getAbsolutePath() + ",大小:" + cropTempFile.length() + "bytes"); -// -// // Java7 语法:Bitmap采样率加载(避免OOM) -// BitmapFactory.Options options = new BitmapFactory.Options(); -// // 第一步:仅获取图片尺寸,不加载完整Bitmap到内存 -// options.inJustDecodeBounds = true; -// BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); -// -// // 第二步:计算采样率(最大尺寸计算采样率(最大尺寸限制为2048px,适配所有机型) -// int maxSize = 2048; -// int sampleSize = 1; -// while (options.outWidth / sampleSize > maxSize || options.outHeight / sampleSize > maxSize) { -// sampleSize *= 2; -// } -// sampleSize = Math.min(sampleSize, 16); // 限制最大采样率,避免模糊 -// LogUtils.d(TAG, "【裁剪文件解析】系统裁剪文件原始尺寸:" + options.outWidth + "x" + options.outHeight + ",采样率:" + sampleSize); -// -// // 第三步:设置采样率,加载完整Bitmap -// options.inJustDecodeBounds = false; -// options.inSampleSize = sampleSize; -// options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存 -// options.inPurgeable = true; -// options.inInputShareable = true; -// -// // 第四步:解析文件为Bitmap(捕获异常) -// Bitmap cropBitmap = null; -// try { -// cropBitmap = BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); -// if (cropBitmap == null || cropBitmap.isRecycled()) { -// LogUtils.e(TAG, "【裁剪文件解析】解析失败,文件可能破损"); -// return null; -// } -// LogUtils.d(TAG, "【裁剪文件解析】成功,Bitmap内存大小:" + cropBitmap.getByteCount() / 1024 + "KB"); -// } catch (OutOfMemoryError e) { -// LogUtils.d(TAG, "【裁剪文件解析】OOM异常:" + e.getMessage(), Thread.currentThread().getStackTrace()); -// ToastUtils.show("图片解析失败,可能因图片过大"); -// return null; -// } catch (Exception e) { -// LogUtils.e(TAG, "【裁剪文件解析】未知异常:" + e.getMessage(), e); -// return null; -// } -// -// return cropBitmap; -// } - - - /** - * 启动系统裁剪(Java7 语法,记录接口比例参数,用于后续校验) - * @param isCropFree 是否自由裁剪(true:无固定比例,false:按视图比例) - */ - public void startCropImageActivity(boolean isCropFree) { - LogUtils.d(TAG, "startCropImageActivity: 启动系统裁剪,自由裁剪:" + isCropFree); - BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); - previewBean.setIsUseBackgroundScaledCompressFile(false); - mBgSourceUtils.saveSettings(); - doubleRefreshPreview(); - - // 校验裁剪原图 - File previewFile = new File(previewBean.getBackgroundFilePath()); - if (previewFile == null || !previewFile.exists() || previewFile.length() <= 100) { - Toast.makeText(this, "无有效图片可裁剪", Toast.LENGTH_SHORT).show(); - LogUtils.e(TAG, "startCropImageActivity: 裁剪原图无效"); - return; - } - - // 生成裁剪输入Uri(适配MIUI权限) - Uri inputUri = null; - try { - inputUri = mBgSourceUtils.getFileProviderUri(previewFile); - // 授予系统裁剪工具权限(Java7 手动授予) - grantUriPermission("com.miui.gallery", inputUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - LogUtils.d(TAG, "startCropImageActivity: 输入Uri:" + inputUri.toString() + ",已授予MIUI权限"); - } catch (Exception e) { - LogUtils.e(TAG, "startCropImageActivity: 生成输入Uri失败:" + e.getMessage()); - Toast.makeText(this, "裁剪失败:无法获取图片权限", Toast.LENGTH_SHORT).show(); - return; - } - - // 生成裁剪输出临时文件 - File cropResultTempFile = new File(previewBean.getBackgroundScaledCompressFilePath()); - if (cropResultTempFile == null) { - Toast.makeText(this, "裁剪路径创建失败", Toast.LENGTH_SHORT).show(); - return; - } - - // 构建裁剪意图(Java7 语法,不使用Lambda) - Intent intent = new Intent("com.android.camera.action.CROP"); - intent.setDataAndType(inputUri, "image/*"); - intent.putExtra("crop", "true"); - intent.putExtra("noFaceDetection", true); - - // 关键:设置裁剪比例并记录(仅本类使用,不跨类传递) - if (!isCropFree) { - // 按视图尺寸计算比例(Java7 手动获取尺寸) - int viewWidth = mBackgroundView.getWidth() > 0 ? mBackgroundView.getWidth() : getResources().getDisplayMetrics().widthPixels; - int viewHeight = mBackgroundView.getHeight() > 0 ? mBackgroundView.getHeight() : getResources().getDisplayMetrics().heightPixels; - int gcd = calculateGCD(viewWidth, viewHeight); - // 记录传入系统裁剪的比例参数(临时变量,仅内部使用) - mCropAspectX = viewWidth / gcd; - mCropAspectY = viewHeight / gcd; - intent.putExtra("aspectX", mCropAspectX); - intent.putExtra("aspectY", mCropAspectY); - LogUtils.d(TAG, "startCropImageActivity: 传入系统裁剪接口比例:" + mCropAspectX + ":" + mCropAspectY); - } else { - // 自由裁剪:重置比例参数 - mCropAspectX = 0; - mCropAspectY = 0; - LogUtils.d(TAG, "startCropImageActivity: 启用自由裁剪(无固定比例)"); - } - - // 输出尺寸设置(Java7 手动计算) - int maxOutputSize = 2048; - int outputX = Math.min(getResources().getDisplayMetrics().widthPixels, maxOutputSize); - int outputY = Math.min(getResources().getDisplayMetrics().heightPixels, maxOutputSize); - intent.putExtra("outputX", outputX); - intent.putExtra("outputY", outputY); - intent.putExtra("scale", true); - intent.putExtra("scaleUpIfNeeded", true); - - // 输出配置(Java7 枚举值手动设置) - intent.putExtra("outputFormat", CompressFormat.JPEG.toString()); - intent.putExtra("quality", 100); // 100%质量,避免压缩偏差 - intent.putExtra("return-data", false); // 禁用返回Bitmap,避免OOM - - // 权限设置(Java7 手动添加Flags) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - } - - // 适配系统裁剪工具(Java7 手动遍历解析) - try { - List resolveInfos = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - if (!resolveInfos.isEmpty()) { - ResolveInfo resolveInfo = resolveInfos.get(0); - String cropPackageName = resolveInfo.activityInfo.packageName; - String cropActivityName = resolveInfo.activityInfo.name; - Log.d(TAG, "startCropImageActivity: 找到裁剪工具:" + cropPackageName + "/" + cropActivityName); - - // 生成输出Uri并授予权限 - Uri outputUri = mBgSourceUtils.getFileProviderUri(cropResultTempFile); - if (cropPackageName.equals("com.miui.gallery")) { - grantUriPermission(cropPackageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - Log.d(TAG, "startCropImageActivity: 授予MIUI裁剪工具写入权限"); - } - - // 显式启动裁剪Activity(Java7 手动创建Component) - Intent cropIntent = new Intent(intent); - cropIntent.setComponent(new ComponentName(cropPackageName, cropActivityName)); - cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); - cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - // 保存输出路径 - previewBean.setBackgroundScaledCompressFilePath(cropResultTempFile.getAbsolutePath()); - startActivityForResult(cropIntent, REQUEST_CROP_IMAGE); - Log.d(TAG, "startCropImageActivity: 启动系统裁剪,输出路径:" + cropResultTempFile.getAbsolutePath()); - } else { - // 兜底:启动第三方裁剪工具 - Uri outputUri = mBgSourceUtils.getFileProviderUri(cropResultTempFile); - intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); - Intent chooser = Intent.createChooser(intent, "选择裁剪工具"); - chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - previewBean.setBackgroundScaledCompressFilePath(cropResultTempFile.getAbsolutePath()); - if (chooser.resolveActivity(getPackageManager()) != null) { - startActivityForResult(chooser, REQUEST_CROP_IMAGE); - } else { - Toast.makeText(this, "无可用裁剪工具", Toast.LENGTH_SHORT).show(); - } - } - } catch (Exception e) { - Log.e(TAG, "startCropImageActivity: 启动裁剪失败:" + e.getMessage()); - Toast.makeText(this, "无法启动裁剪工具", Toast.LENGTH_SHORT).show(); - } - } - - - /** - * 处理裁剪结果(核心:闭环校验比例,确保系统接口→系统输出→保存文件 比例一致) - */ private void handleCropImageResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, "handleCropImageResult: 处理裁剪结果"); - // 获取系统裁剪输出文件(仅读取文件,不依赖外部比例) File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath()); boolean isFileExist = cropTempFile.exists(); boolean isFileReadable = isFileExist ? cropTempFile.canRead() : false; @@ -1384,39 +468,19 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg boolean isCropSuccess = (resultCode == RESULT_OK) && isFileExist && isFileReadable && fileSize > 100; if (isCropSuccess) { - Log.d(TAG, "handleCropImageResult: 裁剪成功,开始比例校验与保存"); + Log.d(TAG, "handleCropImageResult: 裁剪成功"); BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); previewBean.setIsUseBackgroundFile(true); previewBean.setIsUseBackgroundScaledCompressFile(true); mBgSourceUtils.saveSettings(); - // 核心步骤1:获取两个关键比例(接口参数比例 + 系统输出比例) - float finalCropRatio = 0f; - float systemFileRatio = getRatioFromSystemCropFile(cropTempFile); // 系统输出文件比例 - float inputInterfaceRatio = (mCropAspectX > 0 && mCropAspectY > 0) ? (float) mCropAspectX / mCropAspectY : systemFileRatio; // 接口参数比例 - - // 核心步骤2:比例校验与确认(Java7 手动判断,不使用Lambda) + float systemFileRatio = getRatioFromSystemCropFile(cropTempFile); if (systemFileRatio > 0) { - if (inputInterfaceRatio > 0 && Math.abs(inputInterfaceRatio - systemFileRatio) < 0.001f) { - finalCropRatio = inputInterfaceRatio; - Log.d(TAG, "handleCropImageResult: 比例校验通过,接口与系统输出一致:" + finalCropRatio); - } else if (inputInterfaceRatio > 0 && Math.abs(inputInterfaceRatio - systemFileRatio) >= 0.001f) { - finalCropRatio = systemFileRatio; - Log.w(TAG, "handleCropImageResult: 接口与系统输出偏差,以系统为准:" + finalCropRatio); - } else { - finalCropRatio = systemFileRatio; - Log.d(TAG, "handleCropImageResult: 自由裁剪,使用系统比例:" + finalCropRatio); - } - } - - // 核心步骤3:按最终比例调整并保存(确保比例一致) - if (finalCropRatio > 0) { Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile); if (cropBitmap != null && !cropBitmap.isRecycled()) { - Bitmap scaledCropBitmap = adjustBitmapToFinalRatio(cropBitmap, finalCropRatio); + Bitmap scaledCropBitmap = adjustBitmapToFinalRatio(cropBitmap, systemFileRatio); saveScaledBitmapToFile(scaledCropBitmap, cropTempFile); - // 回收临时Bitmap(Java7 手动回收) if (scaledCropBitmap != cropBitmap && !scaledCropBitmap.isRecycled()) { scaledCropBitmap.recycle(); } @@ -1426,13 +490,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } - // 延迟加载图片(仅传文件路径,BackgroundView自主解析) new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { if (mBackgroundView != null && !isFinishing()) { mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); - Log.d(TAG, "handleCropImageResult: 裁剪图片加载完成,比例已与系统一致"); + Log.d(TAG, "handleCropImageResult: 裁剪图片加载完成"); } } }, 50); @@ -1441,11 +504,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } - /** - * 从系统裁剪文件获取比例(Java7 语法,完全独立,不依赖外部) - */ private float getRatioFromSystemCropFile(File systemCropFile) { - Log.d(TAG, "getRatioFromSystemCropFile: 读取系统裁剪文件比例,路径:" + systemCropFile.getAbsolutePath()); + Log.d(TAG, "getRatioFromSystemCropFile: 读取比例"); if (systemCropFile == null || !systemCropFile.exists() || !systemCropFile.isFile()) { Log.e(TAG, "getRatioFromSystemCropFile: 文件无效"); return -1; @@ -1458,20 +518,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg int cropWidth = options.outWidth; int cropHeight = options.outHeight; if (cropWidth <= 0 || cropHeight <= 0) { - Log.e(TAG, "getRatioFromSystemCropFile: 尺寸无效:" + cropWidth + "x" + cropHeight); + Log.e(TAG, "getRatioFromSystemCropFile: 尺寸无效"); return -1; } float systemRatio = (float) cropWidth / cropHeight; - Log.d(TAG, "getRatioFromSystemCropFile: 系统裁剪比例:" + systemRatio + "(" + cropWidth + "x" + cropHeight + ")"); + Log.d(TAG, "getRatioFromSystemCropFile: 比例:" + systemRatio); return systemRatio; } - /** - * 按最终比例调整Bitmap(Java7 语法,高精度对齐) - */ private Bitmap adjustBitmapToFinalRatio(Bitmap originalBitmap, float finalCropRatio) { - Log.d(TAG, "adjustBitmapToFinalRatio: 调整比例,目标:" + finalCropRatio); + Log.d(TAG, "adjustBitmapToFinalRatio: 调整比例"); if (originalBitmap == null || originalBitmap.isRecycled() || finalCropRatio <= 0) { Log.e(TAG, "adjustBitmapToFinalRatio: 参数无效"); return originalBitmap; @@ -1481,13 +538,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg int originalHeight = originalBitmap.getHeight(); float originalRatio = (float) originalWidth / originalHeight; - // 偏差≤0.001,视为一致,无需调整 if (Math.abs(originalRatio - finalCropRatio) < 0.001f) { - Log.d(TAG, "adjustBitmapToFinalRatio: 比例一致,无需调整"); + Log.d(TAG, "adjustBitmapToFinalRatio: 比例一致"); return originalBitmap; } - // 计算目标尺寸(Java7 手动计算,精确到1px) int targetWidth, targetHeight; targetHeight = originalHeight; targetWidth = Math.round(targetHeight * finalCropRatio); @@ -1496,11 +551,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg targetHeight = Math.round(targetWidth / finalCropRatio); } - // 强制修正尺寸,确保比例完全一致 targetWidth = Math.round(targetHeight * finalCropRatio); Log.d(TAG, "adjustBitmapToFinalRatio: 调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight); - // 居中裁剪(Java7 手动计算偏移量) Bitmap adjustedBitmap = Bitmap.createBitmap( originalBitmap, (originalWidth - targetWidth) / 2, @@ -1509,27 +562,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg targetHeight ); - // 最终校验 - float adjustedRatio = (float) adjustedBitmap.getWidth() / adjustedBitmap.getHeight(); - if (Math.abs(adjustedRatio - finalCropRatio) < 0.001f) { - Log.d(TAG, "adjustBitmapToFinalRatio: 调整完成,比例一致"); - } else { - Log.w(TAG, "adjustBitmapToFinalRatio: 存在微小偏差,已强制修正"); - } return adjustedBitmap; } - /** - * 保存调整后的Bitmap(Java7 语法,100%质量,避免压缩偏差) - */ private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) { - Log.d(TAG, "saveScaledBitmapToFile: 保存图片,路径:" + targetFile.getAbsolutePath()); + Log.d(TAG, "saveScaledBitmapToFile: 保存图片"); if (bitmap == null || bitmap.isRecycled() || targetFile == null) { Log.e(TAG, "saveScaledBitmapToFile: 参数无效"); return; } - // 删除原文件(Java7 手动删除) if (targetFile.exists()) { boolean deleteSuccess = targetFile.delete(); Log.d(TAG, "saveScaledBitmapToFile: 删除原文件:" + (deleteSuccess ? "成功" : "失败")); @@ -1537,38 +579,25 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg OutputStream outputStream = null; try { - // Java7 手动创建流,不使用try-with-resources outputStream = new BufferedOutputStream(new FileOutputStream(targetFile)); - bitmap.compress(CompressFormat.JPEG, 100, outputStream); // 100%质量 + bitmap.compress(CompressFormat.JPEG, 100, outputStream); outputStream.flush(); - Log.d(TAG, "saveScaledBitmapToFile: 保存成功,文件大小:" + targetFile.length() + "bytes"); - - // 保存后校验比例 - float savedRatio = getRatioFromSystemCropFile(targetFile); - if (savedRatio > 0 && Math.abs(savedRatio - (float) bitmap.getWidth()/bitmap.getHeight()) < 0.001f) { - Log.d(TAG, "saveScaledBitmapToFile: 保存后比例校验通过"); - } else { - Log.w(TAG, "saveScaledBitmapToFile: 保存后比例存在偏差"); - } + Log.d(TAG, "saveScaledBitmapToFile: 保存成功"); } catch (IOException e) { - Log.e(TAG, "saveScaledBitmapToFile: 保存异常:" + e.getMessage()); + Log.e(TAG, "saveScaledBitmapToFile: 异常:" + e.getMessage()); } finally { - // Java7 手动关闭流,避免资源泄漏 if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { - Log.e(TAG, "saveScaledBitmapToFile: 关闭流异常:" + e.getMessage()); + Log.e(TAG, "saveScaledBitmapToFile: 关闭流异常"); } } } } - /** - * 解析裁剪文件为Bitmap(Java7 语法,采样率加载) - */ private Bitmap parseCropTempFileToBitmap(File cropTempFile) { - Log.d(TAG, "parseCropTempFileToBitmap: 解析裁剪文件:" + cropTempFile.getAbsolutePath()); + Log.d(TAG, "parseCropTempFileToBitmap: 解析文件"); if (cropTempFile == null || !cropTempFile.exists() || !cropTempFile.isFile() || cropTempFile.length() <= 100) { Log.e(TAG, "parseCropTempFileToBitmap: 文件无效"); return null; @@ -1578,14 +607,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg options.inJustDecodeBounds = true; BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); - // 计算采样率(Java7 普通while循环) int maxSize = 2048; int sampleSize = 1; while (options.outWidth / sampleSize > maxSize || options.outHeight / sampleSize > maxSize) { sampleSize *= 2; } sampleSize = Math.min(sampleSize, 16); - Log.d(TAG, "parseCropTempFileToBitmap: 采样率:" + sampleSize + ",原始尺寸:" + options.outWidth + "x" + options.outHeight); + Log.d(TAG, "parseCropTempFileToBitmap: 采样率:" + sampleSize); options.inJustDecodeBounds = false; options.inSampleSize = sampleSize; @@ -1600,18 +628,109 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg Log.e(TAG, "parseCropTempFileToBitmap: 解析失败"); return null; } - Log.d(TAG, "parseCropTempFileToBitmap: 解析成功,尺寸:" + cropBitmap.getWidth() + "x" + cropBitmap.getHeight()); + Log.d(TAG, "parseCropTempFileToBitmap: 解析成功"); } catch (OutOfMemoryError e) { - Log.e(TAG, "parseCropTempFileToBitmap: OOM异常:" + e.getMessage()); - Toast.makeText(this, "图片解析失败,可能过大", Toast.LENGTH_SHORT).show(); + Log.e(TAG, "parseCropTempFileToBitmap: OOM"); + Toast.makeText(this, "图片解析失败", Toast.LENGTH_SHORT).show(); return null; } catch (Exception e) { - Log.e(TAG, "parseCropTempFileToBitmap: 解析异常:" + e.getMessage()); + Log.e(TAG, "parseCropTempFileToBitmap: 异常:" + e.getMessage()); return null; } return cropBitmap; } + private void doubleRefreshPreview() { + LogUtils.d(TAG, "【双重刷新】开始"); + if (mBackgroundView != null && !isFinishing()) { + mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); + LogUtils.d(TAG, "【双重刷新】第一重完成"); + } else { + LogUtils.w(TAG, "【双重刷新】跳过"); + return; + } + + new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { + @Override + public void run() { + if (mBackgroundView != null && !isFinishing()) { + mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); + LogUtils.d(TAG, "【双重刷新】第二重完成"); + } + } + }, 50); + } + + private void handleSelectPictureResult(int resultCode, Intent data) { + if (resultCode != RESULT_OK || data == null) { + handleOperationCancelOrFail(); + return; + } + + Uri selectedImage = data.getData(); + if (selectedImage == null) { + ToastUtils.show("图片Uri为空"); + return; + } + LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + getContentResolver().takePersistableUriPermission( + selectedImage, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ); + LogUtils.d(TAG, "【选图权限】已添加持久化权限"); + } + + mBgSourceUtils.createCropFileProviderPath(selectedImage); + String targetCompressPath = mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath(); + + LogUtils.d(TAG, "【选图同步】路径绑定完成"); + // 选图后启动固定比例裁剪(调用工具类) + ImageCropUtils.startSystemCrop( + this, + new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()), + mBackgroundView.getWidth(), + mBackgroundView.getHeight(), + false, + REQUEST_CROP_IMAGE + ); + } + + private void handleOperationCancelOrFail() { + initBackgroundViewByPreviewBean(); + LogUtils.d(TAG, "【操作回调】取消或失败"); + ToastUtils.show("操作已取消"); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + LogUtils.d(TAG, "【权限回调】转发处理"); + mPermissionUtils.handleStoragePermissionResult(this, requestCode, permissions, grantResults); + } + + @Override + public void finish() { + if(isCommitSettings) { + super.finish(); + } else{ + YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener(){ + @Override + public void onYes() { + mBgSourceUtils.commitPreviewSourceToCurrent(); + isCommitSettings = true; + finish(); + } + + @Override + public void onNo() { + isCommitSettings = true; + finish(); + } + }); + } + } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageCropUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageCropUtils.java new file mode 100644 index 00000000..c56dfa2d --- /dev/null +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageCropUtils.java @@ -0,0 +1,148 @@ +package cc.winboll.studio.powerbell.utils; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.util.Log; +import android.widget.Toast; +import androidx.core.content.FileProvider; +import java.io.File; +import java.util.List; +import cc.winboll.studio.powerbell.App; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/12/04 06:45 + * @Describe 图片裁剪工具类(仅调用系统裁剪,传入比例/自由裁剪参数) + */ +public class ImageCropUtils { + private static final String TAG = "ImageCropUtils"; + // FileProvider授权(与清单文件一致) + + /** + * 启动系统裁剪工具 + * @param activity 上下文 + * @param srcFile 裁剪原图 + * @param aspectX 裁剪宽比例(自由裁剪传0) + * @param aspectY 裁剪高比例(自由裁剪传0) + * @param isFreeCrop 是否自由裁剪 + * @param requestCode 裁剪请求码 + */ + public static void startSystemCrop(Activity activity, File srcFile, int aspectX, int aspectY, boolean isFreeCrop, int requestCode) { + + Log.d(TAG, "startSystemCrop: 启动系统裁剪,自由裁剪:" + isFreeCrop); + // 校验原图 + if (srcFile == null || !srcFile.exists() || srcFile.length() <= 100) { + Toast.makeText(activity, "无有效图片可裁剪", Toast.LENGTH_SHORT).show(); + Log.e(TAG, "startSystemCrop: 原图无效"); + return; + } + + // 生成输入Uri + Uri inputUri = getFileProviderUri(activity, srcFile); + if (inputUri == null) { + Toast.makeText(activity, "获取图片Uri失败", Toast.LENGTH_SHORT).show(); + Log.e(TAG, "startSystemCrop: 输入Uri生成失败"); + return; + } + + // 生成输出文件(使用BackgroundSourceUtils的压缩路径) + BackgroundSourceUtils bgSourceUtils = BackgroundSourceUtils.getInstance(activity); + File outputFile = new File(bgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath()); + if (outputFile == null) { + Toast.makeText(activity, "裁剪输出路径无效", Toast.LENGTH_SHORT).show(); + Log.e(TAG, "startSystemCrop: 输出文件为空"); + return; + } + Uri outputUri = getFileProviderUri(activity, outputFile); + + // 构建裁剪意图 + Intent intent = new Intent("com.android.camera.action.CROP"); + intent.setDataAndType(inputUri, "image/*"); + intent.putExtra("crop", "true"); + intent.putExtra("noFaceDetection", true); + + // 设置裁剪比例(自由裁剪则不设置) + if (!isFreeCrop && aspectX > 0 && aspectY > 0) { + intent.putExtra("aspectX", aspectX); + intent.putExtra("aspectY", aspectY); + Log.d(TAG, "startSystemCrop: 裁剪比例:" + aspectX + ":" + aspectY); + } + + // 输出配置 + intent.putExtra("outputX", 2048); + intent.putExtra("outputY", 2048); + intent.putExtra("scale", true); + intent.putExtra("scaleUpIfNeeded", true); + intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); + intent.putExtra("quality", 100); + intent.putExtra("return-data", false); + intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); + + // 权限设置 + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + } + + // 适配系统裁剪工具 + try { + List resolveInfos = activity.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + if (!resolveInfos.isEmpty()) { + ResolveInfo resolveInfo = resolveInfos.get(0); + String cropPackageName = resolveInfo.activityInfo.packageName; + String cropActivityName = resolveInfo.activityInfo.name; + + // 授予MIUI裁剪权限 + if (cropPackageName.equals("com.miui.gallery")) { + activity.grantUriPermission(cropPackageName, inputUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + activity.grantUriPermission(cropPackageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + + // 显式启动裁剪 + Intent cropIntent = new Intent(intent); + cropIntent.setComponent(new ComponentName(cropPackageName, cropActivityName)); + activity.startActivityForResult(cropIntent, requestCode); + Log.d(TAG, "startSystemCrop: 启动裁剪成功,输出路径:" + outputFile.getAbsolutePath()); + } else { + // 兜底启动 + Intent chooser = Intent.createChooser(intent, "选择裁剪工具"); + chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (chooser.resolveActivity(activity.getPackageManager()) != null) { + activity.startActivityForResult(chooser, requestCode); + } else { + Toast.makeText(activity, "无可用裁剪工具", Toast.LENGTH_SHORT).show(); + } + } + } catch (Exception e) { + Log.e(TAG, "startSystemCrop: 启动裁剪失败:" + e.getMessage()); + Toast.makeText(activity, "裁剪工具启动失败", Toast.LENGTH_SHORT).show(); + } + } + + /** + * 获取FileProvider Uri(复用方法,避免重复代码) + */ + public static Uri getFileProviderUri(Activity activity, File file) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + String FILE_PROVIDER_AUTHORITY = activity.getPackageName() + ".fileprovider"; + + return FileProvider.getUriForFile(activity, FILE_PROVIDER_AUTHORITY, file); + } else { + return Uri.fromFile(file); + } + } catch (Exception e) { + Log.e(TAG, "getFileProviderUri: 生成Uri失败:" + e.getMessage()); + return null; + } + } +} +