Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Проблема с GetRealPathFromURI в Android 15 при загрузке файлов на сервер PHP: причины и решения

Delphi , Синтаксис , API реализация

 

При разработке Android приложений на Delphi, использующих Intents для выбора файлов и последующей их загрузки на сервер (например, PHP), можно столкнуться с проблемой, когда функция GetRealPathFromURI перестает работать на Android 15 (и, возможно, на более новых версиях). В частности, она может возвращать пустую строку, что делает невозможным получение физического пути к выбранному файлу.

Причина проблемы:

Начиная с Android API 33 (Android 13), Google ввела более строгие ограничения на доступ к внешнему хранилищу. Разрешение READ_EXTERNAL_STORAGE больше не предоставляет доступ ко всем файлам на внешнем хранилище. Это изменение направлено на повышение конфиденциальности пользователей. В Android 15 эти ограничения, вероятно, еще больше ужесточены. Функция GetRealPathFromURI, полагающаяся на получение прямого пути, может не работать из-за отсутствия необходимых разрешений или изменений в структуре хранения файлов.

Решение, предложенное автором оригинального поста:

Автор оригинального поста решил проблему, скопировав выбранный файл из URI во внутреннюю или кэш-директорию приложения, а затем загрузив копию на сервер.

Пример кода на Object Pascal (Delphi):

uses
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Net,
  Androidapi.JNI.Os,
  Androidapi.JNIBridge,
  System.IOUtils,
  System.Net.HttpClient,
  System.Net.URLClient,
  IOUtils;

function CopyFileFromUri(const URI: string; const DestDir: string): Boolean;
var
  InputStream: JInputStream;
  OutputStream: TJavaOutputStream;
  Buffer: array[0..4095] of Byte;
  BytesRead: Integer;
  FileName: string;
  DestPath: string;
  Context: JContext;
begin
  Result := False;
  try
    Context := SharedActivityContext; // Получаем контекст Android
    InputStream := (Context.getContentResolver.openInputStream(StringToJURI(URI)) as JInputStream);
    if InputStream <> nil then
    begin
      FileName := GetFileNameByUri(URI); // Функция для получения имени файла из URI (см. ниже)
      DestPath := IncludeTrailingPathDelimiter(DestDir) + FileName;
      OutputStream := TJavaOutputStream.Create(TFileStream.Create(DestPath, fmCreate));
      try
        repeat
          BytesRead := InputStream.read(Buffer, 0, SizeOf(Buffer));
          if BytesRead > 0 then
            OutputStream.write(Buffer, 0, BytesRead);
        until BytesRead = -1;
        Result := True;
      finally
        OutputStream.Free;
      end;
      InputStream.close;
    end;
  except
    on E: Exception do
      // Обработка ошибок, например, запись в лог
      ShowMessage('Ошибка при копировании файла: ' + E.Message);
  end;
end;

function GetFileNameByUri(const URI: string): string;
var
  Cursor: JCursor;
  ColumnIndex: Integer;
  FileName: string;
  Context: JContext;
begin
  Result := '';
  try
    Context := SharedActivityContext;
    Cursor := Context.getContentResolver.query(StringToJURI(URI), nil, nil, nil, nil);
    if Cursor <> nil then
    begin
      try
        ColumnIndex := Cursor.getColumnIndex(StringToJString('_display_name'));
        if ColumnIndex > -1 then
        begin
          Cursor.moveToFirst;
          FileName := JStringToString(Cursor.getString(ColumnIndex));
          Result := FileName;
        end;
      finally
        Cursor.close;
      end;
    end;
  except
    on E: Exception do
      // Обработка ошибок
      ShowMessage('Ошибка при получении имени файла: ' + E.Message);
  end;
end;

procedure TForm1.ButtonUploadClick(Sender: TObject);
var
  FilePath: string;
  IntentData: string;
begin
  // Предполагается, что IntentManager1.GetDataUri(intentData) возвращает URI выбранного файла
  IntentData := IntentManager1.GetDataUri(intentData);

  // Получаем путь к директории загрузок (можно использовать другую директорию, например, кэш)
  CopyFileFromUri(IntentData, GetEnvironmentDirectoryPath(dirDownloads));
  FilePath := GetEnvironmentDirectoryPath(dirDownloads) + '/' + GetFileNameByUri(IntentData);

  if FileExists(FilePath) then
  begin
    // Загрузка файла на сервер (пример с использованием HttpClient)
    HttpClient1.UploadFile('http://192.xx.xx.xx/webapi/uploadfile.php', FilePath, 'fileupload');
  end
  else
    ShowMessage('Файл не существует по адресу: ' + FilePath);
end;

Альтернативное решение (более предпочтительное):

Вместо копирования файла, более эффективным и безопасным подходом является работа с InputStream напрямую. Это позволяет избежать ненужного копирования и экономит место на устройстве.

Пример кода на Object Pascal (Delphi):

uses
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Net,
  Androidapi.JNI.Os,
  Androidapi.JNIBridge,
  System.IOUtils,
  System.Net.HttpClient,
  System.Net.URLClient,
  IOUtils;

procedure UploadFileFromUri(const URI: string; const UploadURL: string; const FieldName: string);
var
  InputStream: JInputStream;
  HttpClient: THTTPClient;
  Content: TMultipartFormDataContent;
  StreamContent: TStreamContent;
  Context: JContext;
  FileName: string;
  Response: IHTTPResponse;
