これは Delphi Advent Calendar 補欠の記事です。
#THttpClient とはなんぞや
THttpClient は System.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 5.x, Android 6.x 端末で動作確認していたのですが…!
Android 4.x で動かすと…
何も表示されていない!!!
しかも 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
つづくよ!!