В последнее время разработчики Delphi, работающие с мобильными приложениями для Android, сталкиваются с неожиданными проблемами при работе с файловыми операциями. В частности, как показал пример пользователя Peter C, возникают сложности с копированием файлов между директориями с использованием TDirectory.Copy. В этой статье мы разберем причины таких проблем и предложим несколько решений.
Описание проблемы
Пользователь столкнулся со следующей ситуацией:
При первом запуске копирование из TPath.GetPublicPath + '/diary' в TPath.GetSharedDocumentsPath + '/backup' работает корректно (2-3 секунды)
При повторном запуске операция завершается мгновенно, но файлы не копируются
После ручного удаления файлов и повторного запуска копирование не работает
После перезагрузки устройства копирование снова работает корректно
Если приложение создает новый файл, копируется только он
Анализ проблемы
Особенности работы TDirectory.Copy в Delphi
Метод TDirectory.Copy в Delphi имеет две перегрузки:
class procedure Copy(const SourceDirName, DestDirName: string); overload;
class procedure Copy(const SourceDirName, DestDirName: string; IgnoreErrors: Boolean); overload;
По умолчанию используется первая перегрузка, которая эквивалентна вызову второй с параметром IgnoreErrors=True. Это означает, что при возникновении ошибок копирования исключения не будут генерироваться.
Проблемы с разрешениями в Android
Начиная с Android 10 (API 29), Google ввел ограниченный доступ к хранилищу (Scoped Storage), что существенно изменило правила работы с файлами:
Приложения имеют доступ только к своим собственным файлам и определенным типам общих файлов
Операции с файлами, созданными вне приложения, могут быть ограничены
Удаление файлов вручную может привести к блокировкам
Решения проблемы
1. Использование IgnoreErrors=False
Как предложил Remy Lebeau, можно использовать перегрузку с IgnoreErrors=False для получения подробной информации об ошибках:
try
TDirectory.Copy(MASTER_PATH, BACKUP_PATH, False);
except
on E: EInOutError do
ShowMessage('Ошибка копирования: ' + E.Message);
end;
2. Принудительное копирование с проверкой файлов
Альтернативное решение - реализовать собственный механизм копирования с проверкой каждого файла:
procedure ForceDirectoryCopy(const SourceDir, DestDir: string);
var
Files: TStringDynArray;
I: Integer;
SourceFile, DestFile: string;
begin
if not TDirectory.Exists(DestDir) then
TDirectory.CreateDirectory(DestDir);
Files := TDirectory.GetFiles(SourceDir);
for I := 0 to High(Files) do
begin
SourceFile := Files[I];
DestFile := TPath.Combine(DestDir, TPath.GetFileName(SourceFile));
try
// Проверяем, нужно ли копировать (по размеру или дате модификации)
if not FileExists(DestFile) or
(TFile.GetLastWriteTime(SourceFile) > TFile.GetLastWriteTime(DestFile)) then
begin
TFile.Copy(SourceFile, DestFile, True);
end;
except
on E: Exception do
Log.d('Ошибка копирования файла ' + SourceFile + ': ' + E.Message);
end;
end;
end;
3. Работа с Android Storage Access Framework
Для корректной работы с файлами в Android рекомендуется использовать Storage Access Framework:
uses
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Net,
Androidapi.JNI.JavaTypes,
Androidapi.Helpers;
procedure RequestFileAccess;
var
Intent: JIntent;
begin
Intent := TJIntent.JavaClass.ACTION_OPEN_DOCUMENT_TREE;
TAndroidHelper.Activity.startActivityForResult(Intent, REQUEST_CODE_OPEN_DIRECTORY);
end;
Рекомендации по работе с файлами в Delphi для Android
Всегда обрабатывайте исключения при файловых операциях
Не полагайтесь на работу с файлами вне sandbox вашего приложения
Кэшируйте файловые операции для уменьшения количества обращений к хранилищу
Учитывайте ограничения Scoped Storage в новых версиях Android
Пример полного решения
procedure TForm1.BackupFiles;
var
Files: TStringDynArray;
I: Integer;
SourceFile, DestFile: string;
Success, AnyError: Boolean;
begin
if not TDirectory.Exists(MASTER_PATH) then
begin
ShowMessage('Исходная директория не существует');
Exit;
end;
if not TDirectory.Exists(BACKUP_PATH) then
TDirectory.CreateDirectory(BACKUP_PATH);
Files := TDirectory.GetFiles(MASTER_PATH);
AnyError := False;
for I := 0 to High(Files) do
begin
SourceFile := Files[I];
DestFile := TPath.Combine(BACKUP_PATH, TPath.GetFileName(SourceFile));
try
if not FileExists(DestFile) or
(TFile.GetLastWriteTime(SourceFile) > TFile.GetLastWriteTime(DestFile)) then
begin
TFile.Copy(SourceFile, DestFile, True);
end;
Success := True;
except
on E: Exception do
begin
Log.d('Ошибка копирования: ' + E.Message);
AnyError := True;
Success := False;
end;
end;
TThread.Synchronize(nil,
procedure
begin
UpdateProgress(I, High(Files), TPath.GetFileName(SourceFile), Success);
end);
end;
TThread.Synchronize(nil,
procedure
begin
if AnyError then
ShowMessage('Резервное копирование завершено с ошибками')
else
ShowMessage('Резервное копирование успешно завершено');
end);
end;
Заключение
Проблемы с копированием файлов в Delphi на Android связаны в первую очередь с изменениями в политике безопасности Google. Лучшим решением является:
Использование специальных методов для работы с файлами
Правильная обработка исключений
Ограничение операций только теми файлами, которые созданы самим приложением
Использование механизмов Android для запроса прав доступа при необходимости
Приведенные в статье решения помогут избежать описанных проблем и создать стабильно работающее приложение для резервного копирования файлов на Android устройствах.
Статья рассматривает проблемы копирования файлов в Delphi на Android, связанные с ограничениями Scoped Storage, и предлагает решения, включая обработку ошибок, альтернативные методы копирования и работу с Storage Access Framework.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.