Android でHTTP通信の注意

Android 4.0 で、何も考慮せずに HTTP通信しようとすると強制停止されてできない。Android3.0 から
そうなってる。
3.0 以降でもHTTP通信を実行したければ、スレッドでHTTP通信しなければならない。

AsyncTask を使うのが常套手段と思ったが、あえて ExecutorService で書いてみた。

テスト用に HTTP-GET に対して、WicketJSON形式のデータを応答するものを用意する。

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
import org.apache.wicket.request.resource.ContentDisposition;
import org.apache.wicket.util.resource.StringResourceStream;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
 * Userという任意クラスで、UserAgent とHTTP-GETの時刻をJSON形式で返す。
 * JsonOutPage.html は、
 *  <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">http://www.w3.org/TR/html4/loose.dtd">
 * という1行だけの記述で用意する。
 */

public class JsonOutPage extends WebPage{
   public JsonOutPage(){
      GsonBuilder builder = new GsonBuilder();
      builder.setDateFormat("yyyy/MM/dd HH:mm:ss");

      String userAgent = *1.getHeader("User-Agent");
      User user = new User(userAgent);

      Gson gson = builder.create();
      getRequestCycle().scheduleRequestHandlerAfterCurrent(
         new ResourceStreamRequestHandler(new StringResourceStream(
               gson.toJson(user),"application/json; charset=utf-8")
               ).setContentDisposition(ContentDisposition.ATTACHMENT)
      );
   }
}
public class User{
   public String userAgent;
   public Date date;
   public User(String userAgent){
      this.userAgent = userAgent;
      this.date = new Date();
   }
}

Wicket の Application クラスで、mount を書いて置く。
以下のようなメソッドを用意して、
private void mount(String mountPath, Class<? extends WebPage> pageClass){
   getRootRequestMapperAsCompound().add(new MountedMapper(mountPath,pageClass));
}
Application クラスで、
   mount("/jsontest",JsonOutPage.class);
とWebアプリの相対 url に上の Page クラスを指定する。
Webサーバーがこれで準備OK、、、次に Android.....


テストなので、ボタンクリックしたら、TextView に受信する JSON データを表示するものを
作ってみる。

java.util.concurrent.ExecutorService の submit 実行、Future でデータを受け取るように
してみる。したがって Callable の実装クラスで HttpClient を実行mJSON 形式の変換は、
Google の GSON に任せる。

import java.io.ByteArrayOutputStream;
import java.util.concurrent.Callable;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;

import android.net.http.AndroidHttpClient;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
 * JsonHttpGetter.java
 * コンストラクタで、URL文字列と Type と一致する JSON データ格納クラスを指定する
 */

public class JsonHttpGetter<T> implements Callable<T>{
   private String url;
   private Class<T> type;
   public JsonHttpGetter(String url,Class<T> type){
      this.url = url;
      this.type = type;
   }
   @Override
   public T call() throws Exception{
      String responseText = null;
      AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
      HttpUriRequest httpUriRequest = new HttpGet(url) ;
      HttpResponse response = client.execute(httpUriRequest);
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      response.getEntity().writeTo(byteArrayOutputStream);
      if (response.getStatusLine().getStatusCode()==HttpStatus.SC_OK){
         responseText = byteArrayOutputStream.toString();
      }
      byteArrayOutputStream.close();

      GsonBuilder builder = new GsonBuilder();
      Gson gson = builder.create();

      return gson.fromJson(responseText,this.type);
   }
}
これを、OnClickListener の onClick の中で以下のように実行する

*2.setOnClickListener(new OnClickListener(){
   @Override
   public void onClick(View v){
      TextView textView = (TextView)findViewById(R.id.textView1);
      ExecutorService executorService = Executors.newSingleThreadExecutor();
      // Web側と同じクラス User で指定する。
      Future<User> future = executorService.submit(
new JsonHttpGetter<User>("http://xxx.xxx.xxx.xxx/sample/jsontest",User.class)
      );
      try{
      User user = future.get();
      SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
      textView.setText("["+simpleFormat.format(user.date)+"]["+user.userAgent+"]");
      }catch(InterruptedException e){
         Log.d("sample",e.getMessage(),e);
      }catch(ExecutionException e){
         Log.d("sample",e.getMessage(),e);
      }
   }
});

TextView には、AndroidHttpClient の newInstance で指定した名前で
  [yyyy/MM/dd HH:mm:ss][Android]

と表示される。JsonHttpGetter の 
org.apache.http.client.HttpClient を使うと User-Agent の表示は、
  Apache-HttpClient/UNAVAILABLE(java 1.4)
になるはずだ。

Android 2.3.3 でもこの ExecutorService の submit 実行、Future でデータを受け取る方法で
動いた。
ここで書いた JsonHttpGetter を見て、
  public abstract AbstractHttpGet<T> implements Callable<T> {
    public abstract T convert(String str);
   :
   :
のように書いて
call() メソッドは、AbstractHttpGet の中で実装して HTTP-GET の結果でconvertメソッド
実行して call() メソッドの返り値にしても良いのではと考えた。

*1:ServletWebRequest)getRequestCycle().getRequest(

*2:Button)findViewById(R.id.button1