应用权限框架更新,重新调试剪裁文件流程。

This commit is contained in:
2025-12-11 09:02:41 +08:00
parent ecafd2026f
commit 220aa9dbfb
7 changed files with 532 additions and 342 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #Created by .winboll/winboll_app_build.gradle
#Wed Dec 10 22:51:19 GMT 2025 #Thu Dec 11 00:57:37 GMT 2025
stageCount=15 stageCount=15
libraryProject= libraryProject=
baseVersion=15.12 baseVersion=15.12
publishVersion=15.12.14 publishVersion=15.12.14
buildCount=5 buildCount=13
baseBetaVersion=15.12.15 baseBetaVersion=15.12.15

View File

@@ -44,7 +44,6 @@ public class App extends GlobalApplication {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
setIsDebugging(BuildConfig.DEBUG); setIsDebugging(BuildConfig.DEBUG);
PermissionUtils.init(this);
// 初始化活动窗口管理 // 初始化活动窗口管理
WinBoLLActivityManager.init(this); WinBoLLActivityManager.init(this);

View File

@@ -58,7 +58,7 @@ public class MainActivity extends WinBoLLActivity {
// ======================== 静态常量(移除缓存相关键)======================== // ======================== 静态常量(移除缓存相关键)========================
public static final String TAG = "MainActivity"; public static final String TAG = "MainActivity";
private static final int REQUEST_WRITE_STORAGE_PERMISSION = 1001; private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
public static final int MSG_RELOAD_APPCONFIG = 0; public static final int MSG_RELOAD_APPCONFIG = 0;
public static final int MSG_CURRENTVALUEBATTERY = 1; public static final int MSG_CURRENTVALUEBATTERY = 1;
public static final int MSG_LOAD_BACKGROUND = 2; public static final int MSG_LOAD_BACKGROUND = 2;
@@ -123,9 +123,7 @@ public class MainActivity extends WinBoLLActivity {
loadNonCriticalViewDelayed(); loadNonCriticalViewDelayed();
// 权限申请 // 权限申请
if (!PermissionUtils.getInstance().hasFullStoragePermission()) { PermissionUtils.getInstance().checkAndRequestMediaImagesPermission(this, REQUEST_READ_MEDIA_IMAGES);
PermissionUtils.getInstance().requestFullStoragePermission(this);
}
} }
// 移除 onSaveInstanceState 方法 // 移除 onSaveInstanceState 方法
@@ -217,6 +215,14 @@ public class MainActivity extends WinBoLLActivity {
} }
} }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_READ_MEDIA_IMAGES) {
PermissionUtils.getInstance().handleStoragePermissionResult(this, requestCode, permissions, grantResults);
}
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
moveTaskToBack(true); moveTaskToBack(true);

View File