begin
  try
    Context := SharedActivityContext;
    InputStream := (Context.getContentResolver.openInputStream(StringToJURI(URI)) as JInputStream);
    if InputStream <> nil then
    begin
      FileName := GetFileNameByUri(URI);

      HttpClient := THTTPClient.Create;
      Content := TMultipartFormDataContent.Create;
      StreamContent := TStreamContent.Create(TJavaInputStreamAdapter.Create(InputStream)); // Адаптер для работы с JInputStream как с TStream
      StreamContent.Headers.ContentType := 'application/octet-stream'; // Укажите правильный ContentType, если знаете
      Content.Add(StreamContent, FieldName, FileName);

      try
        Response := HttpClient.Post(UploadURL, Content);
        // Обработка ответа сервера
        if Response.StatusCode = 200 then
          ShowMessage('Файл успешно загружен')
        else
          ShowMessage('Ошибка при загрузке файла. Код ответа: ' + IntToStr(Response.StatusCode));
      finally
        Response := nil;
        Content.Free;
        HttpClient.Free;
      end;

      InputStream.close;
    end;
  except
    on E: Exception do
      ShowMessage('Ошибка при загрузке файла: ' + E.Message);
  end;
end;

// Адаптер для преобразования JInputStream в TStream
type
  TJavaInputStreamAdapter = class(TStream)
  private
    FInputStream: JInputStream;
    FPosition: Int64;
  public
    constructor Create(AInputStream: JInputStream);
    destructor Destroy; override;
    function Read(var Buffer; Count: Integer): Integer; override;
    function Seek(Offset: Int64; Origin: TSeekOrigin): Int64; override;
    property Position: Int64 read FPosition write FPosition;
    property Size: Int64 read GetSize;
  private
    function GetSize: Int64;
  end;

constructor TJavaInputStreamAdapter.Create(AInputStream: JInputStream);
begin
  inherited Create;
  FInputStream := AInputStream;
  FPosition := 0;
end;

destructor TJavaInputStreamAdapter.Destroy;
begin
  FInputStream := nil;
  inherited Destroy;
end;

function TJavaInputStreamAdapter.Read(var Buffer; Count: Integer): Integer;
var
  BytesRead: Integer;
begin
  BytesRead := FInputStream.read(Buffer, 0, Count);
  if BytesRead > 0 then
    FPosition := FPosition + BytesRead;
  Result := BytesRead;
end;

function TJavaInputStreamAdapter.Seek(Offset: Int64; Origin: TSeekOrigin): Int64;
begin
  // Seeking в JInputStream не поддерживается, возвращаем текущую позицию
  Result := FPosition;
end;

function TJavaInputStreamAdapter.GetSize: Int64;
begin
  // Размер JInputStream получить сложно, возвращаем -1
  Result := -1;
end;


procedure TForm1.ButtonUploadClick(Sender: TObject);
var
  IntentData: string;
begin
  IntentData := IntentManager1.GetDataUri(intentData);
  UploadFileFromUri(IntentData, 'http://192.xx.xx.xx/webapi/uploadfile.php', 'fileupload');
end;

Ключевые моменты альтернативного решения:

  • Использование TJavaInputStreamAdapter: Этот класс адаптирует JInputStream (Java InputStream) к TStream (Delphi Stream), что позволяет работать с данными, полученными из URI, как с обычным потоком данных.
  • Загрузка потока данных напрямую: TStreamContent.Create(TJavaInputStreamAdapter.Create(InputStream)) создает контент для загрузки из потока, что позволяет избежать промежуточного сохранения файла.
  • Использование THTTPClient и TMultipartFormDataContent: Эти классы упрощают процесс отправки данных на сервер в формате multipart/form-data, который обычно используется для загрузки файлов.
  • Обработка ContentType: Важно правильно указать ContentType для загружаемого файла. Если тип файла известен, укажите его явно (например, image/jpeg). Если тип файла неизвестен, можно использовать application/octet-stream.

Преимущества альтернативного решения:

  • Экономия места: Не требуется временное хранение файла на устройстве.
  • Производительность: Избегается операция копирования файла, что может быть особенно важно для больших файлов.
  • Безопасность: Меньше риска утечки данных, так как файл не сохраняется во временной директории.

Важно:

  • Убедитесь, что на сервере (PHP) правильно обрабатывается загрузка файлов в формате multipart/form-data.
  • Не забудьте добавить необходимые uses в ваш проект Delphi.
  • Протестируйте оба решения на разных версиях Android, чтобы убедиться в их корректной работе.
  • Обрабатывайте исключения, чтобы обеспечить стабильную работу приложения.

В заключение, проблема с GetRealPathFromURI в Android 15 требует обхода, связанного с ограничениями доступа к файловой системе. Использование InputStream напрямую для загрузки файлов на сервер является более эффективным и предпочтительным решением, чем копирование файла во временную директорию. Приведенные примеры кода на Object Pascal (Delphi) помогут вам реализовать эти решения в ваших Android приложениях.

Создано по материалам из источника по ссылке.

Контекст описывает проблему с `GetRealPathFromURI` в Android 15 при загрузке файлов на сервер PHP и предлагает решения, включая копирование файла и использование `InputStream` напрямую.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: API реализация ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 17:14:06
2025-10-29 02:53:22/0.010272979736328/0