LoginSignup
5
4

More than 5 years have passed since last update.

THttpClient の落とし穴1

Posted at

これは Delphi Advent Calendar 補欠の記事です。

THttpClient とはなんぞや

THttpClientSystem.Net.HttpClinet ユニットに含まれる、新しい HTTP クライアントです。

旧来の Indy TIdHttp は Indy が全ての OS で動くように独自に実装していますが、THttpClient クラスは、それぞれの OS が提供している HTTP ライブラリを使います。
そのため、高速かつ堅牢です(メンテナンスは OS 側に任せられる)。

また、Indy で SSL を使おうとすると OpenSSL ライブラリが必要だったりして、結構面倒でした。
THttpClient クラスは、追加のライブラリはもちろん必要ありません!

普段は非ビジュアルコンポーネントとして THttpClient をラップした TNetHttpClient コンポーネントを使う方が多いかもしれませんが、今回は THttpClient を直接使って、画像を読み込んでみます。

画像の取得

HTTP 経由での画像取得は、Get メソッドを呼び出すだけなのですが、THttpClient はブロッキング動作になるので、一度スレッドで囲んであげます。

今回は実は画像の取得がメインではないので、TThread.CreateAnonymousThread を使って簡単に済ませました。
↓たったこれだけのコードで画像を取得できます。超簡単ですね!!

const
  URL = 'https://qiita-image-store.s3.amazonaws.com/0/12977/af4a05d5-dea6-979e-c367-d80241056406.png';

TThread.CreateAnonymousThread(
  procedure
  var
    Http: THttpClient;
    FS: TFileSTream;
    FileName: String;
  begin
    FileName := TPath.Combine(TPath.GetDocumentsPath, 'test.jpg');

    // 画像の取得
    FS := TFileStream.Create(FileName, fmCreate, $1ff); // $1ff -> rwxrwxrwx
    try
      Http := THttpClient.Create;
      try
        Http.HandleRedirects := True;
        Http.OnReceiveData := HttpReceiveData;

        Http.Get(URL, FS);
      finally
        Http.DisposeOf;
      end;
    finally
      FS.DisposeOf;
    end;

    // 画像の表示
    TThread.Synchronize(
      TThread.Current,
      procedure
      begin
        Image1.Bitmap.LoadFromFile(FileName);
      end
    );
  end
).Start;

Android での実行例
de.png

うん。簡単!!

トラブル発生

Android 5.x, Android 6.x 端末で動作確認していたのですが…!
Android 4.x で動かすと…

de2.png

何も表示されていない!!!
しかも FileSize が 0 byte !

問題は TJavaArray に

Dalivk から ART に実行環境が変わったときに TJavaArray の扱いが変わりました。
これを Fix した時に、Android 4.x 系(Dalvik)での調査が不十分だったのかなあと勝手に勘ぐっております。

問題が結構深い所にあるので、とりあえず QP に報告したものの、このままでは困ります。

ただ、最終的な問題箇所は System.SysUtils.FileWrite の型無し引数版です。
なので、これを型あり引数版を使うようにしてしまえば良いのです!

ということで修正するユニットを作りました。

具体的な修正ポイント

THttpClient からは TStream.WriteData メソッドが呼び出されますが、最終的には TStream.Write メソッドが呼ばれます。
ですので、TFileStream.Write を override して下記の様に書き換えてやります。

type
  TFileStream = class(System.Classes.TFileStream)
  public
    function Write(
      const Buffer: TBytes;
      Offset, Count: Integer): Longint; override;
  end;

implementation

function TFileStream.Write(
  const Buffer: TBytes;
  Offset, Count: Integer): Longint;
begin
  Result := FileWrite(Handle, Buffer[Offset], Count);
end;

この Unit を uses するだけで、THttpClient.Get に TFileStream を渡しても、正しく動作するようになりました。

サンプルアプリ

上記で使ったサンプルアプリを GitHub にアップしました。
[Get image] ボタンをタップすると「でるお」の画像をダウンロードしてきます。

まとめ

実は THttpClient.Get メソッドは第二引数を指定しなければ、TMemoryStream が自動的に生成され THttpClient.Get の戻り値の IHttpResponse.ContentStream からデータを取得できます。
例えば今回のように画像などの小さなデータであれば、メモリ上に読み込んでも問題ありません(しかも、ファイルに書かないから速い!)。
ですが、ゲームに使うようなリソースファイル一式とかの大きなデータの場合、メモリが足りずに失敗してしまいます。
なので、TFileStream を第二引数に指定できるようになっているのですが、Android 4.x では動作しなかったので困ったね、というお話でした。

落とし穴2

つづくよ!!

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4