@@ -3,6 +3,7 @@ package cc.winboll.studio.powerbell.activities;
import android.app.Activity; import android.app.Activity;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat; import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
@@ -14,11 +15,24 @@ 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.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.appcompat.widget.Toolbar;
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.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;
@@ -27,26 +41,27 @@ import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog; import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog;
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.BitmapCacheUtils;
import cc.winboll.studio.powerbell.utils.FileUtils; import cc.winboll.studio.powerbell.utils.FileUtils;
import cc.winboll.studio.powerbell.utils.ImageCropUtils; import cc.winboll.studio.powerbell.utils.ImageCropUtils;
import cc.winboll.studio.powerbell.utils.PermissionUtils; import cc.winboll.studio.powerbell.utils.PermissionUtils;
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.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
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";
private BackgroundSourceUtils mBgSourceUtils; private BackgroundSourceUtils mBgSourceUtils;
private PermissionUtils mPermissionUtils; 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; public static final int REQUEST_SELECT_PICTURE = 0;
public static final int REQUEST_TAKE_PHOTO = 1; public static final int REQUEST_TAKE_PHOTO = 1;
public static final int REQUEST_CROP_IMAGE = 2; public static final int REQUEST_CROP_IMAGE = 2;
private static final int REQUEST_READ_MEDIA = 1001;
private Toolbar mToolbar; private Toolbar mToolbar;
private BackgroundView mBackgroundView; private BackgroundView mBackgroundView;
@@ -73,6 +88,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
mBgSourceUtils = BackgroundSourceUtils.getInstance(this); mBgSourceUtils = BackgroundSourceUtils.getInstance(this);
mBgSourceUtils.loadSettings(); mBgSourceUtils.loadSettings();
mPermissionUtils = PermissionUtils.getInstance(); mPermissionUtils = PermissionUtils.getInstance();
mBitmapCache = BitmapCacheUtils.getInstance();
File tempDir = new File(App.getTempDirPath()); File tempDir = new File(App.getTempDirPath());
if (!tempDir.exists()) { if (!tempDir.exists()) {
@@ -95,9 +111,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
mBgSourceUtils.setCurrentSourceToPreview(); mBgSourceUtils.setCurrentSourceToPreview();
} }
Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath())); //Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()));
// 创建预览数据剪裁环境 File fTestSourceExist = new File(mBgSourceUtils.getCurrentBackgroundBean().getBackgroundFilePath());
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean()); boolean isCreateNewPreviewBean = !fTestSourceExist.exists();
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(isCreateNewPreviewBean, mBgSourceUtils.getPreviewBackgroundBean());
doubleRefreshPreview(); doubleRefreshPreview();
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成"); LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成");
} }
@@ -109,7 +126,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
private void initToolbar() { private void initToolbar() {
mToolbar = (Toolbar) findViewById(R.id.toolbar); mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar); setSupportActionBar(mToolbar);
mToolbar.setSubtitle(getTag()); mToolbar.setSubtitle(getTag());
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText); mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
@@ -173,39 +190,57 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
}; };
// ====================== 核心修改Java 7 兼容的相册选择点击事件 ======================
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() { private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
LogUtils.d(TAG, "【按钮点击】选择图片"); LogUtils.d(TAG, "【按钮点击】选择图片");
// 适配 API 30+ 权限逻辑 // 适配Android 13+权限(已替换为手动定义的常量)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) {
// API 30+ 优先判断全文件管理权限 if (mPermissionUtils.checkAndRequestMediaImagesPermission(BackgroundSettingsActivity.this, REQUEST_READ_MEDIA)) {
if (!Environment.isExternalStorageManager()) { launchImageSelector();
mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
return;
} }
} else { } else {
// 低版本判断传统读写权限 if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) {
if (!mPermissionUtils.hasFullStoragePermission()) { launchImageSelector();
mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
return;
} }
} }
}
};
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;
// API 30+ 推荐的标准化相册选择 Intent
Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.setType("image/*"); pickIntent.setType("image/*");
// 添加权限 Flag确保 Uri 可读取
pickIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); pickIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intents[1] = pickIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 持久化 Uri 权限,避免后续操作失效 Intent openDocIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
pickIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); 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;
Intent chooser = Intent.createChooser(pickIntent, "选择图片"); for (int i = 0; i < intents.length; i++) {
if (chooser.resolveActivity(getPackageManager()) != null) { 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); startActivityForResult(chooser, REQUEST_SELECT_PICTURE);
LogUtils.d(TAG, "【选图意图】启动图片选择"); LogUtils.d(TAG, "【选图意图】启动图片选择");
} else { } else {
@@ -235,19 +270,18 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}); });
} }
} }
};
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) {
LogUtils.d(TAG, "【按钮点击】固定比例裁剪"); LogUtils.d(TAG, "【按钮点击】固定比例裁剪");
// 调用裁剪工具类:传入上下文、预览图、固定比例(按视图宽高)、请求码
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
mBgSourceUtils.getPreviewBackgroundBean(), mBgSourceUtils.getPreviewBackgroundBean(),
mBackgroundView.getWidth(), mBackgroundView.getWidth(),
mBackgroundView.getHeight(), mBackgroundView.getHeight(),
false, false,
REQUEST_CROP_IMAGE); REQUEST_CROP_IMAGE
);
} }
}; };
@@ -255,13 +289,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override @Override
public void onClick(View v) { public void onClick(View v) {
LogUtils.d(TAG, "【按钮点击】自由裁剪"); LogUtils.d(TAG, "【按钮点击】自由裁剪");
// 调用裁剪工具类传入上下文、预览图、自由裁剪比例参数传0、请求码
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
mBgSourceUtils.getPreviewBackgroundBean(), mBgSourceUtils.getPreviewBackgroundBean(),
0, 0,
0, 0,
true, true,
REQUEST_CROP_IMAGE); REQUEST_CROP_IMAGE
);
} }
}; };
@@ -286,7 +320,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return; return;
} }
if (mPermissionUtils.hasFullStoragePermission()) { if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) {
LogUtils.d(TAG, "【拍照权限】已获取"); LogUtils.d(TAG, "【拍照权限】已获取");
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
try { try {
@@ -300,7 +334,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
LogUtils.e(TAG, "【拍照失败】"); LogUtils.e(TAG, "【拍照失败】");
} }
} else { } else {
mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
LogUtils.d(TAG, "【拍照权限】已申请"); LogUtils.d(TAG, "【拍照权限】已申请");
} }
} }
@@ -318,7 +351,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override @Override
public void onClick(View v) { public void onClick(View v) {
LogUtils.d(TAG, "【按钮点击】像素拾取"); LogUtils.d(TAG, "【按钮点击】像素拾取");
String targetImagePath = mBgSourceUtils.getCurrentBackgroundFilePath(); String targetImagePath = mBgSourceUtils.getCurrentBackgroundBean().getBackgroundFilePath();
File targetFile = new File(targetImagePath); File targetFile = new File(targetImagePath);
if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) { if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) {
ToastUtils.show("无有效图片可拾取像素"); ToastUtils.show("无有效图片可拾取像素");
@@ -352,7 +385,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
LogUtils.d(TAG, "【回调触发】requestCode" + requestCode + "resultCode" + resultCode); LogUtils.d(TAG, "【回调触发】requestCode" + requestCode + "resultCode" + resultCode);
try { try {
if (requestCode == PermissionUtils.REQUEST_CODE_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (requestCode == PermissionUtils.REQUEST_READ_MEDIA_IMAGES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
handleStoragePermissionCallback(); handleStoragePermissionCallback();
return; return;
} }
@@ -393,8 +426,12 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
private void handleTakePhotoResult(int resultCode, Intent data) { private void handleTakePhotoResult(int resultCode, Intent data) {
if (resultCode != RESULT_OK || !mfTakePhoto.exists() || mfTakePhoto.length() <= 0) { if (resultCode != RESULT_OK || data == null) {
handleOperationCancelOrFail(); handleOperationCancelOrFail();
return;
}
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
ToastUtils.show("拍照文件无效"); ToastUtils.show("拍照文件无效");
return; return;
} }
@@ -410,13 +447,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
mBgSourceUtils.saveFileToPreviewBean(mfTakePhoto, mfTakePhoto.getAbsolutePath()); mBgSourceUtils.saveFileToPreviewBean(mfTakePhoto, mfTakePhoto.getAbsolutePath());
doubleRefreshPreview(); doubleRefreshPreview();
// 拍照后启动固定比例裁剪(调用工具类)
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
mBgSourceUtils.getPreviewBackgroundBean(), mBgSourceUtils.getPreviewBackgroundBean(),
mBackgroundView.getWidth(), mBackgroundView.getWidth(),
mBackgroundView.getHeight(), mBackgroundView.getHeight(),
false, false,
REQUEST_CROP_IMAGE); REQUEST_CROP_IMAGE
);
LogUtils.d(TAG, "【拍照完成】已启动裁剪"); LogUtils.d(TAG, "【拍照完成】已启动裁剪");
} }
@@ -467,9 +504,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
ToastUtils.show("图片Uri为空"); ToastUtils.show("图片Uri为空");
return; return;
} }
LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString()); LogUtils.d(TAG, "【选图回调】系统返回Uri : " + selectedImage.toString());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 1. 申请持久化读取权限
if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) {
getContentResolver().takePersistableUriPermission( getContentResolver().takePersistableUriPermission(
selectedImage, selectedImage,
Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -477,15 +515,46 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
LogUtils.d(TAG, "【选图权限】已添加持久化权限"); LogUtils.d(TAG, "【选图权限】已添加持久化权限");
} }
// 2. 强制拷贝到应用私有目录(彻底规避系统虚拟路径问题)
// String privateImagePath = copyUriToPrivateDir(selectedImage);
// if (TextUtils.isEmpty(privateImagePath)) {
// ToastUtils.show("系统图片路径无效,拷贝失败");
// LogUtils.e(TAG, "【选图校验】系统Uri拷贝失败Uri=" + selectedImage.toString());
// return;
// }
//
// // 3. 严格校验私有目录文件有效性
// File srcFile = new File(privateImagePath);
// if (srcFile == null
// || !srcFile.exists()
// || !srcFile.isFile()
// || srcFile.length() <= 0) {
// ToastUtils.show("系统图片文件无效");
// LogUtils.e(TAG, "【选图校验】私有目录文件无效,路径=" + privateImagePath);
// // 清理无效文件
// if (srcFile != null && srcFile.exists()) {
// srcFile.delete();
// }
// return;
// }
//LogUtils.d(TAG, "【选图校验】系统图片校验通过,私有路径:" + privateImagePath);
// 4. 同步到预览 Bean 并启动裁剪
if (putUriFileToPreviewSource(selectedImage)) {
LogUtils.d(TAG, "【选图同步】路径绑定完成"); LogUtils.d(TAG, "【选图同步】路径绑定完成");
// 选图后启动固定比例裁剪(调用工具类) mBitmapCache.removeCachedBitmap(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
putUriFileToPreviewSource(selectedImage);
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
mBgSourceUtils.getPreviewBackgroundBean(), mBgSourceUtils.getPreviewBackgroundBean(),
mBackgroundView.getWidth(), mBackgroundView.getWidth(),
mBackgroundView.getHeight(), mBackgroundView.getHeight(),
false, false,
REQUEST_CROP_IMAGE); REQUEST_CROP_IMAGE
);
} else {
ToastUtils.show("图片同步失败");
LogUtils.e(TAG, "【选图同步】文件复制失败");
}
} }
boolean putUriFileToPreviewSource(Uri srcUriFile) { boolean putUriFileToPreviewSource(Uri srcUriFile) {
@@ -494,6 +563,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
boolean putUriFileToPreviewSource(File srcFile) { boolean putUriFileToPreviewSource(File srcFile) {
LogUtils.d(TAG, String.format("putUriFileToPreviewSource(File srcFile) srcFile %s", srcFile));
mBgSourceUtils.loadSettings(); mBgSourceUtils.loadSettings();
File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
return FileUtils.copyFile(srcFile, dstFile); return FileUtils.copyFile(srcFile, dstFile);
@@ -531,7 +601,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
} }
// 延迟300ms调用双重刷新确保文件写入完成
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -685,20 +754,32 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private void doubleRefreshPreview() { private void doubleRefreshPreview() {
LogUtils.d(TAG, "【双重刷新】开始"); LogUtils.d(TAG, "【双重刷新】开始");
// 1. 清空缓存工具类的旧数据 // 清空缓存工具类的旧数据
if (mBgSourceUtils != null) { if (mBgSourceUtils != null) {
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
if (previewBean != null && previewBean.isUseBackgroundFile()) { if (previewBean != null && previewBean.isUseBackgroundFile()) {
String bgPath = previewBean.isUseBackgroundScaledCompressFile() String bgPath = previewBean.isUseBackgroundScaledCompressFile()
? previewBean.getBackgroundScaledCompressFilePath() ? previewBean.getBackgroundScaledCompressFilePath()
: previewBean.getBackgroundFilePath(); : previewBean.getBackgroundFilePath();
// 强制清除缓存 mBitmapCache.removeCachedBitmap(bgPath);
App._mBitmapCacheUtils.removeCachedBitmap(bgPath);
LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath); LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath);
} }
} }
// 2. 重新加载最新数据 // 清空 BackgroundView 自身的 Bitmap 引用
// if (mBackgroundView != null) {
// if (mBackgroundView instanceof ImageView) {
// ((ImageView) mBackgroundView).setImageBitmap(null);
// }
// Drawable drawable = mBackgroundView.getBackground();
// if (drawable != null) {
// drawable.setCallback(null);
// mBackgroundView.setBackground(null);
// }
// LogUtils.d(TAG, "【双重刷新】已清空控件 Bitmap 引用");
// }
// 重新加载最新数据
if (mBackgroundView != null && !isFinishing()) { if (mBackgroundView != null && !isFinishing()) {
mBgSourceUtils.loadSettings(); mBgSourceUtils.loadSettings();
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
@@ -708,7 +789,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return; return;
} }
// 3. 延长延迟到200ms确保文件完全加载 // 延长延迟到200ms确保文件完全加载
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -724,7 +805,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private void handleOperationCancelOrFail() { private void handleOperationCancelOrFail() {
mBgSourceUtils.setCurrentSourceToPreview(); mBgSourceUtils.setCurrentSourceToPreview();
LogUtils.d(TAG, "【操作回调】取消或失败"); LogUtils.d(TAG, "【操作回调】取消或失败");
ToastUtils.show("操作取消或失败"); ToastUtils.show("操作回调】取消或失败");
doubleRefreshPreview(); doubleRefreshPreview();
} }
@@ -752,30 +833,92 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override @Override
public void finish() { public void finish() {
if (isCommitSettings) { if (isCommitSettings) {
setResult(RESULT_OK);
super.finish(); super.finish();
} else { } else {
// 如果预览背景改变过就提示是否更换背景
if (isPreviewBackgroundChanged) { if (isPreviewBackgroundChanged) {
YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener() { YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener() {
@Override @Override
public void onYes() { public void onYes() {
mBgSourceUtils.commitPreviewSourceToCurrent(); mBgSourceUtils.commitPreviewSourceToCurrent();
isCommitSettings = true; isCommitSettings = true;
setResult(RESULT_OK);
finish(); finish();
} }
@Override @Override
public void onNo() { public void onNo() {
isCommitSettings = true; isCommitSettings = true;
setResult(RESULT_CANCELED);
finish(); finish();
} }
}); });
} else { } else {
// 如果预览背景未改变就直接退出 setResult(RESULT_OK);
isCommitSettings = true; isCommitSettings = true;
finish(); finish();
} }
} }
} }
/**
* 解析Uri到真实文件路径兼容所有Android版本
* @param uri 图片的Content Uri
* @return 真实文件路径 / null
*/
// 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对应的图片拷贝到应用私有临时目录
* @param uri 图片Uri
* @return 拷贝后的文件路径 / null
*/
// 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;
// }
// }
} }

