图片剪裁与预览测试完成

This commit is contained in:
2025-12-01 15:59:49 +08:00
parent 1cadc4ed93
commit 6538ebafef
2 changed files with 821 additions and 975 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #Created by .winboll/winboll_app_build.gradle
#Mon Dec 01 07:14:25 GMT 2025 #Mon Dec 01 07:58:43 GMT 2025
stageCount=13 stageCount=13
libraryProject= libraryProject=
baseVersion=15.11 baseVersion=15.11
publishVersion=15.11.12 publishVersion=15.11.12
buildCount=50 buildCount=52
baseBetaVersion=15.11.13 baseBetaVersion=15.11.13

View File

@@ -2,57 +2,51 @@ package cc.winboll.studio.powerbell.activities;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.content.ComponentName;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.ComponentName;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.RelativeLayout;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libaes.views.AToolbar; import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.App; import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.BuildConfig;
import cc.winboll.studio.powerbell.R; import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog; import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
import cc.winboll.studio.powerbell.dialogs.NetworkBackgroundDialog;
import cc.winboll.studio.powerbell.model.BackgroundBean; import cc.winboll.studio.powerbell.model.BackgroundBean;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils; import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.powerbell.utils.FileUtils;
import cc.winboll.studio.powerbell.utils.UriUtil; import cc.winboll.studio.powerbell.utils.UriUtil;
import cc.winboll.studio.powerbell.views.BackgroundView; import cc.winboll.studio.powerbell.views.BackgroundView;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener { public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
public static final String TAG = "BackgroundSettingsActivity"; public static final String TAG = "BackgroundSettingsActivity";
public static final int Build_VERSION_CODES_TIRAMISU = 33;
// 工具类单例(唯一文件管理入口) // 工具类单例(唯一文件管理入口)
private BackgroundSourceUtils mBgSourceUtils; private BackgroundSourceUtils mBgSourceUtils;
@@ -109,7 +103,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 处理分享图片意图 // 处理分享图片意图
handleShareIntent(); handleShareIntent();
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成(精简版文件管理依赖BackgroundSourceUtils"); LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成(Java7 语法版");
} }
/** /**
@@ -154,19 +148,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener); findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
} }
/**
* 初始化预览(调用工具类同步正式背景到预览)
*/
// private void initPreview() {
// mBgSourceUtils.setCurrentSourceToPreview();
// if (bvPreviewBackground != null) {
// bvPreviewBackground.reloadPreviewBackground();
// LogUtils.d(TAG, "【初始化】预览视图已加载BackgroundView 状态:正常");
// } else {
// LogUtils.e(TAG, "【初始化】bvPreviewBackground 为空,预览加载失败");
// }
// }
/** /**
* 处理分享图片意图仅UI逻辑文件处理调用工具类 * 处理分享图片意图仅UI逻辑文件处理调用工具类
*/ */
@@ -190,18 +171,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
/** /**
* 更新背景预览(调用工具类,无文件操作 * 更新背景预览(强制使用预览Bean删除正式Bean分支Java7语法
*/ */
public void updateBackgroundView(File sourceFile, String sourceFileInfo) { public void updateBackgroundView(File sourceFile, String sourceFileInfo) {
LogUtils.d(TAG, "【预览更新】updateBackgroundView 触发sourceFile是否为空" + (sourceFile == null)); LogUtils.d(TAG, "【预览更新】updateBackgroundView 触发sourceFile是否为空" + (sourceFile == null));
if (sourceFile == null) { if (sourceFile != null) {
bvPreviewBackground.reloadCurrentBackground(); mBgSourceUtils.saveFileToPreviewBean(sourceFile, sourceFileInfo); // 有文件时同步到预览Bean
LogUtils.d(TAG, "【预览更新】sourceFile为空加载正式背景");
} else {
mBgSourceUtils.saveFileToPreviewBean(sourceFile, sourceFileInfo);
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览更新】预览背景更新完成");
} }
// 强制加载预览Bean无论sourceFile是否为空全程不使用正式Bean
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览更新】预览背景更新完成强制使用previewBackgroundBean");
} }
// 点击事件取消背景仅操作Bean无文件逻辑 // 点击事件取消背景仅操作Bean无文件逻辑
@@ -247,9 +226,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
intents[2] = openDocIntent; intents[2] = openDocIntent;
} }
// 查找有效意图 // 查找有效意图Java7 for循环
Intent validIntent = null; Intent validIntent = null;
for (Intent intent : intents) { for (int i = 0; i < intents.length; i++) {
Intent intent = intents[i];
if (intent != null && intent.resolveActivity(getPackageManager()) != null) { if (intent != null && intent.resolveActivity(getPackageManager()) != null) {
validIntent = intent; validIntent = intent;
break; break;
@@ -294,7 +274,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}; };
// 点击事件:固定比例裁剪(调用工具类获取裁剪路径,无文件逻辑) // 点击事件:固定比例裁剪(调用工具类获取裁剪路径,无文件逻辑)
// 点击事件固定比例裁剪添加MIUI裁剪提示
private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() { private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -328,7 +307,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}; };
// 点击事件:自由裁剪(调用工具类获取裁剪路径,无文件逻辑) // 点击事件:自由裁剪(调用工具类获取裁剪路径,无文件逻辑)
// 点击事件自由裁剪添加MIUI裁剪提示
private View.OnClickListener onCropFreePictureClickListener = new View.OnClickListener() { private View.OnClickListener onCropFreePictureClickListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -452,7 +430,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}; };
/** /**
* 压缩图片并保存(调用工具类复制文件,精简文件操作逻辑 * 压缩图片并保存(核心修复:路径非空校验+兜底路径Java7 手动管理流
*/ */
void compressQualityToRecivedPicture(Bitmap bitmap) { void compressQualityToRecivedPicture(Bitmap bitmap) {
LogUtils.d(TAG, "【压缩启动】开始压缩图片Bitmap是否有效" + (bitmap != null && !bitmap.isRecycled())); LogUtils.d(TAG, "【压缩启动】开始压缩图片Bitmap是否有效" + (bitmap != null && !bitmap.isRecycled()));
@@ -463,42 +441,60 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
OutputStream outStream = null; OutputStream outStream = null;
FileOutputStream fos = null; // 新增保留FileOutputStream实例引用 FileOutputStream fos = null;
try { try {
// 从工具类获取预览压缩路径(统一路径管理) // 核心修复1获取压缩路径后,增加非空/空字符串校验
String scaledCompressFilePath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath(); String scaledCompressFilePath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath();
if (TextUtils.isEmpty(scaledCompressFilePath)) {
LogUtils.e(TAG, "【压缩异常】工具类返回预览压缩路径为空,使用兜底路径");
// 核心修复2兜底路径复用App临时目录确保路径有效
File tempDir = new File(App.getTempDirPath(), "PreviewCompress");
if (!tempDir.exists()) {
tempDir.mkdirs();
mBgSourceUtils.copyFile(new File(""), tempDir); // 复用工具类创建目录
}
// 生成唯一兜底文件名(避免重复)
scaledCompressFilePath = new File(tempDir, "preview_compress_" + System.currentTimeMillis() + ".jpg").getAbsolutePath();
LogUtils.d(TAG, "【压缩兜底】使用临时路径:" + scaledCompressFilePath);
}
File fRecivedPicture = new File(scaledCompressFilePath); File fRecivedPicture = new File(scaledCompressFilePath);
LogUtils.d(TAG, "【压缩配置】目标压缩路径:" + scaledCompressFilePath + "Bitmap原始大小" + bitmap.getByteCount() / 1024 + "KB"); LogUtils.d(TAG, "【压缩配置】目标压缩路径:" + scaledCompressFilePath + "Bitmap原始大小" + bitmap.getByteCount() / 1024 + "KB");
// 确保父目录存在(调用工具类创建目录,避免重复代码) // 核心修复3父目录非空校验避免NullPointerException
File parentDir = fRecivedPicture.getParentFile(); File parentDir = fRecivedPicture.getParentFile();
if (parentDir == null) {
LogUtils.e(TAG, "【压缩异常】目标文件父目录为空,无法创建");
ToastUtils.show("压缩失败:路径无效");
return;
}
// 确保父目录存在(调用工具类创建目录,避免重复代码)
if (!parentDir.exists()) { if (!parentDir.exists()) {
parentDir.mkdirs(); parentDir.mkdirs();
mBgSourceUtils.copyFile(new File(""), parentDir); // 复用工具类目录创建逻辑 mBgSourceUtils.copyFile(new File(""), parentDir); // 复用工具类目录创建逻辑
LogUtils.d(TAG, "【压缩准备】目标目录已通过工具类创建:" + parentDir.getAbsolutePath()); LogUtils.d(TAG, "【压缩准备】目标目录已通过工具类创建:" + parentDir.getAbsolutePath());
} }
// 创建目标文件(调用工具类清理旧文件 → 替换为正确的public接口 // 创建目标文件(调用工具类清理旧文件)
if (fRecivedPicture.exists()) { if (fRecivedPicture.exists()) {
mBgSourceUtils.clearOldFileByExternal(fRecivedPicture, "旧压缩文件"); // 关键用新增的public方法 mBgSourceUtils.clearOldFileByExternal(fRecivedPicture, "旧压缩文件");
} }
fRecivedPicture.createNewFile(); fRecivedPicture.createNewFile();
// 压缩并保存 → 关键修改保留FileOutputStream实例 // 压缩并保存Java7 手动管理流)
fos = new FileOutputStream(fRecivedPicture); // 子类实例支持getFD() fos = new FileOutputStream(fRecivedPicture);
outStream = new BufferedOutputStream(fos); // 包装为缓冲流提升性能 outStream = new BufferedOutputStream(fos);
boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream); boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream);
outStream.flush(); // 先强制刷新缓冲区到文件流 outStream.flush();
// 关键修复通过FileOutputStream子类实例调用getFD().sync(),确保数据写入磁盘 // 强制同步数据到磁盘(兼容部分设备)
if (fos != null) { if (fos != null) {
try { try {
fos.getFD().sync(); // 仅子类能调用,强制将文件流同步到物理磁盘 fos.getFD().sync();
LogUtils.d(TAG, "【压缩保存】已强制同步数据到磁盘,确保文件写入完成"); LogUtils.d(TAG, "【压缩保存】已强制同步数据到磁盘");
} catch (IOException e) { } catch (IOException e) {
// 兼容异常:部分设备/Android版本可能不支持sync()用flush()兜底 LogUtils.w(TAG, "【压缩保存】sync()调用失败用flush()兜底" + e.getMessage());
LogUtils.w(TAG, "【压缩保存】getFD().sync()调用失败已用flush()兜底:" + e.getMessage()); outStream.flush();
outStream.flush(); // 双重兜底,确保数据不丢失
} }
} }
@@ -512,7 +508,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
LogUtils.e(TAG, "【压缩异常】IO异常" + e.getMessage()); LogUtils.e(TAG, "【压缩异常】IO异常" + e.getMessage());
ToastUtils.show("图片压缩失败"); ToastUtils.show("图片压缩失败");
} finally { } finally {
// 关闭流资源 → 先关缓冲流,再关文件流(规范操作 // 关闭流资源Java7 手动关闭,避免内存泄漏
if (outStream != null) { if (outStream != null) {
try { try {
outStream.close(); outStream.close();
@@ -520,7 +516,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
LogUtils.e(TAG, "【压缩异常】缓冲流关闭失败:" + e.getMessage()); LogUtils.e(TAG, "【压缩异常】缓冲流关闭失败:" + e.getMessage());
} }
} }
if (fos != null) { // 新增关闭FileOutputStream if (fos != null) {
try { try {
fos.close(); fos.close();
} catch (IOException e) { } catch (IOException e) {
@@ -535,7 +531,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
/** /**
* 启动图片裁剪活动(核心:调用工具类获取裁剪路径,精简文件管理逻辑 * 启动图片裁剪活动(核心:调用工具类获取裁剪路径,Java7 语法
* @param isCropFree 是否自由裁剪 * @param isCropFree 是否自由裁剪
*/ */
public void startCropImageActivity(boolean isCropFree) { public void startCropImageActivity(boolean isCropFree) {
@@ -600,7 +596,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
// 输出尺寸设置(原有逻辑不变) // 输出尺寸设置(原有逻辑不变)
int maxOutputSize = Build.VERSION.SDK_INT >= Build_VERSION_CODES_TIRAMISU ? 1536 : 2048; int maxOutputSize = 2048; // 移除Android13+判断统一用2048适配所有版本
int outputX = Math.min(getResources().getDisplayMetrics().widthPixels, maxOutputSize); int outputX = Math.min(getResources().getDisplayMetrics().widthPixels, maxOutputSize);
int outputY = Math.min(getResources().getDisplayMetrics().heightPixels, maxOutputSize); int outputY = Math.min(getResources().getDisplayMetrics().heightPixels, maxOutputSize);
intent.putExtra("outputX", outputX); intent.putExtra("outputX", outputX);
@@ -616,11 +612,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 权限Flags添加持久化权限适配MIUI // 权限Flags添加持久化权限适配MIUI
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build_VERSION_CODES_TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
} }
// 5. 适配系统裁剪工具MIUI专属处理 // 5. 适配系统裁剪工具MIUI专属处理Java7 for循环
try { try {
List<ResolveInfo> resolveInfos = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); List<ResolveInfo> resolveInfos = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (!resolveInfos.isEmpty()) { if (!resolveInfos.isEmpty()) {
@@ -837,10 +833,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
} }
/**
* 辅助函数解析选图Uri为File核心修复适配Android11+ 共享存储私有路径)
* 放弃直接路径读取改用ContentResolver流复制避免Permission denied
*/
/** /**
* 辅助函数解析选图Uri为File核心修复Android14+ 共享存储私有路径适配) * 辅助函数解析选图Uri为File核心修复Android14+ 共享存储私有路径适配)
* 放弃直接路径读取改用ContentResolver流复制避免Permission denied * 放弃直接路径读取改用ContentResolver流复制避免Permission denied
@@ -878,7 +870,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
/** /**
* 辅助函数通过ContentResolver流复制生成临时文件核心修复绕开共享存储权限限制 * 辅助函数通过ContentResolver流复制生成临时文件核心修复绕开共享存储权限限制
* 直接读取Uri流不依赖文件路径适配所有相册Uri包括私有隐藏路径 * 直接读取Uri流不依赖文件路径适配所有相册Uri包括私有隐藏路径Java7语法
*/ */
private File createTempFileByStreamCopy(Uri uri) { private File createTempFileByStreamCopy(Uri uri) {
// 1. 初始化临时目录(复用工具类目录,统一路径管理) // 1. 初始化临时目录(复用工具类目录,统一路径管理)
@@ -891,7 +883,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
String uniqueFileName = "selected_temp_" + System.currentTimeMillis() + ".jpg"; String uniqueFileName = "selected_temp_" + System.currentTimeMillis() + ".jpg";
File tempFile = new File(tempDir, uniqueFileName); File tempFile = new File(tempDir, uniqueFileName);
// 3. 流复制核心用ContentResolver打开Uri流绕开路径权限限制 // 3. 流复制核心用ContentResolver打开Uri流绕开路径权限限制Java7手动关闭流
InputStream is = null; InputStream is = null;
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
@@ -911,7 +903,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
fos = new FileOutputStream(tempFile); fos = new FileOutputStream(tempFile);
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区提升复制效率 byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区提升复制效率
int len; int len;
// 循环读取流并写入目标文件 // 循环读取流并写入目标文件Java7 while循环
while ((len = is.read(buffer)) != -1) { while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len); fos.write(buffer, 0, len);
} }
@@ -1065,9 +1057,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
* 辅助函数根据图片格式适配Bitmap配置 * 辅助函数根据图片格式适配Bitmap配置
*/ */
private Bitmap.Config getBitmapConfigByMimeType(String mimeType) { private Bitmap.Config getBitmapConfigByMimeType(String mimeType) {
return (mimeType != null && mimeType.contains("png")) if (mimeType != null && mimeType.contains("png")) {
? Bitmap.Config.ARGB_8888 return Bitmap.Config.ARGB_8888;
: Bitmap.Config.RGB_565; } else {
return Bitmap.Config.RGB_565;
}
} }
/** /**
@@ -1085,7 +1079,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
/** /**
* 保存裁剪后的Bitmap调用工具类保存精简文件逻辑 * 保存裁剪后的Bitmap调用工具类保存Java7 语法
*/ */
private void saveCropBitmap(Bitmap bitmap) { private void saveCropBitmap(Bitmap bitmap) {
LogUtils.d(TAG, "【保存启动】开始保存裁剪图片仅更新预览Bean不影响正式Bean"); LogUtils.d(TAG, "【保存启动】开始保存裁剪图片仅更新预览Bean不影响正式Bean");
@@ -1095,7 +1089,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return; return;
} }
// 内存优化:大图片缩放(保留原有逻辑) // 内存优化:大图片缩放(保留原有逻辑Java7 语法
Bitmap scaledBitmap = bitmap; Bitmap scaledBitmap = bitmap;
int originalSize = bitmap.getByteCount() / 1024 / 1024; // 转换为MB int originalSize = bitmap.getByteCount() / 1024 / 1024; // 转换为MB
if (originalSize > 5) { // 超过5MB自动缩放 if (originalSize > 5) { // 超过5MB自动缩放
@@ -1105,409 +1099,261 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
if (scale < 0.2f) break; if (scale < 0.2f) break;
scaledBitmap = scaleBitmap(scaledBitmap, scale); scaledBitmap = scaleBitmap(scaledBitmap, scale);
} }
if (scaledBitmap != bitmap) { LogUtils.d(TAG, "【内存优化】大图片自动缩放:原始大小=" + originalSize + "MB缩放后大小=" + scaledBitmap.getByteCount() / 1024 / 1024 + "MB");
bitmap.recycle(); // 回收原Bitmap
}
} }
// 1. 保存裁剪图到预览路径(调用工具类,统一路径管理)
File cropSaveFile = new File(mBgSourceUtils.getPreviewBackgroundFilePath());
FileOutputStream fos = null; FileOutputStream fos = null;
BufferedOutputStream bos = null;
try { try {
// 1. 生成唯一文件名(仅用于预览图,正式图由退出时拷贝生成 // 清理旧文件复用工具类public方法
String uniqueFileName = FileUtils.createUniqueFileName(new File("background.jpg")); if (cropSaveFile.exists()) {
String compressFileName = "ScaledCompress_" + uniqueFileName; // 预览压缩图文件名 mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "旧裁剪预览图");
}
// 创建新文件确保父目录存在Java7 手动校验)
File parentDir = cropSaveFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
mBgSourceUtils.copyFile(new File(""), parentDir); // 复用目录创建逻辑
}
// 父目录非空校验,避免创建文件失败
if (parentDir == null) {
LogUtils.e(TAG, "【裁剪保存失败】目标文件父目录为空");
ToastUtils.show("裁剪图片保存失败");
return;
}
cropSaveFile.createNewFile();
// 2. 定义预览图路径(统一目录,仅操作预览相关文件 // 写入文件Java7 手动管理流无Lambda/try-with-resources
String backgroundDir = mBgSourceUtils.getBackgroundSourceDirPath(); fos = new FileOutputStream(cropSaveFile);
File targetPreviewOriginalFile = new File(backgroundDir, uniqueFileName); // 裁剪后预览正式图 bos = new BufferedOutputStream(fos);
File targetPreviewCompressFile = new File(backgroundDir, compressFileName); // 裁剪后预览压缩图 scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 85, bos);
bos.flush();
// 确保目录存在(调用工具类,避免创建失败 // 强制同步到磁盘,避免文件损坏(兼容部分设备缓冲问题
File parentDir = targetPreviewOriginalFile.getParentFile(); if (fos != null) {
if (!parentDir.exists()) { try {
mBgSourceUtils.copyFile(new File(""), parentDir); fos.getFD().sync();
} catch (IOException e) {
LogUtils.w(TAG, "【裁剪保存】sync()调用失败用flush()兜底:" + e.getMessage());
bos.flush();
}
} }
// 3. 保存裁剪后的预览正式图(仅写入预览目录,不触碰正式图) LogUtils.d(TAG, "【裁剪保存】预览图保存成功:" + cropSaveFile.getAbsolutePath() + ",大小=" + cropSaveFile.length() + "bytes");
if (targetPreviewOriginalFile.exists()) {
mBgSourceUtils.clearOldFileByExternal(targetPreviewOriginalFile, "旧预览正式图"); // 2. 同步更新预览Bean核心仅操作预览Bean不修改正式Bean
mBgSourceUtils.saveFileToPreviewBean(cropSaveFile, "裁剪后图片");
// 3. 生成压缩预览图(确保预览时加载缩略图,提升性能)
compressQualityToRecivedPicture(scaledBitmap);
ToastUtils.show("裁剪图片保存成功");
} catch (IOException e) {
LogUtils.e(TAG, "【裁剪保存失败】IO异常" + e.getMessage(), e);
ToastUtils.show("裁剪图片保存失败");
// 清理异常文件
if (cropSaveFile.exists()) {
mBgSourceUtils.clearOldFileByExternal(cropSaveFile, "异常裁剪图");
} }
targetPreviewOriginalFile.createNewFile();
mBgSourceUtils.setFilePermissions(targetPreviewOriginalFile);
fos = new FileOutputStream(targetPreviewOriginalFile);
Bitmap.CompressFormat format = targetPreviewOriginalFile.getName().endsWith(".png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG;
boolean previewOriginalSaveSuccess = scaledBitmap.compress(format, 80, fos);
fos.flush();
fos.getFD().sync(); // 强制同步磁盘,确保文件写入完成
fos.close(); // 关闭预览正式图流
// 4. 生成预览压缩图预览Bean依赖此图必须生成
LogUtils.d(TAG, "【裁剪压缩】开始生成预览压缩图:" + targetPreviewCompressFile.getAbsolutePath());
FileOutputStream compressFos = new FileOutputStream(targetPreviewCompressFile);
boolean previewCompressSaveSuccess = scaledBitmap.compress(format, 50, compressFos); // 压缩质量50%(按需调整)
compressFos.flush();
compressFos.getFD().sync();
compressFos.close();
LogUtils.d(TAG, "【裁剪压缩】预览压缩图保存" + (previewCompressSaveSuccess ? "成功" : "失败") + ",大小:" + targetPreviewCompressFile.length() + "bytes");
// 5. 关键逻辑仅更新预览Bean正式Bean不改动留待退出时确认
if (previewOriginalSaveSuccess && previewCompressSaveSuccess
&& targetPreviewOriginalFile.length() > 100
&& targetPreviewCompressFile.length() > 100) {
// 获取当前预览Bean窗口启动时已由正式Bean拷贝而来
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
// 仅更新预览Bean的路径信息不操作正式Bean
previewBean.setBackgroundFileName(uniqueFileName); // 预览正式图文件名
previewBean.setBackgroundFilePath(targetPreviewOriginalFile.getAbsolutePath()); // 预览正式图路径
previewBean.setBackgroundScaledCompressFileName(compressFileName); // 预览压缩图文件名
previewBean.setBackgroundScaledCompressFilePath(targetPreviewCompressFile.getAbsolutePath()); // 预览压缩图路径
previewBean.setIsUseBackgroundFile(true); // 标记预览图可用
previewBean.setIsUseBackgroundScaledCompressFile(false); // 标记:未确认成为正式图(关键标记)
mBgSourceUtils.saveSettings(); // 仅持久化预览Bean不保存正式Bean
// 同步预览文件到预览Bean目录工具类仅操作预览相关文件
mBgSourceUtils.saveFileToPreviewBean(targetPreviewOriginalFile, targetPreviewOriginalFile.getAbsolutePath());
mBgSourceUtils.saveFileToPreviewBean(targetPreviewCompressFile, targetPreviewCompressFile.getAbsolutePath());
ToastUtils.show("预览图片保存成功(未设为正式背景)");
// 触发预览刷新仅加载预览Bean中的路径
doubleRefreshPreview();
} else {
ToastUtils.show("预览图片保存失败");
LogUtils.e(TAG, "【预览保存失败】正式图:" + previewOriginalSaveSuccess + ",压缩图:" + previewCompressSaveSuccess);
// 回滚预览Bean标记为未使用文件
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseBackgroundFile(false);
previewBean.setIsUseBackgroundScaledCompressFile(false);
mBgSourceUtils.saveSettings();
}
} catch (Exception e) {
LogUtils.e(TAG, "【裁剪保存异常】" + e.getMessage(), e);
ToastUtils.show("预览图片保存失败");
// 异常时回滚预览Bean
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseBackgroundFile(false);
previewBean.setIsUseBackgroundScaledCompressFile(false);
mBgSourceUtils.saveSettings();
} finally { } finally {
// 关闭流资源 // 关闭流Java7 手动关闭,避免内存泄漏)
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
LogUtils.e(TAG, "【流关闭失败】BufferedOutputStream" + e.getMessage());
}
}
if (fos != null) { if (fos != null) {
try { try {
fos.close(); fos.close();
} catch (IOException e) { } catch (IOException e) {
LogUtils.e(TAG, "裁剪保存异常】流关闭失败:" + e.getMessage()); LogUtils.e(TAG, "【流关闭失败】FileOutputStream" + e.getMessage());
} }
} }
// 回收Bitmap // 回收Bitmap避免OOMJava7 手动判断)
if (scaledBitmap != null && !scaledBitmap.isRecycled()) { if (scaledBitmap != bitmap && scaledBitmap != null && !scaledBitmap.isRecycled()) {
scaledBitmap.recycle(); scaledBitmap.recycle();
} }
// 清理裁剪临时文件(仅清理缓存,不影响预览/正式文件) if (bitmap != null && !bitmap.isRecycled()) {
mBgSourceUtils.clearCropTempFiles(); bitmap.recycle();
} }
} }
// 4. 清理裁剪临时文件(调用工具类,统一清理)
mBgSourceUtils.clearCropTempFiles();
LogUtils.d(TAG, "【裁剪保存】流程结束,临时文件已清理");
}
/** /**
* 辅助函数缩放Bitmap适配大图片 * 辅助函数缩放BitmapJava7 手动实现不依赖Lambda/Stream
*/ */
private Bitmap scaleBitmap(Bitmap bitmap, float scale) { private Bitmap scaleBitmap(Bitmap bitmap, float scale) {
if (bitmap == null || scale <= 0 || scale >= 1.0f) { if (bitmap == null || bitmap.isRecycled() || scale <= 0) {
return bitmap; return bitmap;
} }
Matrix matrix = new Matrix(); Matrix matrix = new Matrix();
matrix.postScale(scale, scale); matrix.postScale(scale, scale);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); Bitmap scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
LogUtils.d(TAG, "【图片缩放】完成:原始宽高=" + bitmap.getWidth() + "x" + bitmap.getHeight() + ",缩放比例=" + scale + ",新宽高=" + scaledBitmap.getWidth() + "x" + scaledBitmap.getHeight());
return scaledBitmap;
} }
/** /**
* 辅助函数:双重刷新预览适配MIUI渲染延迟 * 双重刷新预览适配MIUI渲染延迟确保裁剪后立即显示Java7 语法
*/ */
private void doubleRefreshPreview() { private void doubleRefreshPreview() {
runOnUiThread(new Runnable() { LogUtils.d(TAG, "【预览刷新】触发双重刷新适配MIUI");
@Override // 首次刷新(立即执行)
public void run() { bvPreviewBackground.reloadPreviewBackground();
// 1. 同时校验正式图和压缩图(两者都存在才刷新 // 延迟刷新解决MIUI渲染延迟Java7 Handler+匿名内部类
String previewOriginalPath = mBgSourceUtils.getPreviewBackgroundFilePath();
final String previewCompressPath = mBgSourceUtils.getPreviewBackgroundScaledCompressFilePath();
File originalFile = new File(previewOriginalPath);
File compressFile = new File(previewCompressPath);
// 校验逻辑:正式图存在 + 压缩图存在 + 大小均>100字节避免空文件
boolean isPreviewReady = originalFile.exists() && originalFile.length() > 100
&& compressFile.exists() && compressFile.length() > 100;
if (!isPreviewReady) {
LogUtils.w(TAG, "【预览刷新】预览图未就绪(正式图:" + originalFile.exists() + ",压缩图:" + compressFile.exists() + "延迟500ms重试");
// 延迟500ms重试适配压缩图生成延迟
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
bvPreviewBackground.reloadPreviewBackground(); bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览刷新】延迟重试刷新500ms压缩图路径" + previewCompressPath); LogUtils.d(TAG, "【预览刷新】双重刷新完成");
} }
}, 500); }, 300); // 300ms延迟适配大多数机型
return;
}
// 2. 第一次刷新(立即)
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览刷新】第一次刷新(立即),正式图路径:" + previewOriginalPath + ",压缩图路径:" + previewCompressPath);
// 3. 第二次刷新延迟300ms适配MIUI渲染延迟
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【预览刷新】第二次刷新延迟300ms压缩图路径" + previewCompressPath);
}
}, 300);
}
});
} }
/** /**
* 检查类型是否为图片(精简版,保留核心校验 * 检查图片类型是否为图片(辅助方法Java7 语法
*/ */
private boolean isImageType(String type) { private boolean isImageType(String type) {
if (TextUtils.isEmpty(type)) { if (TextUtils.isEmpty(type)) {
return false; return false;
} }
return type.startsWith("image/") || "image/jpeg".equals(type) || "image/png".equals(type); return type.startsWith("image/");
} }
/** /**
* 检查并申请存储权限(精简版,仅保留核心权限校验 * 设置背景颜色仅操作Bean无文件逻辑→ 修复替换R.color.transparent为0x00000000纯透明色值
*/
private void setBackgroundColor() {
BackgroundBean bean = mBgSourceUtils.getCurrentBackgroundBean();
int pixelColor = bean.getPixelColor();
if (pixelColor != 0) {
bvPreviewBackground.setBackgroundColor(pixelColor);
LogUtils.d(TAG, "【颜色设置】背景颜色已更新:" + pixelColor);
} else {
// 关键修复用0x00000000ARGB格式A=0即透明替代R.color.transparent兼容所有项目
bvPreviewBackground.setBackgroundColor(0x00000000);
LogUtils.d(TAG, "【颜色设置】背景颜色重置为透明");
}
}
/**
* 检查并申请存储权限适配所有Android版本移除READ_MEDIA_IMAGESJava7 语法)
*/ */
private boolean checkAndRequestStoragePermission() { private boolean checkAndRequestStoragePermission() {
LogUtils.d(TAG, "【权限校验】checkAndRequestStoragePermission 触发Android版本" + Build.VERSION.SDK_INT); LogUtils.d(TAG, "【权限检查】开始检查存储权限Android版本" + Build.VERSION.SDK_INT);
List<String> needPermissions = new ArrayList<String>();
// Android14+:申请WRITE_EXTERNAL_STORAGE // 统一用WRITE_EXTERNAL_STORAGE + READ_EXTERNAL_STORAGE适配所有Android版本避免READ_MEDIA_IMAGES找不到符号
if (Build.VERSION.SDK_INT >= Build_VERSION_CODES_TIRAMISU) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
boolean hasWritePerm = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; needPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (!hasWritePerm) { }
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_REQUEST); if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
needPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
// 申请必要权限Java7 for循环无增强for循环简化
if (!needPermissions.isEmpty()) {
String[] permissionsArr = new String[needPermissions.size()];
for (int i = 0; i < needPermissions.size(); i++) {
permissionsArr[i] = needPermissions.get(i);
}
ActivityCompat.requestPermissions(this, permissionsArr, STORAGE_PERMISSION_REQUEST);
LogUtils.d(TAG, "【权限申请】已触发权限申请:" + Arrays.toString(permissionsArr));
return false; return false;
} }
}
// Android11+:检查所有文件访问权限 LogUtils.d(TAG, "【权限检查】存储权限已全部获取");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
boolean hasAllFilePerm = Environment.isExternalStorageManager();
if (!hasAllFilePerm) {
startActivity(new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION));
ToastUtils.show("请开启「所有文件访问权限」");
return false;
}
}
// Android6.0-10申请读写权限
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
boolean hasReadPerm = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
boolean hasWritePerm = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
if (!hasReadPerm || !hasWritePerm) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_REQUEST);
return false;
}
}
LogUtils.d(TAG, "【权限校验】存储权限校验通过");
return true; return true;
} }
/**
* 设置页面背景颜色(仅操作控件,无文件逻辑)
*/
void setBackgroundColor() {
LogUtils.d(TAG, "【背景设置】setBackgroundColor 触发");
BackgroundBean bean = mBgSourceUtils.getCurrentBackgroundBean();
int pixelColor = bean.getPixelColor();
LogUtils.d(TAG, "【背景设置】当前像素颜色:" + pixelColor);
RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1);
if (mainLayout != null) {
mainLayout.setBackgroundColor(pixelColor);
} else {
LogUtils.e(TAG, "【背景设置】主布局为空,无法更新颜色");
}
}
@Override
protected void onResume() {
super.onResume();
LogUtils.d(TAG, "【生命周期】onResume 触发");
// 刷新背景颜色+预览
setBackgroundColor();
bvPreviewBackground.reloadPreviewBackground();
LogUtils.d(TAG, "【生命周期】onResume 完成");
}
/**
* 显示网络图片下载对话框仅UI逻辑
*/
public void onNetworkBackgroundDialog(View view) {
LogUtils.d(TAG, "【网络图片】onNetworkBackgroundDialog 触发");
NetworkBackgroundDialog dialog = new NetworkBackgroundDialog(this, new NetworkBackgroundDialog.OnDialogClickListener() {
@Override
public void onConfirm(String szConfirmFilePath, String szConfirmFileUrl) {
LogUtils.d(TAG, "【网络图片】用户确认下载:" + szConfirmFilePath);
preViewFilePath = szConfirmFilePath;
preViewFileUrl = szConfirmFileUrl;
onRecivedPictureListener.onRecivedPicture(preViewFilePath, preViewFileUrl);
}
@Override
public void onCancel() {
LogUtils.d(TAG, "【网络图片】用户取消下载");
}
});
dialog.setTitle("网络图片下载");
dialog.setContent("是否下载该图片作为背景?");
dialog.show();
}
/**
* 图片接收监听器(调用工具类同步预览,无文件逻辑)
*/
interface OnRecivedPictureListener {
void onRecivedPicture(String srcFilePath, String srcFileUrl);
}
// 图片接收监听器实现(精简版,依赖工具类)
OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){
@Override
public void onRecivedPicture(String srcFilePath, String srcFileUrl) {
LogUtils.d(TAG, "【图片接收】onRecivedPicture 触发,文件路径:" + srcFilePath + "文件Url" + srcFileUrl);
// 校验文件路径有效性
if (TextUtils.isEmpty(srcFilePath)) {
ToastUtils.show("网络图片路径为空");
LogUtils.e(TAG, "【图片接收失败】图片路径为空,无法处理");
return;
}
// 校验文件是否存在且有效(依赖工具类目录逻辑,无本地文件操作)
File srcFile = new File(srcFilePath);
LogUtils.d(TAG, "【图片接收校验】图片文件:路径=" + srcFile.getAbsolutePath() + ",是否存在=" + srcFile.exists() + ",文件大小=" + srcFile.length() + " bytes");
if (!srcFile.exists() || srcFile.length() <= 0) {
ToastUtils.show("网络图片文件不存在或损坏");
LogUtils.e(TAG, "【图片接收失败】图片文件无效,无法加载");
return;
}
// 同步图片到预览Bean并刷新调用工具类统一处理无本地文件复制
mBgSourceUtils.saveFileToPreviewBean(srcFile, srcFileUrl);
LogUtils.d(TAG, "【图片接收】图片已同步到预览Bean刷新预览视图");
// 刷新预览(确保图片正常显示)
bvPreviewBackground.reloadPreviewBackground();
// 启动自由裁剪(调用工具类获取裁剪路径,无本地文件管理)
startCropImageActivity(true);
LogUtils.d(TAG, "【图片接收】已启动自由裁剪,裁剪路径由工具类管理");
}
};
/**
* 重写finish方法确保所有退出场景都触发确认提示仅操作Bean无文件逻辑
*/
@Override
public void finish() {
LogUtils.d(TAG, "【生命周期】finish 触发,是否已提交配置:" + isCommitSettings);
// 未提交配置时,显示确认对话框(避免误退出丢失预览配置)
if (!isCommitSettings) {
LogUtils.d(TAG, "【退出确认】未提交配置,显示应用背景确认对话框");
YesNoAlertDialog.show(this, "应用背景更改提示:", "是否应用预览图片?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onNo() {
// 用户选择“不应用”保留原正式背景仅更新启用状态依赖工具类Bean
LogUtils.d(TAG, "【退出确认】用户选择:不应用预览图片,保留原背景配置");
isCommitSettings = true; // 标记为已提交,避免重复弹窗
BackgroundBean currentBean = mBgSourceUtils.getCurrentBackgroundBean();
// 根据预览路径是否为空,同步正式背景启用状态(避免残留无效配置)
currentBean.setIsUseBackgroundFile(!TextUtils.isEmpty(preViewFilePath));
mBgSourceUtils.saveSettings(); // 调用工具类持久化原配置(仅更新启用状态)
LogUtils.d(TAG, "【退出配置】原背景配置保存完成,正式背景启用状态:" + currentBean.isUseBackgroundFile());
// 退出前清理裁剪临时文件(调用工具类统一清理,无本地文件删除)
mBgSourceUtils.clearCropTempFiles();
finish();
}
@Override
public void onYes() {
// 用户选择“应用”:将预览背景深拷贝到正式背景(调用工具类统一处理)
LogUtils.d(TAG, "【退出确认】用户选择:应用预览图片到正式背景");
mBgSourceUtils.commitPreviewSourceToCurrent(); // 核心:工具类处理深拷贝+持久化
isCommitSettings = true; // 标记为已提交
LogUtils.d(TAG, "【退出配置】预览背景提交完成,正式背景已更新");
ToastUtils.show("背景图片应用成功"); // 补充用户反馈,明确操作结果
// 退出前清理裁剪临时文件(调用工具类统一清理)
mBgSourceUtils.clearCropTempFiles();
finish();
}
});
} else {
// 已提交配置(或用户已选择弹窗选项),直接执行退出,避免循环
LogUtils.d(TAG, "【生命周期】已提交配置执行super.finish()正常退出");
// 退出前清理裁剪临时文件(调用工具类统一清理)
mBgSourceUtils.clearCropTempFiles();
super.finish();
}
}
/**
* 权限申请回调(仅处理权限结果,无文件逻辑)
*/
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LogUtils.d(TAG, "【权限回调】onRequestPermissionsResult 触发requestCode" + requestCode); LogUtils.d(TAG, "【权限回调】onRequestPermissionsResult 触发requestCode" + requestCode);
// 处理存储权限申请结果
if (requestCode == STORAGE_PERMISSION_REQUEST) { if (requestCode == STORAGE_PERMISSION_REQUEST) {
boolean isGranted = false; boolean allGranted = true;
// 校验所有申请的权限是否都通过(读写权限需同时授予 // 校验所有权限是否授予Java7 for循环无增强for循环
for (int i = 0; i < grantResults.length; i++) { for (int i = 0; i < grantResults.length; i++) {
String perm = permissions[i]; if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
int result = grantResults[i]; allGranted = false;
LogUtils.d(TAG, "【权限回调】权限:" + perm + ",申请结果:" + (result == PackageManager.PERMISSION_GRANTED ? "通过" : "拒绝")); LogUtils.d(TAG, "【权限回调】权限未授予" + permissions[i]);
if (result == PackageManager.PERMISSION_GRANTED) {
isGranted = true;
} else {
isGranted = false;
break; break;
} }
} }
if (isGranted) {
ToastUtils.show("存储权限已获取,正在重试操作"); if (allGranted) {
LogUtils.d(TAG, "【权限回调】存储权限全部通过,自动重试上次操作"); LogUtils.d(TAG, "【权限回调】所有存储权限已授予");
// 自动重试图片选择(无需用户再次点击按钮) ToastUtils.show("权限获取成功,请重新操作");
onSelectPictureClickListener.onClick(findViewById(R.id.activitybackgroundpictureAButton2));
} else { } else {
ToastUtils.show("需要存储权限才能选择/保存图片"); LogUtils.d(TAG, "【权限回调】部分/全部存储权限被拒绝");
LogUtils.e(TAG, "【权限回调】存储权限申请被拒绝,图片功能受限"); // 检查是否勾选“不再询问”适配Android 6.0+Java7 for循环
} boolean shouldShowRationale = false;
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
shouldShowRationale = true;
break;
} }
} }
/** if (shouldShowRationale) {
* 兼容父类方法(若有重写需求,保留空实现或精简逻辑) // 未勾选“不再询问”提示用户授予权限Java7 匿名内部类)
*/ new AlertDialog.Builder(this)
.setTitle("权限申请")
.setMessage("需要存储权限才能选择/拍照/裁剪图片,请授予权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override @Override
public void onBackPressed() { public void onClick(DialogInterface dialog, int which) {
LogUtils.d(TAG, "【生命周期】onBackPressed 触发执行finish"); checkAndRequestStoragePermission();
finish(); }
})
.setNegativeButton("取消", null)
.show();
} else {
// 已勾选“不再询问”引导用户去设置页开启权限Java7 匿名内部类)
new AlertDialog.Builder(this)
.setTitle("权限被拒绝")
.setMessage("存储权限已被拒绝且勾选“不再询问”,请前往设置页开启权限")
.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
})
.setNegativeButton("取消", null)
.show();
}
}
}
} }
/**
* 避免内存泄漏:清空工具类引用(可选,根据父类生命周期规范补充)
*/
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
LogUtils.d(TAG, "【生命周期】onDestroy 触发,清理资源"); LogUtils.d(TAG, "【生命周期】BackgroundSettingsActivity 销毁");
// 清空工具类引用避免Activity销毁后持有引用导致内存泄漏 // 清理临时资源Java7 手动校验非空
if (mBgSourceUtils != null) { if (mfTakePhoto != null && mfTakePhoto.exists()) {
mBgSourceUtils.clearCropTempFiles(); // 退出前最后清理一次临时文件 mBgSourceUtils.clearOldFileByExternal(mfTakePhoto, "拍照临时文件");
} }
mBgSourceUtils = null; mBgSourceUtils.clearCropTempFiles();
// 清空控件引用 // 清理压缩兜底路径的临时文件(避免残留)
bvPreviewBackground = null; File compressTempDir = new File(App.getTempDirPath(), "PreviewCompress");
mAToolbar = null; if (compressTempDir != null && compressTempDir.exists()) {
LogUtils.d(TAG, "【生命周期】onDestroy 完成,资源清理完毕"); mBgSourceUtils.clearOldFileByExternal(compressTempDir, "压缩兜底临时目录");
}
// 关键修复:删除不存在的 bvPreviewBackground.recycleBitmap() 调用
// 原因BackgroundView 源码中无此方法,且内部已通过 setDefaultTransparentBackground() 处理资源释放
LogUtils.d(TAG, "Activity销毁临时文件已清理BackgroundView资源由自身生命周期管理");
} }
} }