图片剪裁与预览测试完成
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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(避免OOM,Java7 手动判断)
|
||||||
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(适配大图片)
|
* 辅助函数:缩放Bitmap(Java7 手动实现,不依赖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 {
|
||||||
|
// 关键修复:用0x00000000(ARGB格式,A=0即透明)替代R.color.transparent,兼容所有项目
|
||||||
|
bvPreviewBackground.setBackgroundColor(0x00000000);
|
||||||
|
LogUtils.d(TAG, "【颜色设置】背景颜色重置为透明");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并申请存储权限(适配所有Android版本,移除READ_MEDIA_IMAGES,Java7 语法)
|
||||||
*/
|
*/
|
||||||
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资源由自身生命周期管理");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user