相册权限申请模块改进中。。。

This commit is contained in:
2025-12-11 06:53:22 +08:00
parent 6ed9bc0d8e
commit ecafd2026f
7 changed files with 311 additions and 380 deletions

View File

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

View File

@@ -14,6 +14,7 @@ import cc.winboll.studio.powerbell.utils.AppCacheUtils;
import cc.winboll.studio.powerbell.utils.AppConfigUtils; import cc.winboll.studio.powerbell.utils.AppConfigUtils;
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.BitmapCacheUtils;
import cc.winboll.studio.powerbell.utils.PermissionUtils;
import java.io.File; import java.io.File;
public class App extends GlobalApplication { public class App extends GlobalApplication {
@@ -43,6 +44,7 @@ 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

@@ -2,7 +2,6 @@ package cc.winboll.studio.powerbell;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@@ -25,7 +24,6 @@ import android.widget.SeekBar;
import android.widget.Switch; import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.activitys.AboutActivity; import cc.winboll.studio.libaes.activitys.AboutActivity;
import cc.winboll.studio.libaes.models.APPInfo; import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.utils.AESThemeUtil; import cc.winboll.studio.libaes.utils.AESThemeUtil;
@@ -125,7 +123,9 @@ public class MainActivity extends WinBoLLActivity {
loadNonCriticalViewDelayed(); loadNonCriticalViewDelayed();
// 权限申请 // 权限申请
PermissionUtils.getInstance().checkAndRequestStoragePermission(this); if (!PermissionUtils.getInstance().hasFullStoragePermission()) {
PermissionUtils.getInstance().requestFullStoragePermission(this);
}
} }
// 移除 onSaveInstanceState 方法 // 移除 onSaveInstanceState 方法

View File

@@ -15,11 +15,10 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.provider.MediaStore; import android.provider.MediaStore;
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 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;
@@ -28,17 +27,16 @@ 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.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.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.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.powerbell.utils.UriUtil;
import cc.winboll.studio.powerbell.utils.FileUtils;
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener { public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
@@ -111,7 +109,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
private void initToolbar() { private void initToolbar() {
mToolbar = findViewById(R.id.toolbar); mToolbar = (Toolbar) 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);
@@ -175,44 +173,39 @@ 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, "【按钮点击】选择图片");
if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) { // 适配 API 30+ 权限逻辑
LogUtils.d(TAG, "【选图权限】已获取"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Intent[] intents = new Intent[3]; // API 30+ 优先判断全文件管理权限
Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT); if (!Environment.isExternalStorageManager()) {
getContentIntent.setType("image/*"); mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
getContentIntent.addCategory(Intent.CATEGORY_OPENABLE); return;
getContentIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); }
intents[0] = getContentIntent; } else {
// 低版本判断传统读写权限
if (!mPermissionUtils.hasFullStoragePermission()) {
mPermissionUtils.requestFullStoragePermission(BackgroundSettingsActivity.this);
return;
}
}
// 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) {
Intent openDocIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); // 持久化 Uri 权限,避免后续操作失效
openDocIntent.setType("image/*"); pickIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
openDocIntent.addCategory(Intent.CATEGORY_OPENABLE);
openDocIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intents[2] = openDocIntent;
} }
Intent validIntent = null; // 构建选择器,统一用户交互
for (int i = 0; i < intents.length; i++) { Intent chooser = Intent.createChooser(pickIntent, "选择图片");
Intent intent = intents[i]; if (chooser.resolveActivity(getPackageManager()) != null) {
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 {
@@ -241,9 +234,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
}); });
} }
} else {
LogUtils.d(TAG, "【选图权限】已申请");
}
} }
}; };
@@ -296,7 +286,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return; return;
} }
if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) { if (mPermissionUtils.hasFullStoragePermission()) {
LogUtils.d(TAG, "【拍照权限】已获取"); LogUtils.d(TAG, "【拍照权限】已获取");
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
try { try {
@@ -310,6 +300,7 @@ 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, "【拍照权限】已申请");
} }
} }
@@ -361,7 +352,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_MANAGE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (requestCode == PermissionUtils.REQUEST_CODE_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
handleStoragePermissionCallback(); handleStoragePermissionCallback();
return; return;
} }
@@ -402,12 +393,8 @@ 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 || data == null) { if (resultCode != RESULT_OK || !mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
handleOperationCancelOrFail(); handleOperationCancelOrFail();
return;
}
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
ToastUtils.show("拍照文件无效"); ToastUtils.show("拍照文件无效");
return; return;
} }
@@ -512,9 +499,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return FileUtils.copyFile(srcFile, dstFile); return FileUtils.copyFile(srcFile, dstFile);
} }
/**
* 核心修改:裁剪成功后调用双重刷新,确保最新图片加载
*/
private void handleCropImageResult(int requestCode, int resultCode, Intent data) { private void handleCropImageResult(int requestCode, int resultCode, Intent data) {
LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果"); LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果");
File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath()); File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
@@ -547,7 +531,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
} }
// 关键修改:延迟300ms调用双重刷新确保文件写入完成 // 延迟300ms调用双重刷新确保文件写入完成
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -699,9 +683,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return cropBitmap; return cropBitmap;
} }
/**
* 核心修改:添加缓存清理 + 控件 Bitmap 清空逻辑
*/
private void doubleRefreshPreview() { private void doubleRefreshPreview() {
LogUtils.d(TAG, "【双重刷新】开始"); LogUtils.d(TAG, "【双重刷新】开始");
// 1. 清空缓存工具类的旧数据 // 1. 清空缓存工具类的旧数据
@@ -717,22 +698,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
} }
// 2. 清空 BackgroundView 自身的 Bitmap 引用 // 2. 重新加载最新数据
// if (mBackgroundView != null) {
// // 清空 ImageView 持有的 Bitmap
// if (mBackgroundView instanceof BackgroundView) {
// ((BackgroundView) mBackgroundView).setImageBitmap(null);
// }
// // 清空背景 Drawable 引用
// Drawable drawable = mBackgroundView.getBackground();
// if (drawable != null) {
// drawable.setCallback(null);
// mBackgroundView.setBackground(null);
// }
// LogUtils.d(TAG, "【双重刷新】已清空控件 Bitmap 引用");
// }
// 3. 重新加载最新数据
if (mBackgroundView != null && !isFinishing()) { if (mBackgroundView != null && !isFinishing()) {
mBgSourceUtils.loadSettings(); mBgSourceUtils.loadSettings();
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
@@ -742,7 +708,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
return; return;
} }
// 4. 延长延迟到200ms确保文件完全加载 // 3. 延长延迟到200ms确保文件完全加载
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -755,22 +721,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}, 200); }, 200);
} }
private void handleOperationCancelOrFail() { private void handleOperationCancelOrFail() {
mBgSourceUtils.setCurrentSourceToPreview(); mBgSourceUtils.setCurrentSourceToPreview();
LogUtils.d(TAG, "【操作回调】取消或失败"); LogUtils.d(TAG, "【操作回调】取消或失败");
ToastUtils.show("操作回调】取消或失败"); ToastUtils.show("操作取消或失败");
doubleRefreshPreview(); doubleRefreshPreview();
} }
/**
* 获取FileProvider Uri复用方法避免重复代码
*/
public Uri getFileProviderUri(File file) { public Uri getFileProviderUri(File file) {
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String FILE_PROVIDER_AUTHORITY = getPackageName() + ".fileprovider"; String FILE_PROVIDER_AUTHORITY = getPackageName() + ".fileprovider";
return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, file); return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, file);
} else { } else {
return Uri.fromFile(file); return Uri.fromFile(file);

View File

@@ -52,8 +52,10 @@ 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().checkAndRequestStoragePermission(this)) { if (PermissionUtils.getInstance().hasFullStoragePermission()) {
ToastUtils.show("【权限检查】存储权限已全部获取"); ToastUtils.show("【权限检查】存储权限已全部获取");
} else {
PermissionUtils.getInstance().requestFullStoragePermission(this);
} }
} }
} }

