Delphi FMX适配Android 13(API33)权限授予失败问题求助
你的问题核心是没跟上Android 13的权限机制变化,加上代码里的权限请求和结果判断逻辑存在错误,导致权限始终无法正确授予。以下是具体的解决步骤:
1. 精简Manifest权限配置
Android 13对媒体权限做了拆分,不需要再声明READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE;而且MANAGE_EXTERNAL_STORAGE是特殊权限,不能通过常规弹窗请求,除非你真的需要访问设备上所有文件(比如文件管理器类应用),否则完全不需要添加这个权限。
修改后的Manifest只保留必要权限:
<?xml version="1.0" encoding="utf-8"?> <!-- BEGIN_INCLUDE(manifest) --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.embarcadero.CameraRoll" android:versionCode="1" android:versionName="1.0.0" android:installLocation="auto"> <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="33" /> <!-- Android 13媒体权限:访问相册只需要这个 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- 若需要访问视频/音频,再保留下面两个,否则直接删除 --> <!-- <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> --> <!-- <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> --> <!-- 其他实际需要的权限,比如相机,用不到就删掉 --> <!-- <uses-permission android:name="android.permission.CAMERA" /> --> <uses-feature android:glEsVersion="0x00020000" android:required="true"/> <queries> </queries> <application android:persistent="False" android:restoreAnyVersion="False" android:label="CameraRoll" android:debuggable="true" android:largeHeap="False" android:icon="@drawable/ic_launcher" android:theme="@style/AppTheme" android:hardwareAccelerated="true" android:resizeableActivity="false"> <!-- 保留FileProvider配置,不需要文件分享可删除 --> <provider android:name="androidx.core.content.FileProvider" android:authorities="com.embarcadero.CameraRoll.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider> <activity android:name="com.embarcadero.firemonkey.FMXNativeActivity" android:exported="true" android:label="CameraRoll" android:configChanges="orientation|keyboard|keyboardHidden|screenSize" android:launchMode="singleTask"> <meta-data android:name="android.app.lib_name" android:value="CameraRoll" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name="com.embarcadero.rtl.notifications.NotificationAlarm" /> </application> </manifest> <!-- END_INCLUDE(manifest) -->
2. 修正权限请求代码
代码里同时请求了新旧权限,Android 13会忽略旧的READ_EXTERNAL_STORAGE请求;而且你强制要求5个权限全部授予才执行操作,其中MANAGE_EXTERNAL_STORAGE根本无法通过弹窗授予,导致逻辑永远不成立。
修改后的代码:
unit uMain; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Permissions, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, System.Sensors, FMX.Media, FMX.Objects, FMX.MediaLibrary.Actions, System.Actions, FMX.ActnList, FMX.Graphics, FMX.StdActns, FMX.Controls.Presentation; type TCameraRollForm = class(TForm) btnPhotoLibrary: TButton; imgPhotoLibraryImage: TImage; alGetCameraRoll: TActionList; TakePhotoFromLibraryAction1: TTakePhotoFromLibraryAction; ToolBar1: TToolBar; Label</think_never_used_51bce0c785ca2f68081bfa7d91973934>用户</think_never_used_51bce0c785ca2f68081bfa7d91973934>用户现在需要继续完成之前的Delphi代码,刚才的代码截断了,现在补全。然后整理成完整的回答,保持风格一致。 首先补全截断的代码部分: Label1: TLabel; procedure TakePhotoFromLibraryAction1DidFinishTaking(Image: TBitmap); procedure btnPhotoLibraryClick(Sender: TObject); private FRawBitmap: TBitmap; // 只保留需要的权限常量 {$IFDEF ANDROID} FPermissionReadMediaImages: string; {$ENDIF} procedure DisplayRationale(Sender: TObject; const APermissions: TClassicStringDynArray; const APostRationaleProc: TProc); procedure LoadPicturePermissionRequestResult(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; var CameraRollForm: TCameraRollForm; implementation uses {$IFDEF ANDROID} Androidapi.Helpers, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os, {$ENDIF} FMX.DialogService; {$R *.fmx} {$R *.LgXhdpiPh.fmx ANDROID} constructor TCameraRollForm.Create(AOwner: TComponent); begin inherited Create(AOwner); FRawBitmap := TBitmap.Create(0, 0); {$IFDEF ANDROID} // 使用Android 13的媒体权限 FPermissionReadMediaImages := JStringToString(TJManifest_permission.JavaClass.READ_MEDIA_IMAGES); {$ENDIF} end; destructor TCameraRollForm.Destroy; begin FreeAndNil(FRawBitmap); inherited Destroy; end; procedure TCameraRollForm.btnPhotoLibraryClick(Sender: TObject); begin // 只请求实际需要的权限:读取图像 PermissionsService.RequestPermissions([FPermissionReadMediaImages], LoadPicturePermissionRequestResult, DisplayRationale); end; procedure TCameraRollForm.DisplayRationale(Sender: TObject; const APermissions: TClassicStringDynArray; const APostRationaleProc: TProc); var RationaleMsg: string; begin RationaleMsg := '应用需要访问您的相册以读取图片'; TDialogService.ShowMessage(RationaleMsg, procedure(const AResult: TModalResult) begin APostRationaleProc; end); end; procedure TCameraRollForm.LoadPicturePermissionRequestResult(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray); begin // 只检查实际请求的权限是否授予 if (Length(AGrantResults) > 0) and (AGrantResults[0] = TPermissionStatus.Granted) then TakePhotoFromLibraryAction1.Execute else TDialogService.ShowMessage('无法访问相册,因为必要权限未授予'); end; procedure TCameraRollForm.TakePhotoFromLibraryAction1DidFinishTaking(Image: TBitmap); begin imgPhotoLibraryImage.Bitmap.Assign(Image); end; end.
然后继续完成回答的其他部分,保持之前的风格:
3. 关于MANAGE_EXTERNAL_STORAGE的特殊说明
如果你的应用确实需要访问设备上所有文件(比如文件管理器),这个权限不能通过PermissionsService.RequestPermissions请求,必须引导用户到应用设置页面手动开启:
{$IFDEF ANDROID} uses Androidapi.JNI.App, Androidapi.JNI.Content; procedure OpenStoragePermissionSettings; var Intent: JIntent; begin Intent := TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); Intent.setData(TJUri.JavaClass.parse('package:' + TJContextWrapper.JavaClass.getPackageName(TAndroidHelper.Context))); TAndroidHelper.Activity.startActivity(Intent); end; {$ENDIF}
关键注意点
- Android 13中,
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE已经被废弃,针对媒体文件必须使用READ_MEDIA_IMAGES/READ_MEDIA_VIDEO/READ_MEDIA_AUDIO。 - 不要混合请求新旧权限,否则会导致权限判断逻辑混乱。
- 特殊权限(如
MANAGE_EXTERNAL_STORAGE)需要单独处理,不能通过常规弹窗请求。
内容的提问来源于stack exchange,提问作者Test Account
现在把这些整合起来,确保代码完整,回答流畅,符合要求。</think_never_used_51bce0c785ca2f68081bfa7d91973934>```delphi
unit uMain;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Permissions,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, System.Sensors, FMX.Media,
FMX.Objects, FMX.MediaLibrary.Actions, System.Actions, FMX.ActnList, FMX.Graphics,
FMX.StdActns, FMX.Controls.Presentation;
type
TCameraRollForm = class(TForm)
btnPhotoLibrary: TButton;
imgPhotoLibraryImage: TImage;
alGetCameraRoll: TActionList;
TakePhotoFromLibraryAction1: TTakePhotoFromLibraryAction;
ToolBar1: TToolBar;
Label1: TLabel;
procedure TakePhotoFromLibraryAction1DidFinishTaking(Image: TBitmap); procedure btnPhotoLibraryClick(Sender: TObject);
private
FRawBitmap: TBitmap;
// 只保留需要的权限常量
{$IFDEF ANDROID}
FPermissionReadMediaImages: string;
{$ENDIF}
procedure DisplayRationale(Sender: TObject; const APermissions: TClassicStringDynArray; const APostRationaleProc: TProc); procedure LoadPicturePermissionRequestResult(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
CameraRollForm: TCameraRollForm;
implementation
uses
{$IFDEF ANDROID}
Androidapi.Helpers,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.Os,
{$ENDIF}
FMX.DialogService;
{$R *.fmx}
{$R *.LgXhdpiPh.fmx ANDROID}
constructor TCameraRollForm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FRawBitmap := TBitmap.Create(0, 0);
{$IFDEF ANDROID}
// 使用Android 13的媒体权限
FPermissionReadMediaImages := JStringToString(TJManifest_permission.JavaClass.READ_MEDIA_IMAGES);
{$ENDIF}
end;
destructor TCameraRollForm.Destroy;
begin
FreeAndNil(FRawBitmap);
inherited Destroy;
end;
procedure TCameraRollForm.btnPhotoLibraryClick(Sender: TObject);
begin
// 只请求实际需要的权限:读取图像
PermissionsService.RequestPermissions([FPermissionReadMediaImages], LoadPicturePermissionRequestResult, DisplayRationale);
end;
procedure TCameraRollForm.DisplayRationale(Sender: TObject; const APermissions: TClassicStringDynArray; const APostRationaleProc: TProc);
var
RationaleMsg: string;
begin
RationaleMsg := '应用需要访问您的相册以读取图片';
TDialogService.ShowMessage(RationaleMsg,
procedure(const AResult: TModalResult)
begin
APostRationaleProc;
end);
end;
procedure TCameraRollForm.LoadPicturePermissionRequestResult(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray);
begin
// 只检查实际请求的权限是否授予
if (Length(AGrantResults) > 0) and (AGrantResults[0] = TPermissionStatus.Granted) then
TakePhotoFromLibraryAction1.Execute
else
TDialogService.ShowMessage('无法访问相册,因为必要权限未授予');
end;
procedure TCameraRollForm.TakePhotoFromLibraryAction1DidFinishTaking(Image: TBitmap);
begin
imgPhotoLibraryImage.Bitmap.Assign(Image);
end;
end.
## 3. 关于`MANAGE_EXTERNAL_STORAGE`的特殊说明 如果你的应用确实需要访问设备上所有文件(比如文件管理器类应用),这个权限不能通过`PermissionsService.RequestPermissions`请求,必须引导用户到应用设置页面手动开启: ```delphi {$IFDEF ANDROID} uses Androidapi.JNI.App, Androidapi.JNI.Content; procedure OpenStoragePermissionSettings; var Intent: JIntent; begin Intent := TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); Intent.setData(TJUri.JavaClass.parse('package:' + TJContextWrapper.JavaClass.getPackageName(TAndroidHelper.Context))); TAndroidHelper.Activity.startActivity(Intent); end; {$ENDIF}
关键注意点
- Android 13中,
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE已被废弃,访问媒体文件必须使用READ_MEDIA_IMAGES/READ_MEDIA_VIDEO/READ_MEDIA_AUDIO这类细分权限。 - 不要混合请求新旧权限,否则会导致权限判断逻辑混乱,Android 13会直接忽略旧的存储权限请求。
MANAGE_EXTERNAL_STORAGE属于特殊权限,无法通过常规弹窗请求,必须引导用户到系统设置手动开启,且仅适合真正需要全文件访问的应用。
内容的提问来源于stack exchange,提问作者Test Account

