Delphi 11中TDirectory.GetFiles()在Android共享存储返回空列表问题
Delphi 11 Android共享存储访问问题解决方案
问题现象
在Delphi 10.4中可正常获取Android设备共享存储的文件列表,但升级到Delphi 11后,即便已配置必要权限(代码或设备端设置),仍出现以下异常:
TDirectory.GetFiles()返回空列表TDirectory.GetDirectories()可正常工作TFile.Exists()能识别文件存在,但调用TFile.ReadAllText()时提示权限拒绝
问题根源
- Android权限机制变更:Delphi 11默认以Android 11(API 30)及以上版本为目标平台,Android 11开始限制了
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE权限的作用范围,这类权限不再授予对共享存储的全面访问权。 - 异步权限请求逻辑错误:原代码中在调用
PermissionsService.RequestPermissions后立刻判断权限状态,但权限请求是异步操作,此时回调尚未执行,fOK的初始值会导致逻辑判断失效。
解决方案
1. 修复异步权限请求逻辑
将文件操作逻辑移至权限回调方法中,确保在权限确认授予后再执行文件访问操作。
2. 适配Android 11+的存储权限
- 媒体文件访问:如果仅需访问图片、音频、视频等媒体文件,Android 13+可替换旧权限为
READ_MEDIA_IMAGES、READ_MEDIA_AUDIO、READ_MEDIA_VIDEO;Android 11-12可保留READ_EXTERNAL_STORAGE并配合媒体存储API使用。 - 全文件访问:如果需要访问所有类型的文件,需请求
MANAGE_EXTERNAL_STORAGE权限,同时在AndroidManifest.xml中添加以下声明:
注意:该权限需要引导用户到应用设置页面手动授权,无法通过代码直接触发弹窗授权。<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
3. 修改后的示例代码
uses System.Types, System.Classes, System.SysUtils, System.IOUtils, System.Permissions, {$IFDEF ANDROID} Androidapi.Helpers, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os, Androidapi.JNI.App, {$ENDIF} FMX.Types, FMX.Forms, FMX.Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private procedure PermissionsResult(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray); procedure HandleFileAccess; end; var Form1: TForm1; implementation {$R *.fmx} procedure TForm1.PermissionsResult(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray); var n: Integer; AllGranted: Boolean; begin AllGranted := True; if Length(AGrantResults) > 0 then for n := 0 to Length(AGrantResults) - 1 do if AGrantResults[n] <> TPermissionStatus.Granted then begin AllGranted := False; Break; end; if AllGranted then HandleFileAccess else ShowMessage('权限被拒绝'); end; procedure TForm1.HandleFileAccess; var DataDir: string; list: TStringList; LList: TStringDynArray; I: Integer; begin list := TStringList.Create; try DataDir := TPath.GetSharedDocumentsPath; ShowMessage(DataDir); // 适配Android 11+:如果是媒体文件,建议使用MediaStore API获取 LList := TDirectory.GetFiles(DataDir); if Length(LList) > 0 then for I := 0 to Length(LList) - 1 do list.Add(LList[I]); ShowMessage(IntToStr(list.Count) + ' 个文件:' + #13#10 + list.Text); finally list.Free; end; end; procedure TForm1.FormCreate(Sender: TObject); var Permissions: TArray<string>; {$IFDEF ANDROID} SDKVersion: Integer; {$ENDIF} begin {$IFDEF ANDROID} SDKVersion := TJBuild_VERSION.JavaClass.SDK_INT; if SDKVersion >= 33 then // Android 13+ 媒体文件权限 Permissions := [JStringToString(TJManifest_permission.JavaClass.READ_MEDIA_IMAGES), JStringToString(TJManifest_permission.JavaClass.READ_MEDIA_AUDIO), JStringToString(TJManifest_permission.JavaClass.READ_MEDIA_VIDEO)] else if SDKVersion >= 30 then // Android 11-12 使用旧存储权限 Permissions := [JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE)] else // Android 10及以下 Permissions := [JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE), JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)]; {$ELSE} Permissions := []; {$ENDIF} if Length(Permissions) > 0 then PermissionsService.RequestPermissions(Permissions, PermissionsResult, nil) else HandleFileAccess; end; end.
全文件访问权限引导代码
如果需要访问非媒体类文件(如文档、压缩包等),Android 11+下可通过以下代码引导用户到设置页面授权:
{$IFDEF ANDROID} if not TJEnvironment.JavaClass.isExternalStorageManager then begin var Intent := TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); var Uri := TJUri.JavaClass.fromString('package:' + TAndroidHelper.Context.getPackageName); Intent.setData(Uri); TAndroidHelper.Activity.startActivity(Intent); end; {$ENDIF}
内容的提问来源于stack exchange,提问作者user20492839
相关产品推荐
相关产品推荐

