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 7f5a371..d615d68 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 @@ -3,11 +3,9 @@ package cc.winboll.studio.powerbell.activities; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -17,22 +15,19 @@ import android.os.Looper; import android.provider.MediaStore; import android.text.TextUtils; import android.view.View; -import android.widget.ImageView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; -import androidx.core.content.FileProvider; import androidx.appcompat.widget.Toolbar; +import androidx.core.content.FileProvider; import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import cc.winboll.studio.libaes.views.AToolbar; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.powerbell.App; @@ -47,16 +42,11 @@ import cc.winboll.studio.powerbell.utils.ImageCropUtils; import cc.winboll.studio.powerbell.utils.PermissionUtils; import cc.winboll.studio.powerbell.utils.UriUtil; import cc.winboll.studio.powerbell.views.BackgroundView; -import cc.winboll.studio.powerbell.utils.AssetsCopyUtils; public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener { + // ====================== 常量定义 ====================== public static final String TAG = "BackgroundSettingsActivity"; - private BackgroundSourceUtils mBgSourceUtils; - private PermissionUtils mPermissionUtils; - private BitmapCacheUtils mBitmapCache; - - // 新增:手动定义 Android 13 对应的 SDK 版本号 private static final int SDK_VERSION_TIRAMISU = 33; public static final int REQUEST_SELECT_PICTURE = 0; @@ -64,12 +54,18 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg public static final int REQUEST_CROP_IMAGE = 2; private static final int REQUEST_READ_MEDIA = 1001; + // ====================== 成员变量 ====================== + private BackgroundSourceUtils mBgSourceUtils; + private PermissionUtils mPermissionUtils; + private BitmapCacheUtils mBitmapCache; + private Toolbar mToolbar; private BackgroundView mBackgroundView; private File mfTakePhoto; volatile boolean isCommitSettings = false; volatile boolean isPreviewBackgroundChanged = false; + // ====================== 生命周期方法 ====================== @Override public Activity getActivity() { return this; @@ -84,13 +80,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_background_settings); + LogUtils.d(TAG, "【生命周期】onCreate 开始初始化"); - mBackgroundView = (BackgroundView) findViewById(R.id.background_view); + // 初始化视图与工具类 + mBackgroundView = findViewById(R.id.background_view); mBgSourceUtils = BackgroundSourceUtils.getInstance(this); mBgSourceUtils.loadSettings(); mPermissionUtils = PermissionUtils.getInstance(); mBitmapCache = BitmapCacheUtils.getInstance(); + // 初始化临时文件与目录 File tempDir = new File(App.getTempDirPath()); if (!tempDir.exists()) { tempDir.mkdirs(); @@ -100,12 +99,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp"); if (!selectTempDir.exists()) { selectTempDir.mkdirs(); - LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath()); + LogUtils.d(TAG, "【目录初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath()); } + // 初始化界面与事件 initToolbar(); initClickListeners(); + // 处理分享意图或初始化预览 if (handleShareIntent()) { ToastUtils.show("handleShareIntent"); } else { @@ -114,15 +115,92 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean()); doubleRefreshPreview(); - LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成"); + LogUtils.d(TAG, "【生命周期】onCreate 初始化完成"); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); doubleRefreshPreview(); + LogUtils.d(TAG, "【生命周期】onPostCreate 执行双重刷新"); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + LogUtils.d(TAG, "【回调触发】requestCode:" + requestCode + ",resultCode:" + resultCode); + + try { + if (requestCode == PermissionUtils.REQUEST_READ_MEDIA_IMAGES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + handleStoragePermissionCallback(); + return; + } + + if (resultCode != RESULT_OK) { + handleOperationCancelOrFail(); + return; + } + + switch (requestCode) { + case REQUEST_SELECT_PICTURE: + handleSelectPictureResult(resultCode, data); + break; + case REQUEST_TAKE_PHOTO: + handleTakePhotoResult(resultCode, data); + break; + case REQUEST_CROP_IMAGE: + handleCropImageResult(requestCode, resultCode, data); + break; + default: + LogUtils.d(TAG, "【回调忽略】未知requestCode"); + break; + } + } catch (Exception e) { + LogUtils.e(TAG, "【回调异常】" + e.getMessage()); + ToastUtils.show("操作失败"); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + LogUtils.d(TAG, "【权限回调】转发处理 requestCode:" + requestCode); + mPermissionUtils.handleStoragePermissionResult(this, requestCode, permissions, grantResults); + } + + @Override + public void finish() { + LogUtils.d(TAG, "【生命周期】finish 触发,isCommitSettings:" + isCommitSettings + ",isPreviewBackgroundChanged:" + isPreviewBackgroundChanged); + if (isCommitSettings) { + setResult(RESULT_OK); + super.finish(); + } else { + if (isPreviewBackgroundChanged) { + YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener() { + @Override + public void onYes() { + mBgSourceUtils.commitPreviewSourceToCurrent(); + isCommitSettings = true; + setResult(RESULT_OK); + finish(); + } + + @Override + public void onNo() { + isCommitSettings = true; + setResult(RESULT_CANCELED); + finish(); + } + }); + } else { + setResult(RESULT_OK); + isCommitSettings = true; + finish(); + } + } + } + + // ====================== 界面初始化方法 ====================== private void initToolbar() { mToolbar = findViewById(R.id.toolbar); setSupportActionBar(mToolbar); @@ -139,6 +217,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } private void initClickListeners() { + LogUtils.d(TAG, "【界面初始化】绑定按钮点击事件"); findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener); findViewById(R.id.activitybackgroundpictureAButton4).setOnClickListener(onReceivedPictureClickListener); findViewById(R.id.activitybackgroundpictureAButton1).setOnClickListener(onTakePhotoClickListener); @@ -149,35 +228,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener); } - private boolean handleShareIntent() { - Intent intent = getIntent(); - if (intent != null) { - String action = intent.getAction(); - String type = intent.getType(); - if (Intent.ACTION_SEND.equals(action) && type != null && isImageType(type)) { - BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this); - dlg.show(); - LogUtils.d(TAG, "【分享处理】收到分享图片意图"); - return true; - } - } - return false; - } - - boolean isImageType(String lowerMimeType) { - return lowerMimeType.equals("image/jpeg") - || lowerMimeType.equals("image/png") - || lowerMimeType.equals("image/tiff") - || lowerMimeType.equals("image/jpg") - || lowerMimeType.equals("image/svg+xml"); - } - - @Override - public void onAcceptRecivedPicture(String szPreRecivedPictureName) { - ToastUtils.show("图片接收功能暂未实现"); - LogUtils.d(TAG, "【分享接收】图片名:" + szPreRecivedPictureName); - } - + // ====================== 按钮点击事件 ====================== private View.OnClickListener onOriginNullClickListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -192,7 +243,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg @Override public void onClick(View v) { LogUtils.d(TAG, "【按钮点击】选择图片"); - // 适配Android 13+权限(已替换为手动定义的常量) if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) { if (mPermissionUtils.checkAndRequestMediaImagesPermission(BackgroundSettingsActivity.this, REQUEST_READ_MEDIA)) { launchImageSelector(); @@ -205,70 +255,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } }; - private void launchImageSelector() { - LogUtils.d(TAG, "【选图权限】已获取,启动选择器"); - Intent[] intents = new Intent[3]; - 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; - - 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; - - 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 (int i = 0; i < intents.length; i++) { - Intent intent = intents[i]; - if (intent != null && intent.resolveActivity(getPackageManager()) != null) { - validIntent = intent; - break; - } - } - - if (validIntent != null) { - 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, "【选图意图】启动图片选择"); - } else { - LogUtils.d(TAG, "【选图意图】无相册应用"); - 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 public void onClick(View v) { @@ -329,7 +315,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } catch (Exception e) { String errMsg = "拍照启动异常:" + e.getMessage(); ToastUtils.show(errMsg.substring(0, 20)); - LogUtils.e(TAG, "【拍照失败】"); + LogUtils.e(TAG, "【拍照失败】" + e.getMessage()); } } else { LogUtils.d(TAG, "【拍照权限】已申请"); @@ -353,13 +339,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg File targetFile = new File(targetImagePath); if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) { ToastUtils.show("无有效图片可拾取像素"); - LogUtils.e(TAG, "【像素拾取失败】"); + LogUtils.e(TAG, "【像素拾取失败】文件无效:" + targetImagePath); return; } Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class); intent.putExtra("imagePath", targetImagePath); startActivity(intent); - LogUtils.d(TAG, "【像素拾取启动】"); + LogUtils.d(TAG, "【像素拾取启动】路径:" + targetImagePath); } }; @@ -377,42 +363,323 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } }; - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - LogUtils.d(TAG, "【回调触发】requestCode:" + requestCode + ",resultCode:" + resultCode); - + // ====================== 工具方法 ====================== + /** + * 生成 FileProvider Uri,适配 Android 7.0+ + */ + public Uri getFileProviderUri(File file) { try { - if (requestCode == PermissionUtils.REQUEST_READ_MEDIA_IMAGES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - handleStoragePermissionCallback(); - return; - } - - if (resultCode != RESULT_OK) { - handleOperationCancelOrFail(); - return; - } - - switch (requestCode) { - case REQUEST_SELECT_PICTURE: - handleSelectPictureResult(resultCode, data); - break; - case REQUEST_TAKE_PHOTO: - handleTakePhotoResult(resultCode, data); - break; - case REQUEST_CROP_IMAGE: - handleCropImageResult(requestCode, resultCode, data); - break; - default: - LogUtils.d(TAG, "【回调忽略】未知requestCode"); - break; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + String FILE_PROVIDER_AUTHORITY = getPackageName() + ".fileprovider"; + return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, file); + } else { + return Uri.fromFile(file); } } catch (Exception e) { - LogUtils.e(TAG, "【回调异常】" + e.getMessage()); - ToastUtils.show("操作失败"); + LogUtils.e(TAG, "getFileProviderUri: 生成Uri失败:" + e.getMessage()); + return null; } } + /** + * 校验 Bitmap 是否有效(未被回收且不为空) + */ + private boolean isBitmapValid(Bitmap bitmap) { + return bitmap != null && !bitmap.isRecycled(); + } + + /** + * 双重刷新预览,确保背景加载最新数据 + */ + private void doubleRefreshPreview() { + LogUtils.d(TAG, "【工具方法】doubleRefreshPreview 开始执行"); + if (mBgSourceUtils == null || mBackgroundView == null || isFinishing()) { + LogUtils.w(TAG, "【双重刷新】跳过:对象为空或Activity已结束"); + return; + } + + BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); + if (previewBean != null && previewBean.isUseBackgroundFile()) { + String bgPath = previewBean.isUseBackgroundScaledCompressFile() + ? previewBean.getBackgroundScaledCompressFilePath() + : previewBean.getBackgroundFilePath(); + mBitmapCache.removeCachedBitmap(bgPath); + LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath); + } + + try { + mBgSourceUtils.loadSettings(); + mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); + LogUtils.d(TAG, "【双重刷新】第一重完成"); + } catch (Exception e) { + LogUtils.e(TAG, "【双重刷新】第一重异常:" + e.getMessage()); + return; + } + + new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { + @Override + public void run() { + if (mBackgroundView != null && !isFinishing() && mBgSourceUtils != null) { + try { + mBgSourceUtils.loadSettings(); + mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); + LogUtils.d(TAG, "【双重刷新】第二重完成"); + } catch (Exception e) { + LogUtils.e(TAG, "【双重刷新】第二重异常:" + e.getMessage()); + } + } + } + }, 200); + } + + /** + * 解析裁剪临时文件为 Bitmap,带采样率优化 + */ + private Bitmap parseCropTempFileToBitmap(File cropTempFile) { + LogUtils.d(TAG, "【工具方法】parseCropTempFileToBitmap 解析文件:" + (cropTempFile != null ? cropTempFile.getAbsolutePath() : "null")); + if (cropTempFile == null || !cropTempFile.exists() || !cropTempFile.isFile() || cropTempFile.length() <= 100) { + LogUtils.e(TAG, "【Bitmap解析】文件无效"); + return null; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); + + 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, "【Bitmap解析】采样率:" + sampleSize); + + options.inJustDecodeBounds = false; + options.inSampleSize = sampleSize; + options.inPreferredConfig = Bitmap.Config.RGB_565; + options.inPurgeable = true; + options.inInputShareable = true; + + try { + Bitmap cropBitmap = BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); + if (!isBitmapValid(cropBitmap)) { + LogUtils.e(TAG, "【Bitmap解析】解析失败"); + return null; + } + LogUtils.d(TAG, "【Bitmap解析】成功,尺寸:" + cropBitmap.getWidth() + "x" + cropBitmap.getHeight()); + return cropBitmap; + } catch (OutOfMemoryError e) { + LogUtils.e(TAG, "【Bitmap解析】OOM异常"); + return null; + } catch (Exception e) { + LogUtils.e(TAG, "【Bitmap解析】异常:" + e.getMessage()); + return null; + } + } + + /** + * 调整 Bitmap 比例至目标比例 + */ + private Bitmap adjustBitmapToFinalRatio(Bitmap originalBitmap, float finalCropRatio) { + LogUtils.d(TAG, "【工具方法】adjustBitmapToFinalRatio 调整比例,目标比例:" + finalCropRatio); + if (!isBitmapValid(originalBitmap) || finalCropRatio <= 0) { + LogUtils.e(TAG, "【比例调整】参数无效"); + return null; + } + + int originalWidth = originalBitmap.getWidth(); + int originalHeight = originalBitmap.getHeight(); + float originalRatio = (float) originalWidth / originalHeight; + + if (Math.abs(originalRatio - finalCropRatio) < 0.001f) { + LogUtils.d(TAG, "【比例调整】比例一致,生成副本"); + return originalBitmap.copy(originalBitmap.getConfig(), false); + } + + int targetWidth, targetHeight; + targetHeight = originalHeight; + targetWidth = Math.round(targetHeight * finalCropRatio); + if (targetWidth > originalWidth) { + targetWidth = originalWidth; + targetHeight = Math.round(targetWidth / finalCropRatio); + } + targetWidth = Math.round(targetHeight * finalCropRatio); + LogUtils.d(TAG, "【比例调整】调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight); + + try { + Bitmap adjustedBitmap = Bitmap.createBitmap( + originalBitmap, + (originalWidth - targetWidth) / 2, + (originalHeight - targetHeight) / 2, + targetWidth, + targetHeight + ); + return adjustedBitmap; + } catch (OutOfMemoryError e) { + LogUtils.e(TAG, "【比例调整】OOM异常"); + return null; + } + } + + /** + * 保存 Bitmap 到目标文件 + */ + private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) { + LogUtils.d(TAG, "【工具方法】saveScaledBitmapToFile 保存图片:" + targetFile.getAbsolutePath()); + if (!isBitmapValid(bitmap) || targetFile == null) { + LogUtils.e(TAG, "【图片保存】参数无效"); + return; + } + + if (targetFile.exists()) { + boolean deleteSuccess = targetFile.delete(); + LogUtils.d(TAG, "【图片保存】删除原文件:" + (deleteSuccess ? "成功" : "失败")); + } + + OutputStream outputStream = null; + try { + outputStream = new BufferedOutputStream(new FileOutputStream(targetFile)); + bitmap.compress(CompressFormat.JPEG, 100, outputStream); + outputStream.flush(); + LogUtils.d(TAG, "【图片保存】成功,文件大小:" + targetFile.length() + " bytes"); + } catch (IOException e) { + LogUtils.e(TAG, "【图片保存】异常:" + e.getMessage()); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + LogUtils.e(TAG, "【图片保存】关闭流异常"); + } + } + } + } + + /** + * 从裁剪文件中读取比例 + */ + private float getRatioFromSystemCropFile(File systemCropFile) { + LogUtils.d(TAG, "【工具方法】getRatioFromSystemCropFile 读取比例:" + systemCropFile.getAbsolutePath()); + if (systemCropFile == null || !systemCropFile.exists() || !systemCropFile.isFile()) { + LogUtils.e(TAG, "【比例读取】文件无效"); + return -1; + } + + 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, "【比例读取】尺寸无效"); + return -1; + } + + float systemRatio = (float) cropWidth / cropHeight; + LogUtils.d(TAG, "【比例读取】成功,比例:" + systemRatio); + return systemRatio; + } + + // ====================== 业务逻辑方法 ====================== + /** + * 处理分享意图 + */ + private boolean handleShareIntent() { + Intent intent = getIntent(); + if (intent != null) { + String action = intent.getAction(); + String type = intent.getType(); + if (Intent.ACTION_SEND.equals(action) && type != null && isImageType(type)) { + BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this); + dlg.show(); + LogUtils.d(TAG, "【分享处理】收到分享图片意图"); + return true; + } + } + return false; + } + + /** + * 判断是否为图片类型 + */ + boolean isImageType(String lowerMimeType) { + return lowerMimeType.equals("image/jpeg") + || lowerMimeType.equals("image/png") + || lowerMimeType.equals("image/tiff") + || lowerMimeType.equals("image/jpg") + || lowerMimeType.equals("image/svg+xml"); + } + + /** + * 启动图片选择器 + */ + private void launchImageSelector() { + LogUtils.d(TAG, "【业务逻辑】launchImageSelector 启动选择器"); + Intent[] intents = new Intent[3]; + 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; + + 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; + + 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; + } + } + + if (validIntent != null) { + 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, "【选图意图】启动图片选择"); + } else { + LogUtils.d(TAG, "【选图意图】无相册应用"); + 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 void handleStoragePermissionCallback() { if (Environment.isExternalStorageManager()) { LogUtils.d(TAG, "【权限回调】已授予"); @@ -423,7 +690,21 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } + /** + * 处理操作取消或失败 + */ + private void handleOperationCancelOrFail() { + mBgSourceUtils.setCurrentSourceToPreview(); + LogUtils.d(TAG, "【业务逻辑】操作取消或失败,恢复预览"); + ToastUtils.show("操作取消或失败"); + doubleRefreshPreview(); + } + + /** + * 处理拍照结果 + */ private void handleTakePhotoResult(int resultCode, Intent data) { + LogUtils.d(TAG, "【业务逻辑】handleTakePhotoResult 处理拍照结果"); if (resultCode != RESULT_OK || data == null) { handleOperationCancelOrFail(); return; @@ -455,8 +736,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【拍照完成】已启动裁剪"); } + /** + * 解析拍照返回的 Bitmap + */ private Bitmap getTakePhotoBitmap(Intent data) { - LogUtils.d(TAG, "【拍照Bitmap解析】开始"); + LogUtils.d(TAG, "【业务逻辑】getTakePhotoBitmap 解析拍照Bitmap"); if (mfTakePhoto != null && mfTakePhoto.exists()) { LogUtils.d(TAG, "【拍照Bitmap解析】从文件解析"); Bitmap photoBitmap = parseCropTempFileToBitmap(mfTakePhoto); @@ -472,7 +756,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (data != null) { try { - Bitmap thumbnailBitmap = (Bitmap) data.getParcelableExtra("data"); + Bitmap thumbnailBitmap = data.getParcelableExtra("data"); if (isBitmapValid(thumbnailBitmap)) { LogUtils.d(TAG, "【拍照Bitmap解析】从Intent获取成功"); return thumbnailBitmap; @@ -491,7 +775,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return null; } + /** + * 处理选图结果 + */ private void handleSelectPictureResult(int resultCode, Intent data) { + LogUtils.d(TAG, "【业务逻辑】handleSelectPictureResult 处理选图结果"); if (resultCode != RESULT_OK || data == null) { handleOperationCancelOrFail(); return; @@ -504,7 +792,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } LogUtils.d(TAG, "【选图回调】系统返回Uri : " + selectedImage.toString()); - // 1. 申请持久化读取权限 if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) { getContentResolver().takePersistableUriPermission( selectedImage, @@ -513,7 +800,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【选图权限】已添加持久化权限"); } - // 2. 同步到预览 Bean 并启动裁剪 if (putUriFileToPreviewSource(selectedImage)) { LogUtils.d(TAG, "【选图同步】路径绑定完成"); mBitmapCache.removeCachedBitmap(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); @@ -530,9 +816,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } + /** + * 将 Uri 文件同步到预览 Bean + */ boolean putUriFileToPreviewSource(Uri srcUriFile) { String filePath = UriUtil.getFilePathFromUri(this, srcUriFile); - // 关键修复:校验路径是否为空 if (TextUtils.isEmpty(filePath)) { LogUtils.e(TAG, "putUriFileToPreviewSource: Uri解析路径为空"); return false; @@ -541,15 +829,21 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return putUriFileToPreviewSource(srcFile); } + /** + * 将 File 同步到预览 Bean + */ boolean putUriFileToPreviewSource(File srcFile) { - LogUtils.d(TAG, String.format("putUriFileToPreviewSource(File srcFile) srcFile %s", srcFile)); + LogUtils.d(TAG, "【业务逻辑】putUriFileToPreviewSource 同步文件:" + srcFile.getAbsolutePath()); mBgSourceUtils.loadSettings(); File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); return FileUtils.copyFile(srcFile, dstFile); } + /** + * 处理裁剪结果 + */ private void handleCropImageResult(int requestCode, int resultCode, Intent data) { - LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果"); + LogUtils.d(TAG, "【业务逻辑】handleCropImageResult 处理裁剪结果"); File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath()); boolean isFileExist = cropTempFile.exists(); boolean isFileReadable = isFileExist ? cropTempFile.canRead() : false; @@ -558,7 +852,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (isCropSuccess) { isPreviewBackgroundChanged = true; - LogUtils.d(TAG, "handleCropImageResult: 裁剪成功"); + LogUtils.d(TAG, "【裁剪结果】裁剪成功"); final BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); previewBean.setIsUseBackgroundFile(true); previewBean.setIsUseBackgroundScaledCompressFile(true); @@ -567,17 +861,15 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg float systemFileRatio = getRatioFromSystemCropFile(cropTempFile); if (systemFileRatio > 0) { Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile); - // 校验解析后的Bitmap是否有效 if (isBitmapValid(cropBitmap)) { Bitmap scaledCropBitmap = adjustBitmapToFinalRatio(cropBitmap, systemFileRatio); - // 仅当新Bitmap生成成功时,才保存并回收 if (isBitmapValid(scaledCropBitmap)) { saveScaledBitmapToFile(scaledCropBitmap, cropTempFile); scaledCropBitmap.recycle(); } cropBitmap.recycle(); } else { - LogUtils.e(TAG, "handleCropImageResult: 裁剪Bitmap解析无效"); + LogUtils.e(TAG, "【裁剪结果】裁剪Bitmap解析无效"); } } @@ -586,7 +878,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg public void run() { if (!isFinishing()) { doubleRefreshPreview(); - LogUtils.d(TAG, "handleCropImageResult: 触发双重刷新"); + LogUtils.d(TAG, "【裁剪结果】触发双重刷新"); } } }, 300); @@ -595,315 +887,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } - private float getRatioFromSystemCropFile(File systemCropFile) { - LogUtils.d(TAG, "getRatioFromSystemCropFile: 读取比例"); - if (systemCropFile == null || !systemCropFile.exists() || !systemCropFile.isFile()) { - LogUtils.e(TAG, "getRatioFromSystemCropFile: 文件无效"); - return -1; - } - - 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, "getRatioFromSystemCropFile: 尺寸无效"); - return -1; - } - - float systemRatio = (float) cropWidth / cropHeight; - LogUtils.d(TAG, "getRatioFromSystemCropFile: 比例:" + systemRatio); - return systemRatio; - } - - /** - * 校验 Bitmap 是否有效(未被回收且不为空) - * @param bitmap 待校验的 Bitmap - * @return true=有效 false=无效 - */ - private boolean isBitmapValid(Bitmap bitmap) { - return bitmap != null && !bitmap.isRecycled(); - } - - private Bitmap adjustBitmapToFinalRatio(Bitmap originalBitmap, float finalCropRatio) { - LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整比例"); - // 增加 Bitmap 有效性校验 - if (!isBitmapValid(originalBitmap) || finalCropRatio <= 0) { - LogUtils.e(TAG, "adjustBitmapToFinalRatio: 参数无效"); - return null; // 直接返回null,避免传递无效Bitmap - } - - int originalWidth = originalBitmap.getWidth(); - int originalHeight = originalBitmap.getHeight(); - float originalRatio = (float) originalWidth / originalHeight; - - if (Math.abs(originalRatio - finalCropRatio) < 0.001f) { - LogUtils.d(TAG, "adjustBitmapToFinalRatio: 比例一致,生成副本"); - // 关键修复:不返回原Bitmap,而是生成副本,避免原Bitmap被回收后失效 - return originalBitmap.copy(originalBitmap.getConfig(), false); - } - - int targetWidth, targetHeight; - targetHeight = originalHeight; - targetWidth = Math.round(targetHeight * finalCropRatio); - if (targetWidth > originalWidth) { - targetWidth = originalWidth; - targetHeight = Math.round(targetWidth / finalCropRatio); - } - - targetWidth = Math.round(targetHeight * finalCropRatio); - LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight); - - try { - Bitmap adjustedBitmap = Bitmap.createBitmap( - originalBitmap, - (originalWidth - targetWidth) / 2, - (originalHeight - targetHeight) / 2, - targetWidth, - targetHeight - ); - return adjustedBitmap; - } catch (OutOfMemoryError e) { - LogUtils.e(TAG, "adjustBitmapToFinalRatio: OOM异常"); - return null; - } - } - - private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) { - LogUtils.d(TAG, "saveScaledBitmapToFile: 保存图片"); - if (!isBitmapValid(bitmap) || targetFile == null) { - LogUtils.e(TAG, "saveScaledBitmapToFile: 参数无效"); - return; - } - - if (targetFile.exists()) { - boolean deleteSuccess = targetFile.delete(); - LogUtils.d(TAG, "saveScaledBitmapToFile: 删除原文件:" + (deleteSuccess ? "成功" : "失败")); - } - - OutputStream outputStream = null; - try { - outputStream = new BufferedOutputStream(new FileOutputStream(targetFile)); - bitmap.compress(CompressFormat.JPEG, 100, outputStream); - outputStream.flush(); - LogUtils.d(TAG, "saveScaledBitmapToFile: 保存成功"); - } catch (IOException e) { - LogUtils.e(TAG, "saveScaledBitmapToFile: 异常:" + e.getMessage()); - } finally { - if (outputStream != null) { - try { - outputStream.close(); - } catch (IOException e) { - LogUtils.e(TAG, "saveScaledBitmapToFile: 关闭流异常"); - } - } - } - } - - private Bitmap parseCropTempFileToBitmap(File cropTempFile) { - LogUtils.d(TAG, "parseCropTempFileToBitmap: 解析文件"); - if (cropTempFile == null || !cropTempFile.exists() || !cropTempFile.isFile() || cropTempFile.length() <= 100) { - LogUtils.e(TAG, "parseCropTempFileToBitmap: 文件无效"); - return null; - } - - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); - - 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, "parseCropTempFileToBitmap: 采样率:" + sampleSize); - - options.inJustDecodeBounds = false; - options.inSampleSize = sampleSize; - options.inPreferredConfig = Bitmap.Config.RGB_565; - options.inPurgeable = true; - options.inInputShareable = true; - - Bitmap cropBitmap = null; - try { - cropBitmap = BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options); - if (!isBitmapValid(cropBitmap)) { - LogUtils.e(TAG, "parseCropTempFileToBitmap: 解析失败"); - return null; - } - LogUtils.d(TAG, "parseCropTempFileToBitmap: 解析成功"); - } catch (OutOfMemoryError e) { - LogUtils.e(TAG, "parseCropTempFileToBitmap: OOM"); - Toast.makeText(this, "图片解析失败", Toast.LENGTH_SHORT).show(); - return null; - } catch (Exception e) { - LogUtils.e(TAG, "parseCropTempFileToBitmap: 异常:" + e.getMessage()); - return null; - } - - return cropBitmap; - } - - private void doubleRefreshPreview() { - LogUtils.d(TAG, "【双重刷新】开始"); - // 增加非空校验 - if (mBgSourceUtils == null || mBackgroundView == null || isFinishing()) { - LogUtils.w(TAG, "【双重刷新】跳过:对象为空或Activity已结束"); - return; - } - - // 清空缓存工具类的旧数据 - BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); - if (previewBean != null && previewBean.isUseBackgroundFile()) { - String bgPath = previewBean.isUseBackgroundScaledCompressFile() - ? previewBean.getBackgroundScaledCompressFilePath() - : previewBean.getBackgroundFilePath(); - mBitmapCache.removeCachedBitmap(bgPath); - LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath); - } - - // 重新加载最新数据 - try { - mBgSourceUtils.loadSettings(); - mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); - LogUtils.d(TAG, "【双重刷新】第一重完成"); - } catch (Exception e) { - LogUtils.e(TAG, "【双重刷新】第一重异常:" + e.getMessage()); - return; - } - - // 延长延迟到200ms,确保文件完全加载 - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - if (mBackgroundView != null && !isFinishing() && mBgSourceUtils != null) { - try { - mBgSourceUtils.loadSettings(); - mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); - LogUtils.d(TAG, "【双重刷新】第二重完成"); - } catch (Exception e) { - LogUtils.e(TAG, "【双重刷新】第二重异常:" + e.getMessage()); - } - } - } - }, 200); - } - - private void handleOperationCancelOrFail() { - mBgSourceUtils.setCurrentSourceToPreview(); - LogUtils.d(TAG, "【操作回调】取消或失败"); - ToastUtils.show("【操作回调】取消或失败"); - doubleRefreshPreview(); - } - - public Uri getFileProviderUri(File file) { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - String FILE_PROVIDER_AUTHORITY = getPackageName() + ".fileprovider"; - return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, file); - } else { - return Uri.fromFile(file); - } - } catch (Exception e) { - LogUtils.e(TAG, "getFileProviderUri: 生成Uri失败:" + e.getMessage()); - return null; - } - } - + // ====================== 接口实现 ====================== @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) { - setResult(RESULT_OK); - super.finish(); - } else { - if (isPreviewBackgroundChanged) { - YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener() { - @Override - public void onYes() { - mBgSourceUtils.commitPreviewSourceToCurrent(); - isCommitSettings = true; - setResult(RESULT_OK); - finish(); - } - - @Override - public void onNo() { - isCommitSettings = true; - setResult(RESULT_CANCELED); - finish(); - } - }); - } else { - setResult(RESULT_OK); - isCommitSettings = true; - finish(); - } - } - } - - /** - * 解析Uri到真实文件路径,兼容所有Android版本 - */ - private String getRealPathFromUri(Uri uri) { - String path = null; - String[] projection = {MediaStore.Images.Media.DATA}; - Cursor cursor = getContentResolver().query(uri, projection, null, null, null); - if (cursor != null) { - if (cursor.moveToFirst()) { - int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - path = cursor.getString(columnIndex); - } - cursor.close(); - } - // 针对Android 10+沙箱限制,拷贝到私有目录 - if (TextUtils.isEmpty(path)) { - path = copyUriToPrivateDir(uri); - } - return path; - } - - /** - * 将Uri对应的图片拷贝到应用私有临时目录 - */ - private String copyUriToPrivateDir(Uri uri) { - try { - InputStream inputStream = getContentResolver().openInputStream(uri); - if (inputStream == null) { - return null; - } - File tempDir = new File(App.getTempDirPath()); - if (!tempDir.exists()) { - tempDir.mkdirs(); - } - String fileName = "select_temp_" + System.currentTimeMillis() + ".jpg"; - File destFile = new File(tempDir, fileName); - - OutputStream outputStream = new FileOutputStream(destFile); - byte[] buffer = new byte[1024 * 4]; - int len; - while ((len = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, len); - } - outputStream.flush(); - inputStream.close(); - outputStream.close(); - - LogUtils.d(TAG, "【选图拷贝】文件已拷贝到私有目录:" + destFile.getAbsolutePath()); - return destFile.getAbsolutePath(); - } catch (IOException e) { - LogUtils.e(TAG, "【选图拷贝】异常:" + e.getMessage()); - return null; - } + public void onAcceptRecivedPicture(String szPreRecivedPictureName) { + ToastUtils.show("图片接收功能暂未实现"); + LogUtils.d(TAG, "【分享接收】图片名:" + szPreRecivedPictureName); } }