View File

@@ -1,214 +1,113 @@
package cc.winboll.studio.powerbell.utils; package cc.winboll.studio.powerbell.utils;
import android.app.Activity; import android.content.Context;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import java.util.ArrayList;
import java.util.List;
/** /**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> * 存储权限工具类(适配 API 30+
* @Date 2025/12/01 16:05 * 核心支持1. 传统读写权限 2. MANAGE_EXTERNAL_STORAGE 全文件权限
* @Describe 权限申请工具类(单例)
* 核心特性:
* 1. 适配全Android版本6.0+ 动态权限 / 13+ 兼容)
* 2. 支持多包名场景(无硬编码包名)
* 3. 统一权限校验、申请、回调处理
* 4. 自带用户引导(拒绝权限+不再询问场景)
*/ */
public class PermissionUtils { public class PermissionUtils {
public static final String TAG = "PermissionUtils";
// 存储权限请求码与Activity保持一致避免冲突
public static final int STORAGE_PERMISSION_REQUEST2 = 100;
public static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 101;
// 单例实例(双重校验锁,线程安全)
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) {
synchronized (PermissionUtils.class) { throw new IllegalStateException("请先调用 init(Context) 初始化");
if (sInstance == null) {
sInstance = new PermissionUtils();
}
}
} }
return sInstance; return sInstance;
} }
// ======================================== 存储权限核心方法 ======================================== public static void init(Context context) {
/** if (sInstance == null) {
* 检查并申请存储权限统一入口适配全Android版本 synchronized (PermissionUtils.class) {
* @param activity 上下文(用于权限申请和弹窗) if (sInstance == null) {
* @return true权限已全部获取false需要申请权限 sInstance = new PermissionUtils(context);
*/
public boolean checkAndRequestStoragePermission(Activity activity) {
if (activity == null || activity.isFinishing()) {
LogUtils.e(TAG, "【权限检查】Activity为空或已销毁权限检查失败");
return false;
}
LogUtils.d(TAG, "【权限检查】开始检查存储权限Android版本" + Build.VERSION.SDK_INT);
// 统一使用 WRITE_EXTERNAL_STORAGE + READ_EXTERNAL_STORAGE适配所有版本避免READ_MEDIA_IMAGES找不到符号
String[] requiredPermissions = {
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
android.Manifest.permission.READ_EXTERNAL_STORAGE
};
// 筛选未授予的权限
List<String> needPermissions = new ArrayList<>();
for (String permission : requiredPermissions) {
if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
needPermissions.add(permission);
} }
} }
// 无权限需要申请:触发动态申请
if (!needPermissions.isEmpty()) {
String[] permissionsArr = needPermissions.toArray(new String[0]);
ActivityCompat.requestPermissions(activity, permissionsArr, STORAGE_PERMISSION_REQUEST2);
LogUtils.d(TAG, "【权限申请】已触发存储权限申请:" + TextUtils.join(",", permissionsArr));
return false;
} }
// 所有权限已授予
LogUtils.d(TAG, "【权限检查】存储权限已全部获取");
return true;
} }
/** /**
* 处理存储权限申请回调统一逻辑无需在Activity中重复编写 * 检查是否拥有完整的文件管理权限
* @param activity 上下文
* @param requestCode 请求码匹配STORAGE_PERMISSION_REQUEST
* @param permissions 申请的权限数组
* @param grantResults 权限授予结果数组
* @return true回调已处理false非当前工具类的权限回调
*/ */
public boolean handleStoragePermissionResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) { public boolean hasFullStoragePermission() {
// 过滤非存储权限回调 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (requestCode != STORAGE_PERMISSION_REQUEST2) { // API 30+ 需判断 MANAGE_EXTERNAL_STORAGE 权限
return false; return Environment.isExternalStorageManager();
}
LogUtils.d(TAG, "【权限回调】处理存储权限回调requestCode" + requestCode);
if (activity == null || activity.isFinishing()) {
LogUtils.e(TAG, "【权限回调】Activity为空或已销毁回调处理终止");
return true;
}
// 校验所有权限是否授予
boolean allGranted = true;
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
// 全部授予:提示用户重新操作
ToastUtils.show(activity.getString(R.string.permission_grant_success));
LogUtils.d(TAG, "【权限回调】所有存储权限已授予");
} else { } else {
// 部分/全部拒绝:判断是否勾选“不再询问” // 低版本判断传统读写权限
boolean shouldShowRationale = false; int read = ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.READ_EXTERNAL_STORAGE);
for (String permission : permissions) { int write = ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { return read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED;
shouldShowRationale = true;
break;
} }
} }
if (shouldShowRationale) {
// 未勾选“不再询问”:弹窗引导重新申请
showPermissionRationaleDialog(activity);
} else {
// 已勾选“不再询问”:引导用户去设置页开启
showPermissionSettingDialog(activity);
}
LogUtils.d(TAG, "【权限回调】部分/全部存储权限被拒绝,是否需要引导:" + shouldShowRationale);
}
return true;
}
// ======================================== 辅助方法(私有,封装细节) ========================================
/**
* 弹窗:未勾选“不再询问”时,提示用户授予权限
*/
private void showPermissionRationaleDialog(final Activity activity) {
new AlertDialog.Builder(activity)
.setTitle(activity.getString(R.string.permission_title))
.setMessage(activity.getString(R.string.permission_storage_rationale))
.setPositiveButton(activity.getString(R.string.confirm), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 重新申请权限
checkAndRequestStoragePermission(activity);
}
})
.setNegativeButton(activity.getString(R.string.cancel), null)
.show();
}
/** /**
* 弹窗:已勾选“不再询问”时,引导用户去应用设置页开启权限 * 申请文件管理权限(自动适配系统版本)
* @param activity 发起申请的 Activity
*/ */
private void showPermissionSettingDialog(final Activity activity) { public void requestFullStoragePermission(android.app.Activity activity) {
new AlertDialog.Builder(activity) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
.setTitle(activity.getString(R.string.permission_denied_title)) // API 30+ 跳转系统设置页开启权限
.setMessage(activity.getString(R.string.permission_storage_setting_guide)) Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
.setPositiveButton(activity.getString(R.string.go_to_setting), new DialogInterface.OnClickListener() { Uri uri = Uri.fromParts("package", mContext.getPackageName(), null);
@Override
public void onClick(DialogInterface dialog, int which) {
// 跳转应用设置页(适配多包名,动态获取当前包名)
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri); intent.setData(uri);
activity.startActivity(intent); 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.WRITE_EXTERNAL_STORAGE
},
REQUEST_CODE_STORAGE
);
} }
})
.setNegativeButton(activity.getString(R.string.cancel), null)
.show();
} }
// ======================================== 扩展:其他权限方法(可选) ========================================
/** /**
* 检查单个权限是否已授予(通用方法,可复用) * 处理低版本权限申请结果(在 Activity 的 onRequestPermissionsResult 中调用)
* @param activity 上下文
* @param requestCode 请求码
* @param permissions 权限数组
* @param grantResults 授权结果
* @return 权限是否申请成功
*/ */
public boolean isPermissionGranted(Activity activity, String permission) { public boolean handleStoragePermissionResult(android.app.Activity activity, int requestCode, String[] permissions, int[] grantResults) {
if (activity == null || TextUtils.isEmpty(permission)) { // 仅处理传统存储权限的请求结果
if (requestCode != REQUEST_CODE_STORAGE) {
return false; return false;
} }
return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED; // 校验授权结果:读写权限均需授予
} boolean isGranted = grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED;
/** if (isGranted) {
* 申请单个权限(通用方法,可复用) // 权限授予成功,提示用户
*/ Toast.makeText(activity, "存储权限申请成功", Toast.LENGTH_SHORT).show();
public void requestSinglePermission(Activity activity, String permission, int requestCode) { } else {
if (activity == null || TextUtils.isEmpty(permission)) { // 权限被拒绝,引导用户手动开启
return; Toast.makeText(activity, "存储权限被拒绝,部分功能无法使用", Toast.LENGTH_SHORT).show();
}
if (!isPermissionGranted(activity, permission)) {
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
} }
return isGranted;
} }
} }