View File

@@ -18,6 +18,7 @@ import cc.winboll.studio.powerbell.utils.PermissionUtils;
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity { public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "SettingsActivity"; public static final String TAG = "SettingsActivity";
private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
private Toolbar mToolbar; private Toolbar mToolbar;
@@ -52,10 +53,15 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
public void onCheckPermission(View view) { public void onCheckPermission(View view) {
//ToastUtils.show("onCheckPermission"); //ToastUtils.show("onCheckPermission");
if (PermissionUtils.getInstance().hasFullStoragePermission()) { PermissionUtils.getInstance().checkAndRequestMediaImagesPermission(this, REQUEST_READ_MEDIA_IMAGES);
ToastUtils.show("【权限检查】存储权限已全部获取"); }
} else {
PermissionUtils.getInstance().requestFullStoragePermission(this);
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_READ_MEDIA_IMAGES) {
PermissionUtils.getInstance().handleStoragePermissionResult(this, requestCode, permissions, grantResults);
} }
} }
} }

View File

@@ -4,12 +4,15 @@ import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.media.ExifInterface; import android.media.ExifInterface;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.core.content.FileProvider;
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.BuildConfig; import cc.winboll.studio.powerbell.BuildConfig;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.model.BackgroundBean; import cc.winboll.studio.powerbell.model.BackgroundBean;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
@@ -19,8 +22,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.UUID; import java.util.UUID;
import android.os.Build;
import androidx.core.content.FileProvider;
/** /**
* @Author ZhanGSKen<zhangsken@qq.com> * @Author ZhanGSKen<zhangsken@qq.com>
@@ -248,29 +249,36 @@ public class BackgroundSourceUtils {
/* /*
* 创建预览数据剪裁环境 * 创建预览数据剪裁环境
*/ */
public boolean createAndUpdatePreviewEnvironmentForCropping(BackgroundBean oldPreviewBackgroundBean) { public boolean createAndUpdatePreviewEnvironmentForCropping(boolean isCreateNewPreviewBean, BackgroundBean previewBackgroundBean) {
InputStream is = null; InputStream is = null;
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
clearCropTempFiles(); clearCropTempFiles();
Uri uri = UriUtil.getUriForFile(mContext, oldPreviewBackgroundBean.getBackgroundFilePath());
//String szType = mContext.getContentResolver().getType(uri);
// 2. 截取MIME类型后缀如从image/jpeg中提取jpeg【核心新增逻辑】
String fileSuffix = FileUtils.getFileSuffix(mContext, uri);
String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis(); String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis();
String fileSuffix;
if (isCreateNewPreviewBean) {
fileSuffix = "png";
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
DrawableToFileUtils.saveDrawableToFile(mContext, R.drawable.blank10x10, mCropSourceFile.getAbsolutePath());
mCropResultFile.createNewFile();
} else {
if (FileUtils.isFileExists(previewBackgroundBean.getBackgroundFilePath())) {
File fSourceFileOld = new File(previewBackgroundBean.getBackgroundFilePath());
Uri uri = UriUtil.getUriForFile(mContext, fSourceFileOld);
fileSuffix = FileUtils.getFileSuffix(mContext, uri);
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix); mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix); mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath())) { FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile); if (FileUtils.isFileExists(previewBackgroundBean.getBackgroundScaledCompressFilePath())) {
FileUtils.copyFile(new File(previewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile);
} else { } else {
mCropResultFile.createNewFile(); mCropResultFile.createNewFile();
} }
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundFilePath())) {
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
} else {
mCropSourceFile.createNewFile(); mCropSourceFile.createNewFile();
// 1. 打开Uri输入流兼容content:///file:// 等多种Uri格式 // 1. 打开Uri输入流兼容content:///file:// 等多种Uri格式
is = mContext.getContentResolver().openInputStream(uri); is = mContext.getContentResolver().openInputStream(uri);
@@ -299,6 +307,14 @@ public class BackgroundSourceUtils {
fos.flush(); fos.flush();
} }
} }
// 解析成功日志(打印文件信息,便于问题排查)
LogUtils.d(TAG, "【选图解析】Uri解析成功");
LogUtils.d(TAG, "→ 原Uri" + uri.toString());
} else {
return createAndUpdatePreviewEnvironmentForCropping(true, previewBackgroundBean);
}
} }
// 加载图片数据模型数据 // 加载图片数据模型数据
@@ -312,13 +328,12 @@ public class BackgroundSourceUtils {
// 保存数据模型数据更改 // 保存数据模型数据更改
saveSettings(); saveSettings();
// 6. 解析成功日志(打印文件信息,便于问题排查) // 解析成功日志(打印文件信息,便于问题排查)
LogUtils.d(TAG, "【选图解析】Uri解析成功");
LogUtils.d(TAG, "→ 原Uri" + uri.toString());
LogUtils.d(TAG, "→ 图片剪裁数据源:" + mCropSourceFile.getAbsolutePath()); LogUtils.d(TAG, "→ 图片剪裁数据源:" + mCropSourceFile.getAbsolutePath());
LogUtils.d(TAG, "→ 图片剪裁数据源文件大小:" + mCropSourceFile.length() + " bytes"); LogUtils.d(TAG, "→ 图片剪裁数据源文件大小:" + mCropSourceFile.length() + " bytes");
LogUtils.d(TAG, "→ 剪裁结果数据文件:" + mCropResultFile.getAbsolutePath()); LogUtils.d(TAG, "→ 剪裁结果数据文件:" + mCropResultFile.getAbsolutePath());
LogUtils.d(TAG, "→ 剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes"); LogUtils.d(TAG, "→ 剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes");
return true; return true;
} catch (Exception e) { } catch (Exception e) {
@@ -380,30 +395,30 @@ public class BackgroundSourceUtils {
/** /**
* 获取正式背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验) * 获取正式背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
*/ */
public String getCurrentBackgroundFilePath() { // public String getCurrentBackgroundFilePath() {
String fileName = currentBackgroundBean.getBackgroundFileName(); // String fileName = currentBackgroundBean.getBackgroundFileName();
if (TextUtils.isEmpty(fileName)) { // if (TextUtils.isEmpty(fileName)) {
LogUtils.e(TAG, "【路径管理】正式背景文件名为空,返回空路径"); // LogUtils.e(TAG, "【路径管理】正式背景文件名为空,返回空路径");
return ""; // return "";
} // }
File file = new File(fBackgroundSourceDir, fileName); // File file = new File(fBackgroundSourceDir, fileName);
LogUtils.d(TAG, "【路径管理】正式背景路径:" + file.getAbsolutePath()); // LogUtils.d(TAG, "【路径管理】正式背景路径:" + file.getAbsolutePath());
return file.getAbsolutePath(); // return file.getAbsolutePath();
} // }
//
/** // /**
* 获取预览背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验) // * 获取预览背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
*/ // */
public String getPreviewBackgroundFilePath() { // public String getPreviewBackgroundFilePath() {
String fileName = previewBackgroundBean.getBackgroundFileName(); // String fileName = previewBackgroundBean.getBackgroundFileName();
if (TextUtils.isEmpty(fileName)) { // if (TextUtils.isEmpty(fileName)) {
LogUtils.e(TAG, "【路径管理】预览背景文件名为空,返回空路径"); // LogUtils.e(TAG, "【路径管理】预览背景文件名为空,返回空路径");
return ""; // return "";
} // }
File file = new File(fBackgroundSourceDir, fileName); // File file = new File(fBackgroundSourceDir, fileName);
LogUtils.d(TAG, "【路径管理】预览背景路径:" + file.getAbsolutePath()); // LogUtils.d(TAG, "【路径管理】预览背景路径:" + file.getAbsolutePath());
return file.getAbsolutePath(); // return file.getAbsolutePath();
} // }
/** /**
* 获取预览背景压缩图片路径(同步修复:移除 loadSettings()强化非空校验统一指向BackgroundCrops目录 * 获取预览背景压缩图片路径(同步修复:移除 loadSettings()强化非空校验统一指向BackgroundCrops目录

View File

@@ -1,113 +1,134 @@
package cc.winboll.studio.powerbell.utils; package cc.winboll.studio.powerbell.utils;
import android.content.Context; import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.provider.Settings; import androidx.core.app.ActivityCompat;
import android.widget.Toast;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import cc.winboll.studio.libappbase.LogUtils;
/** /**
* 存储权限工具类(适配 API 30+ * 权限申请工具类
* 核心支持1. 传统读写权限 2. MANAGE_EXTERNAL_STORAGE 全文件权限 * 适配 Android 13+ 媒体权限 & 低版本存储权限
* 兼容 SDK 版本低于 33 的编译环境
*/ */
public class PermissionUtils { public class PermissionUtils {
private static final String TAG = "PermissionUtils";
// 存储权限请求码
public static final int REQUEST_STORAGE = 1000;
// 媒体图片权限请求码Android 13+
public static final int REQUEST_READ_MEDIA_IMAGES = 1001;
// 手动定义 Android 13+ 媒体图片权限常量(解决 SDK 低于 33 无法识别问题)
private static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
// Android 13 对应的 SDK 版本号(替代 Build.VERSION_CODES.TIRAMISU
private static final int SDK_VERSION_TIRAMISU = 33;
// 单例模式
private static volatile PermissionUtils sInstance; private static volatile PermissionUtils sInstance;
private Context mContext;
// 权限请求码 private PermissionUtils() {}
public static final int REQUEST_CODE_STORAGE = 1001;
public static final int REQUEST_CODE_MANAGE_STORAGE = 1002;
private PermissionUtils(Context context) {
this.mContext = context.getApplicationContext();
}
public static PermissionUtils getInstance() { public static PermissionUtils getInstance() {
if (sInstance == null) { if (sInstance == null) {
throw new IllegalStateException("请先调用 init(Context) 初始化"); synchronized (PermissionUtils.class) {
if (sInstance == null) {
sInstance = new PermissionUtils();
}
}
} }
return sInstance; return sInstance;
} }
public static void init(Context context) {
if (sInstance == null) {
synchronized (PermissionUtils.class) {
if (sInstance == null) {
sInstance = new PermissionUtils(context);
}
}
}
}
/** /**
* 检查是否拥有完整的文件管理权限 * 检查并请求 存储权限Android 12及以下
*/ */
public boolean hasFullStoragePermission() { public boolean checkAndRequestStoragePermission(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// API 30+ 需判断 MANAGE_EXTERNAL_STORAGE 权限 // Android 11+ 无需申请 READ_EXTERNAL_STORAGE直接返回true
return Environment.isExternalStorageManager(); return true;
} else { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 低版本判断传统读写权限 String[] permissions = {
int read = ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.READ_EXTERNAL_STORAGE);
int write = ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
return read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED;
}
}
/**
* 申请文件管理权限(自动适配系统版本)
* @param activity 发起申请的 Activity
*/
public void requestFullStoragePermission(android.app.Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// API 30+ 跳转系统设置页开启权限
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", mContext.getPackageName(), null);
intent.setData(uri);
activity.startActivityForResult(intent, REQUEST_CODE_MANAGE_STORAGE);
} else {
// 低版本申请传统读写权限
androidx.core.app.ActivityCompat.requestPermissions(
activity,
new String[]{
android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE android.Manifest.permission.WRITE_EXTERNAL_STORAGE
}, };
REQUEST_CODE_STORAGE if (ContextCompat.checkSelfPermission(activity, permissions[0]) != PackageManager.PERMISSION_GRANTED
); || ContextCompat.checkSelfPermission(activity, permissions[1]) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, permissions, REQUEST_STORAGE);
return false;
}
}
return true;
}
/**
* 检查并请求 媒体图片权限Android 13+
* 兼容 SDK 编译版本低于 33 的情况
*/
public boolean checkAndRequestMediaImagesPermission(Activity activity, int requestCode) {
// 用数值 33 替代 Build.VERSION_CODES.TIRAMISU
if (Build.VERSION.SDK_INT >= SDK_VERSION_TIRAMISU) {
// 用手动定义的权限常量替代 android.Manifest.permission.READ_MEDIA_IMAGES
if (ContextCompat.checkSelfPermission(activity, READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[]{READ_MEDIA_IMAGES}, requestCode);
return false;
}
}
// 低版本已通过存储权限覆盖直接返回true
return true;
}
/**
* 权限请求结果处理
*/
public void handleStoragePermissionResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_STORAGE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LogUtils.d(TAG, "存储权限申请成功");
} else {
LogUtils.e(TAG, "存储权限申请失败");
}
break;
case REQUEST_READ_MEDIA_IMAGES:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LogUtils.d(TAG, "媒体图片权限申请成功");
} else {
LogUtils.e(TAG, "媒体图片权限申请失败");
}
break;
default:
LogUtils.d(TAG, "未知权限请求码:" + requestCode);
} }
} }
/** /**
* 处理低版本权限申请结果(在 Activity 的 onRequestPermissionsResult 中调用 * 检查是否有管理所有文件权限Android 11+
* @param activity 上下文
* @param requestCode 请求码
* @param permissions 权限数组
* @param grantResults 授权结果
* @return 权限是否申请成功
*/ */
public boolean handleStoragePermissionResult(android.app.Activity activity, int requestCode, String[] permissions, int[] grantResults) { public boolean checkManageExternalStoragePermission(Activity activity) {
// 仅处理传统存储权限的请求结果 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (requestCode != REQUEST_CODE_STORAGE) { return android.os.Environment.isExternalStorageManager();
return false; }
return true;
} }
// 校验授权结果:读写权限均需授予
boolean isGranted = grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (isGranted) { /**
// 权限授予成功,提示用户 * 请求管理所有文件权限Android 11+
Toast.makeText(activity, "存储权限申请成功", Toast.LENGTH_SHORT).show(); */
} else { public void requestManageExternalStoragePermission(Activity activity, int requestCode) {
// 权限被拒绝,引导用户手动开启 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Toast.makeText(activity, "存储权限被拒绝,部分功能无法使用", Toast.LENGTH_SHORT).show(); try {
android.content.Intent intent = new android.content.Intent(
android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
);
intent.setData(android.net.Uri.parse("package:" + activity.getPackageName()));
activity.startActivityForResult(intent, requestCode);
} catch (Exception e) {
LogUtils.e(TAG, "请求管理文件权限异常:" + e.getMessage());
}
} }
return isGranted;
} }
} }