Delphi 11 FMX Android获取文件真实路径及内容传输求助
Delphi 11 FMX Android 文件处理解决方案
问题背景
基于Delphi 11 FMX开发Android应用,已完成表单填写、拍照上传功能,现需实现任意文件(照片/视频/文档)选择、本地查看、HTTPS传输功能。当前可获取文件Uri,但返回的是\image:123456或\document:image\img1234.jpg这类伪路径,无法直接用于文件操作,需通过Cursor获取真实路径,同时需要文件打开、转Base64传输的代码示例。
1. 获取文件真实路径(Uri转真实路径)
Android的Content Uri无法直接映射到本地文件路径,需根据Uri类型(媒体/文档)通过ContentResolver查询,或复制文件到应用私有目录规避权限问题:
function GetRealPathFromURI(Uri: Jnet_Uri): String; var Cur: JCursor; ColumnIndex: Integer; Scheme, Authority: JString; begin Result := ''; Scheme := Uri.getScheme; // 处理content://协议的Uri if Scheme.equals(TJContentResolver.JavaClass.SCHEME_CONTENT) then begin Authority := Uri.getAuthority; // 媒体文件(图片/视频/音频) if Authority.equals(StringToJString('media')) then begin Cur := TAndroidHelper.Context.getContentResolver.query(Uri, TJavaObjectArray<JString>.Create(StringToJString('_data')), nil, nil, nil); try if (Cur <> nil) and Cur.moveToFirst then begin ColumnIndex := Cur.getColumnIndex(StringToJString('_data')); if ColumnIndex <> -1 then Result := JStringToString(Cur.getString(ColumnIndex)); end; finally if Cur <> nil then Cur.close; end; end // 文档类文件(文件管理器选择的文档) else begin // 复制到应用私有目录,避免权限限制 Result := CopyUriToPrivateFile(Uri); end; end // 处理file://协议的Uri else if Scheme.equals(TJContentResolver.JavaClass.SCHEME_FILE) then begin Result := JStringToString(Uri.getPath); end; end; // 辅助函数:将Uri指向的文件复制到应用私有目录,返回新路径 function CopyUriToPrivateFile(Uri: Jnet_Uri): String; var Input: JInputStream; Output: JFileOutputStream; Buffer: TJavaArray<Byte>; TempFile: JFile; begin Result := ''; TempFile := TJFile.JavaClass.init(TAndroidHelper.Context.getFilesDir, StringToJString('temp_' + GetFileName(Uri))); try Input := TAndroidHelper.Context.getContentResolver.openInputStream(Uri); Output := TJFileOutputStream.JavaClass.init(TempFile); Buffer := TJavaArray<Byte>.Create(1024); while (Input.read(Buffer) > 0) do begin Output.write(Buffer); end; Result := JStringToString(TempFile.getAbsolutePath); finally if Input <> nil then Input.close; if Output <> nil then Output.close; if Buffer <> nil then Buffer.Free; end; end;
2. 修正文件选择逻辑(替换伪路径为真实路径)
修改原GetFileBrowser中的路径获取代码,使用上面的GetRealPathFromURI函数:
procedure TDM.GetFileBrowser(ReqCode: Integer); var Intent: JIntent; FName: String; RealPath: String; begin Intent := TJIntent.Create; Intent.setType(StringToJString('*/*')); Intent.setAction(TJIntent.JavaClass.ACTION_GET_CONTENT); Intent.putExtra(TJIntent.JavaClass.EXTRA_ALLOW_MULTIPLE, true); TMessageManager.DefaultManager.SubscribeToMessage(TMessageResultNotification, procedure(const Sender : TObject; const aMessage : TMessage) var msgRES : TMessageResultNotification; i: Integer; begin msgRES := TMessageResultNotification(aMessage); if msgRES.RequestCode = ReqCode then if (msgRES.ResultCode = TJActivity.JavaClass.RESULT_OK) then begin URIfileTrn := msgRES.Value.getData(); if URIfileTrn = nil then // 多选模式 begin ClipDataF := msgRES.Value.getClipData; for i := 0 to ClipDataF.getItemCount-1 do begin URIfileTrn := ClipDataF.getItemAt(i).getUri; FName := GetFileName(URIfileTrn); RealPath := GetRealPathFromURI(URIfileTrn); if FName <> '' and (RealPath <> '') then case ReqCode of 1000: SaveFile(FName, RealPath); 2000: SaveSFile(FName, RealPath); end; end; end else begin // 单选模式 FName := GetFileName(URIfileTrn); RealPath := GetRealPathFromURI(URIfileTrn); if FName <> '' and (RealPath <> '') then case ReqCode of 1000: SaveFile(FName, RealPath); 2000: SaveSFile(FName, RealPath); end; end; URIfileTrn := nil; end; end); TAndroidHelper.Activity.startActivityForResult(Intent, ReqCode); end;
3. 修正文件打开代码(适配真实路径)
针对真实路径,使用FileProvider生成可共享的Uri,确保其他应用有权限打开:
procedure TDM.openFile(RealPath: String); var ExtFile: string; mime: JMimeTypeMap; ExtToMime: JString; Intent: JIntent; Uri: Jnet_Uri; LAuthority: JString; TargetFile: JFile; begin if DM.qFiles.IsEmpty then Exit; if not TFile.Exists(RealPath) then begin TDialogService.MessageDialog('文件不存在(可能已删除或移动)。是否从表格中删除该文件记录?', System.UITypes.TMsgDlgType.mtInformation, [System.UITypes.TMsgDlgBtn.mbYes, System.UITypes.TMsgDlgBtn.mbNo], System.UITypes.TMsgDlgBtn.mbNo, 0, procedure(const AResult: TModalResult) begin if AResult = mrYES then DM.qFiles.Delete; end); Exit; end; try ExtFile := AnsiLowerCase(StringReplace(TPath.GetExtension(RealPath), '.', '', [])); mime := TJMimeTypeMap.JavaClass.getSingleton(); ExtToMime := mime.getMimeTypeFromExtension(StringToJString(ExtFile)); if ExtToMime = nil then ExtToMime := StringToJString('application/octet-stream'); // 未知类型默认二进制 TargetFile := TJFile.JavaClass.init(StringToJString(RealPath)); LAuthority := StringToJString(JStringToString(TAndroidHelper.Context.getApplicationContext.getPackageName) + '.fileprovider'); Uri := TJcontent_FileProvider.JavaClass.getUriForFile(TAndroidHelper.Context, LAuthority, TargetFile); Intent := TJIntent.Create; Intent.setFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION); Intent.setAction(TJIntent.JavaClass.ACTION_VIEW); Intent.setDataAndType(Uri, ExtToMime); // 检查是否有应用支持打开该文件 if Intent.resolveActivity(TAndroidHelper.Context.getPackageManager) <> nil then TAndroidHelper.Activity.startActivity(Intent) else ShowMessage('未找到可打开该文件的应用!'); except ShowMessage('无法打开文件!'); end; end;
4. 文件转Base64并HTTPS传输
使用Indy组件实现文件内容转Base64,再通过HTTPS上传至服务器:
uses IdEncoderMIME, IdHTTP, System.Classes, System.NetEncoding; // 文件内容转Base64字符串 function FileToBase64(const FilePath: String): String; var Stream: TFileStream; Encoder: TIdEncoderMIME; begin Result := ''; if not TFile.Exists(FilePath) then Exit; Stream := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite); try Encoder := TIdEncoderMIME.Create(nil); try Result := Encoder.Encode(Stream); finally Encoder.Free; end; finally Stream.Free; end; end; // 上传Base64内容到服务器 procedure UploadFileToServer(const FilePath, ServerURL: String); var Base64Content: String; HTTP: TIdHTTP; Params: TStringList; begin Base64Content := FileToBase64(FilePath); if Base64Content = '' then Exit; HTTP := TIdHTTP.Create(nil); Params := TStringList.Create; try HTTP.Request.ContentType := 'application/x-www-form-urlencoded'; // URL编码避免特殊字符问题 Params.Add('filename=' + TNetEncoding.URL.Encode(TPath.GetFileName(FilePath))); Params.Add('filecontent=' + TNetEncoding.URL.Encode(Base64Content)); HTTP.Post(ServerURL, Params); ShowMessage('文件上传成功!'); except on E: Exception do ShowMessage('上传失败:' + E.Message); end; HTTP.Free; Params.Free; end;
用户原代码参考
// 获取文件名函数 function GetFileName(Uri: Jnet_Uri): String; var Cur: JCursor; begin Result := ''; Cur := TAndroidHelper.Context.getContentResolver().query(Uri, nil, nil, nil, nil, nil); try if (Cur <> nil) then if Cur.moveToFirst then Result := JStringToString(Cur.getString(cur.getColumnIndex(TJOpenableColumns.JavaClass.DISPLAY_NAME))); finally Cur.close; end; end;
内容的提问来源于stack exchange,提问作者HardLink
相关产品推荐
相关产品推荐

