android - 正确进行线程化 FTP 下载 (Indy/Android)

标签 android delphi ftp indy delphi-10.1-berlin

我制作了一个 IdFTP 来从我的 FTP 服务器下载文件,但是当我尝试将其线程化时,它卡在了 Android 操作系统的“正在解析主机名...”中。

没有线程(工作正常):

uses ..., IdFTPCommon;

var
  RecordDownload: TMemoryStream;

uses System.IOUtils;

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdFTP1.Host := 'motoristaajudante.ddns.net';
  IdFTP1.Port := 2121;
  IdFTP1.DataPortMin := 50100;
  IdFTP1.DataPortMax := 51100;
  IdFTP1.Username := 'anonymous';
  IdFTP1.TransferType := IdFTPCommon.TIdFTPTransferType.ftBinary;
  IdFTP1.Passive := True;
  try
    IdFTP1.Connect();
    IdFTP1.Get('00001.m4a',TPath.GetDocumentsPath + PathDelim + '00001.m4a',True,False);
  except
    IdFTP1.Disconnect;
  end;
end;

procedure TForm1.IdFTP1AfterGet(ASender: TObject; AStream: TStream);
begin
  IdFTP1.Disconnect;
end;

procedure TForm1.IdFTP1WorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
  if FileExists(TPath.GetDocumentsPath + PathDelim + '00001.m4a') then
  begin
      ShowMessage('Downloaded!');
  end;
end;

以及我在 this solution 之后制作的线程代码:

uses ..., IdFTPCommon;

type
  TLoadThread = class(TThread)
  public
    constructor Create; reintroduce;
  protected
    procedure Execute; override;
  end;

type
  TForm1 = class(TForm)
  ...
  procedure ThreadTerminated(Sender: TObject);

var
  RecordDownload: TMemoryStream;
  Loading: Boolean = False;
  zLThread: TLoadThread = nil;

uses System.IOUtils;

constructor TLoadThread.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
end;

procedure TLoadThread.Execute;
begin
try
  Form1.IdFTP1.Connect();
  Form1.IdFTP1.Get('00001.m4a',TPath.GetDocumentsPath + PathDelim + '00001.m4a',True,False);
except
  Form1.IdFTP1.Disconnect;
end;
end;

procedure TForm1.ThreadTerminated(Sender: TObject);
begin
  zLThread := nil;
  Loading := False;
  FloatAnimation1.Enabled := False;
  FloatAnimation2.Enabled := False;
  Arc3.StartAngle := -90;
  Arc3.EndAngle := 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdFTP1.Host := 'motoristaajudante.ddns.net';
  IdFTP1.Port := 2121;
  IdFTP1.DataPortMin := 50100;
  IdFTP1.DataPortMax := 51100;
  IdFTP1.Username := 'anonymous';
  IdFTP1.TransferType := IdFTPCommon.TIdFTPTransferType.ftBinary;
  IdFTP1.Passive := True;
  zLThread := TLoadThread.Create;
  zLThread.OnTerminate := ThreadTerminated;
  zLThread.Start;
  Loading := True;
  FloatAnimation1.Enabled := True;
  FloatAnimation2.Enabled := True;
end;

procedure TForm1.IdFTP1AfterGet(ASender: TObject; AStream: TStream);
begin
  IdFTP1.Disconnect;
end;

procedure TForm1.IdFTP1WorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
  Form1.FloatAnimation1.Enabled := False;
  Form1.FloatAnimation2.Enabled := False;
  Form1.Arc3.StartAngle := -90;
  Form1.Arc3.EndAngle := 0;
  if FileExists(TPath.GetDocumentsPath + PathDelim + '00001.m4a') then
  begin
      ShowMessage('Downloaded!');
  end;
end;

procedure TForm1.IdFTP1Status(ASender: TObject; const AStatus: TIdStatus;
  const AStatusText: string);
begin
  Memo1.Lines.Add(AStatusText);
  Application.ProcessMessages;
end;

然后 FTP 状态显示它停留在“正在解析主机名...”。如何使其正确线程?

最佳答案

没有理由按照您使用它们的方式使用 OnAfterGetOnWorkEnd 事件。 Indy 是同步的。 TIdFTP.Get() 在传输完成之前不会返回,如果发生错误,则会引发异常。