View File

@@ -10,8 +10,10 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore; import android.provider.MediaStore;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -105,16 +107,81 @@ public class UriUtil {
} }
public static Uri getUriForFile(Context context, String filePath) { public static Uri getUriForFile(Context context, String filePath) {
// 1. 打印传入的文件路径
LogUtils.d(TAG, "getUriForFile -> 传入路径:" + filePath);
if (filePath == null || filePath.isEmpty()) {
LogUtils.e(TAG, "getUriForFile -> 传入路径为空");
return null;
}
File file = new File(filePath); File file = new File(filePath);
// 2. 打印File对象的绝对路径和存在性
LogUtils.d(TAG, "getUriForFile -> 文件绝对路径:" + file.getAbsolutePath());
LogUtils.d(TAG, "getUriForFile -> 文件是否存在:" + file.exists());
LogUtils.d(TAG, "getUriForFile -> 是否为目录:" + file.isDirectory());
// 3. 合法性校验
if (!file.exists() || file.isDirectory()) {
LogUtils.e(TAG, "getUriForFile -> 非法路径:文件不存在或为目录");
return null;
}
// 4. 校验路径是否在配置的合法目录内
String appFilesDir = context.getExternalFilesDir(null) != null ? context.getExternalFilesDir(null).getAbsolutePath() : "null";
String publicPicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + "/PowerBell/";
String internalFilesDir = context.getFilesDir().getAbsolutePath();
String cacheDir = context.getCacheDir().getAbsolutePath();
String absolutePath = file.getAbsolutePath();
boolean isInConfigDir = absolutePath.startsWith(appFilesDir)
|| absolutePath.startsWith(publicPicDir)
|| absolutePath.startsWith(internalFilesDir)
|| absolutePath.startsWith(cacheDir);
LogUtils.d(TAG, "getUriForFile -> 路径是否在配置目录内:" + isInConfigDir);
if (!isInConfigDir) {
LogUtils.w(TAG, "getUriForFile -> 路径不在FileProvider配置范围内可能导致异常");
// 非强制拦截,保留原有逻辑,仅警告
}
return getUriForFile(context, file); return getUriForFile(context, file);
} }
public static Uri getUriForFile(Context context, File file) { public static Uri getUriForFile(Context context, File file) {
//Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); if (context == null) {
if (Build.VERSION.SDK_INT >= 24) {//android 7.0以上 LogUtils.e(TAG, "getUriForFile -> Context为空");
return FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); return null;
}
if (file == null) {
LogUtils.e(TAG, "getUriForFile -> File对象为空");
return null;
}
// 1. 二次校验文件状态
LogUtils.d(TAG, "getUriForFile(File) -> 文件路径:" + file.getAbsolutePath());
if (!file.exists() || file.isDirectory()) {
LogUtils.e(TAG, "getUriForFile(File) -> 文件不存在或为目录");
return null;
}
// 2. 版本判断与Uri生成
if (Build.VERSION.SDK_INT >= 24) {
LogUtils.d(TAG, "getUriForFile -> Android 7.0+使用FileProvider生成Uri");
try {
String authority = context.getPackageName() + ".fileprovider";
LogUtils.d(TAG, "getUriForFile -> FileProvider authority" + authority);
Uri uri = FileProvider.getUriForFile(context, authority, file);
LogUtils.d(TAG, "getUriForFile -> 生成Content Uri成功" + uri.toString());
return uri;
} catch (IllegalArgumentException e) {
LogUtils.e(TAG, "getUriForFile -> FileProvider生成Uri失败路径未配置或权限不足", e);
return null;
}
} else {
LogUtils.d(TAG, "getUriForFile -> Android 7.0以下使用Uri.fromFile生成Uri");
Uri uri = Uri.fromFile(file);
LogUtils.d(TAG, "getUriForFile -> 生成File Uri成功" + uri.toString());
return uri;
} }
return Uri.fromFile(file);
} }
private static File createTemporalFileFrom(Context context, InputStream inputStream, String fileName) private static File createTemporalFileFrom(Context context, InputStream inputStream, String fileName)