セカイノカタチ

世界のカタチを探求するブログ。関数型言語に興味があり、HaskellやScalaを勉強中。最近はカメラの話題も多め

Android で Volley の RequestFuture を使うと TimeoutException

Android開発をしていて、バックエンドのサーバーにHTTPリクエストを投げるというのは、ありふれたシチュエーションだと思いますが、 Volley というGoogleおすすめのライブラリを使っていてちょっとハマりました。

Volley でHTTPのリクエストを投げるのはこんな感じの処理になります。

val url = "http://hogehoge"
val queue = Volley.newRequestQueue(context.getApplicationContext())
queue.add(JsonObjectRequest(Request.Method.POST, url, JSONObject("{userId: $email, password: $password}"), { 正常処理 }, {エラー処理}))

で、 {正常処理} と {エラー処理} は、それぞれコールバックが入るのですが、面倒なので良い感じに処理してくれて結果を返してくれる RequestFuture といクラスがあります。

val url = "http://hogehoge"
val queue = Volley.newRequestQueue(context.getApplicationContext())
val rf: RequestFuture<JSONObject> = RequestFuture.newFuture()
queue.add(JsonObjectRequest(Request.Method.POST, url, JSONObject("{userId: $email, password: $password}"), rf, rf))

rf.get(10000, TimeUnit.MILLISECONDS) // ここでデッドロックする

このコードは、さくっとデッドロックして java.util.concurrent.TimeoutException を返します。

ちょっと調べると下記のStackOverFlowが引っかかるのですが、誰も回答をくれないので質問者本人がアレコレ試して別スレッドで結果を受け取ったら動いた。という感じの流れになっています。

stackoverflow.com

ちょっと釈然としないので、コードを追ってみました

RequestQueueはUIスレッドで実行される

Volley の RequestQueue::add は、標準でUIスレッドにHandler経由でpostするだけの実装でした。

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

new Handler(Looper.getMainLooper())) で、Handlerを生成して ExecutorDelivery の中でpostしています。

RequestFuture は Threadの事は考えず get() 内部で wait() している

RequestFuture は、ただのクラスなんですが、 get()を呼び出すと、なんやかんややって wait() します。そして、 RequestQueue から onResponse() が呼ばれるのを待ちます。

そして、 RequestQueue は、UIスレッドでレスポンスを処理して onResponse() を呼び出すので・・・。

UIスレッドで RequestFuture のget()を呼び出すと、UIスレッドでレスポンスを処理しようとする RequestQueue とだだかぶりじゃん!?!?!?

結論

RequestFuture の get() は、UIスレッド(mainスレッド)で呼んではいけない!

という結論でした。AndroidのThread周りは複雑だな・・・。

※ 同じハマり方をしている人が見つけやすいようにタイトルにキーワードをふんだんに盛り込んであります

Toneo Voley Playa Villananitos 2017