所以,去掉两个事件处理器,用try/finally代替try/except来调用Disconnect(),处理仅当 Get() 未触发时才下载。

OnWorkEndOnStatus 事件在调用 Connect()Disconnect() 的同一线程的上下文中触发Get()。因此,在您的线程示例中,这将是工作线程,而不是主线程。但是您的事件处理程序不会同步对您的 UI 控件的访问。这可能会导致各种问题,包括您遇到的卡住。您必须同步(TThread.OnTerminated 事件已同步)。

话虽如此,试试这个:

非线程:

uses
  ..., IdFTPCommon;

...

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdFTP1.Host := 'motoristaajudante.ddns.net';
  IdFTP1.Port := 2121;
  IdFTP1.DataPortMin := 50100;
  IdFTP1.DataPortMax := 51100;
  IdFTP1.Username := 'anonymous';
  IdFTP1.TransferType := IdFTPCommon.TIdFTPTransferType.ftBinary;
  IdFTP1.Passive := True;
  try
    IdFTP1.Connect;
    try
      IdFTP1.Get('00001.m4a', TPath.GetDocumentsPath + PathDelim + '00001.m4a', True, False);
    finally
      IdFTP1.Disconnect;
    end;
    ShowMessage('Downloaded!');
  except
    ShowMessage('Error while downloading!');
  end;
end;

线程化:

uses
  ..., IdFTPCommon;

type
  TLoadThread = class(TThread)
  public
    constructor Create; reintroduce;
  protected
    procedure Execute; override;
  end;

type
  TForm1 = class(TForm)
    ...
    IdFTP1: TIdFTP;
    procedure ThreadTerminated(Sender: TObject);
    ... 
  private
    Loading: Boolean;
    zLThread: TLoadThread;
  end;

...

constructor TLoadThread.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
end;

procedure TLoadThread.Execute;
begin
  Form1.IdFTP1.Connect;
  try
    Form1.IdFTP1.Get('00001.m4a', TPath.GetDocumentsPath + PathDelim + '00001.m4a', True, False);
  finally
    Form1.IdFTP1.Disconnect;
  end;
end;

procedure TForm1.ThreadTerminated(Sender: TObject);
begin
  zLThread := nil;
  Loading := False;
  FloatAnimation1.Enabled := False;
  FloatAnimation2.Enabled := False;
  Arc3.StartAngle := -90;
  Arc3.EndAngle := 0;
  If TThread(Sender).FatalException = nil then
    ShowMessage('Downloaded!')
  else
    ShowMessage('Error while Downloading!');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdFTP1.Host := 'motoristaajudante.ddns.net';
  IdFTP1.Port := 2121;
  IdFTP1.DataPortMin := 50100;
  IdFTP1.DataPortMax := 51100;
  IdFTP1.Username := 'anonymous';
  IdFTP1.TransferType := IdFTPCommon.TIdFTPTransferType.ftBinary;
  IdFTP1.Passive := True;
  zLThread := TLoadThread.Create;
  zLThread.OnTerminate := ThreadTerminated;
  zLThread.Start;
  Loading := True;
  FloatAnimation1.Enabled := True;
  FloatAnimation2.Enabled := True;
end;

procedure TForm1.IdFTP1Status(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
begin
  TThread.Queue(nil, 
    procedure
    begin
      Memo1.Lines.Add(AStatusText);
    end
  );
end;

关于android - 正确进行线程化 FTP 下载 (Indy/Android),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46598356/

相关文章:

android - 通知点击回调

delphi - 如果更大,则无法设法使主窗体显示滚动条

delphi - 如何在Delphi中格式化MM/DD/YYYY日期

PHP FTP 递归目录列表

java - 有什么方法可以通过 common-vfs2 为 ftp 或 cifs 支持 IPV6

android - 即使我释放唤醒锁,我的应用程序也会耗尽电池电量

java - readInt() 在 Android 中如何工作?

delphi - 如何在已打开的 Delphi IDE 中从另一个应用程序打开 .pas 文件并定位到行#

authentication - 使用 pam_userdb.so 在 Berkeley DB 中为 vsftpd 加密密码

java - 从内部类内部访问局部变量