You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi 12 FMX Android 文件复制权限问题求助

问题分析与解决方案

核心问题根源

  1. Android 13+ 存储权限机制变更:你的targetSdkVersion设为34,而READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE在Android 13(API33)及以上已被废弃,且Manifest中给这两个权限加了android:maxSdkVersion="32",导致API34设备上系统完全忽略这两个权限,PermissionsService请求时自然不会弹出授权框。
  2. MANAGE_EXTERNAL_STORAGE权限使用错误:该权限无法通过普通权限请求流程触发授权,必须引导用户到应用设置页面手动开启,且Google Play对该权限的审核极为严格,非必要场景不建议使用。
  3. 公共Documents目录访问方式错误:Android 13+ 限制直接通过文件路径访问公共Documents目录,即使拥有全存储权限也可能触发权限拒绝,需通过存储访问框架(SAF)或MediaStore进行操作。

分步解决方案

方案一:存储访问框架(SAF)推荐,符合Google Play政策

无需特殊权限,通过系统文件选择器让用户主动选择目标文件,从根源避免权限问题:

uses
  Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes, Androidapi.JNI.Provider,
  Androidapi.Helpers, FMX.DialogService, System.IOUtils;

// 触发系统文件选择器
procedure TfrmMain.CopyDBFromDocuments;
var
  Intent: JIntent;
begin
{$IFDEF ANDROID}
  Intent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_OPEN_DOCUMENT);
  Intent.addCategory(TJIntent.JavaClass.CATEGORY_OPENABLE);
  Intent.setType('application/sqlite'); // 指定数据库文件类型,更精准
  TAndroidHelper.Activity.startActivityForResult(Intent, 100); // 100为自定义请求码
{$ENDIF}
end;

// 处理文件选择结果
procedure TfrmMain.ActivityResult(RequestCode, ResultCode: Integer; Data: JIntent);
var
  SourceUri: JUri;
  SourceStream, DestStream: TStream;
  DestPath: string;
begin
  inherited;
  if (RequestCode = 100) and (ResultCode = TJActivity.JavaClass.RESULT_OK) and (Data <> nil) then
  begin
    SourceUri := Data.getData;
    DestPath := TPath.Combine(TPath.GetDocumentsPath, 'calorie_consumption.db');
    try
      dmCC.FDConnection.Connected := False;
      // 通过ContentResolver获取文件输入流
      SourceStream := TAndroidHelper.ContentResolver.openInputStream(SourceUri);
      try
        DestStream := TFileStream.Create(DestPath, fmCreate or fmShareDenyWrite);
        try
          DestStream.CopyFrom(SourceStream, SourceStream.Size);
          memSQLTest.Lines.Add('文件复制完成:' + DestPath);
        finally
          DestStream.Free;
        end;
      finally
        SourceStream.Free;
      end;
      dmCC.FDConnection.Connected := True;
    except
      on E: Exception do
        TDialogService.ShowMessage('复制失败:' + E.Message);
    end;
  end;
end;

方案二:MANAGE_EXTERNAL_STORAGE权限仅适合特殊场景

若必须直接访问公共Documents目录,按以下步骤修改:

  1. 修正Manifest权限配置:
    移除READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE的android:maxSdkVersion="32"属性,或直接删除这两个废弃权限,保留MANAGE_EXTERNAL_STORAGE。
  2. 修改权限请求逻辑:
uses
  Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes, Androidapi.Helpers,
  FMX.DialogService, System.IOUtils;

procedure TfrmMain.RequestFullStoragePermission;
var
  Intent: JIntent;
begin
{$IFDEF ANDROID}
  if TAndroidHelper.CheckSelfPermission(TJManifest_permission.JavaClass.MANAGE_EXTERNAL_STORAGE) <> TPermissionStatus.Granted then
  begin
    // 引导用户到应用设置页面开启权限
    Intent := TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
    Intent.setData(TJUri.JavaClass.parse('package:' + TAndroidHelper.Context.getPackageName));
    TAndroidHelper.Activity.startActivity(Intent);
  end else
  begin
    // 权限已授权,执行复制逻辑
    DoCopyDB(True);
  end;
{$ENDIF}
end;

procedure TfrmMain.DoCopyDB(direction2download: Boolean);
var
  f1, f2: String;
begin
  dmCC.FDConnection.Connected := False;
  f1 := TPath.Combine(TPath.GetDocumentsPath, 'calorie_consumption.db');
  f2 := TPath.Combine(TPath.GetSharedDocumentsPath, 'calorie_consumption.db');
  try
    if direction2download then
    begin
      TFile.Copy(f2, f1, True);
      memSQLTest.Lines.Add(f2 + ' -> ' + f1);
    end else
    begin
      TFile.Copy(f1, f2, True);
      memSQLTest.Lines.Add(f1 + ' -> ' + f2);
    end;
  except
    on E: Exception do
      TDialogService.ShowMessage('复制失败:' + E.Message);
  end;
  dmCC.FDConnection.Connected := True;
end;
  1. 注意事项:提交Google Play时必须详细说明应用需要全存储权限的原因,否则会被审核拒绝。

方案三:临时降级targetSdkVersion不推荐

仅用于测试场景,将targetSdkVersion改为32,此时原有的READ/WRITE_EXTERNAL_STORAGE权限可正常生效,PermissionsService也能弹出授权框,但不符合Google Play最新政策,无法用于正式上架。

关键注意点

  • 禁止硬编码文件路径,必须使用TPath.GetSharedDocumentsPath()、TPath.GetDocumentsPath()等官方API获取路径。
  • Android 10+ 推荐使用Scoped Storage,应用私有目录(TPath.GetDocumentsPath())无需任何权限即可自由读写。

内容的提问来源于stack exchange,提问作者sax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:31:03