前回、非同期通信を行う為のAsyncTaskについての記事を書いたのですが、
今回はネットワーク通信用ライブラリ:Volleyについての備忘録です。
Google I/O 2013 : Volley: Easy, Fast Networking for Android
公式リポジトリ : https://android.googlesource.com/platform/frameworks/volley/
Volleyのライブラリを利用するためには、Gitを使ってプロジェクトを取得し、Androidプロジェクトとしてインポートする必要があります。
| 
					 1  | 
						git clone https://android.googlesource.com/platform/frameworks/volley  | 
					
サーバーに画像をアップロードさせたいので、今回も multipart の場合についてです。
RequestQueue
リクエストを管理するキューは原則として1つだけとのことなので、シングルトンで生成します。
Volley.newRequestQueueの第2引数にHurlStackインスタンスを渡す際にSSLSocketFactoryを指定できるので、デバッグ時にはテスト用のSSLSocketFactoryを指定できます。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  | 
						public class VolleyHelper {     private static final Object sLock = new Object();     private static RequestQueue sRequestQueue;     /*      * RequestQueueのシングルトン生成      */     public static RequestQueue getRequestQueue(final Context context) throws IOException {         synchronized(sLock) {             if ( sRequestQueue == null ) {                 sRequestQueue = Volley.newRequestQueue(context, new MyHurlStack(null, MySSLSocketFactory.getSSLContext().getSocketFactory()));             }             return sRequestQueue;         }     } }  | 
					
<参考>
・[Android Developers] Setting Up a RequestQueue
 
Request
Multipart用のRequestクラスです。
サーバーからのレスポンスをJSONで受信するので、JsonRequestクラスを継承しています。
マルチパート形式に対応するためには、以下のライブラリの追加が必要です。
・httpmime-x.x.x.jar
http://hc.apache.org/downloads.cgi ⇒ HttpClient x.x.x (GA) の項目よりダウンロード
(今回私は、httpmime-4.3.3.jarとhttpcore-4.3.2.jarを追加しました。)
※buildMultipartEntity()メソッド内でboundaryの設定を行っていますが(22行目)、
設定が無い場合には MultipartEntityBuilder側でgenerateBoundary()で生成されます。
ただ、何が悪いのかまだ不明ですが、boundaryを設定しないと、サーバー側に渡されるパラメータが不正なものになってしまい、処理が失敗するので、boundaryを設定しています。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67  | 
						/*  * Multipart用のRequestクラス  */ public class MultipartRequest extends JsonRequest<JSONObject> {     private final Map<String, String> mStringParts;     private final Map<String, File> mFileParts;     private MultipartEntityBuilder mBuilder = MultipartEntityBuilder.create();     public MultipartRequest(String url,                             Map<String, String> stringParts,                             Map<String, File> fileParts,                             Response.Listener<JSONObject> listener,                             Response.ErrorListener errorListener) {         super(Method.POST, url, null, listener, errorListener);         mStringParts = stringParts;         mFileParts = fileParts;         buildMultipartEntity();     }     private void buildMultipartEntity() {         mBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);         mBuilder.setBoundary("_____"+Long.toString(System.currentTimeMillis())+"_____");         mBuilder.setCharset(Consts.UTF_8);         for ( Map.Entry<String, String> entry : mStringParts.entrySet() ) {             mBuilder.addTextBody(entry.getKey(), entry.getValue());         }         for ( Map.Entry<String, File> entry : mFileParts.entrySet() ) {             ContentType imageContentType = ContentType.create("image/jpeg");             mBuilder.addBinaryBody("upfile", entry.getValue(), imageContentType, entry.getKey());         }     }     @Override     public String getBodyContentType() {         return mBuilder.build().getContentType().getValue();     } /*  @Override     public byte[] getBody() {         ByteArrayOutputStream bos = new ByteArrayOutputStream();         try {             mBuilder.build().writeTo(bos);         } catch (IOException e) {             e.printStackTrace();         }         return bos.toByteArray();     } */     public HttpEntity getEntity() {         return mBuilder.build();     }     @Override     protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {         try {             String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));             return Response.success(new JSONObject(jsonString), HttpHeaderParser.parseCacheHeaders(response));         } catch (UnsupportedEncodingException e) {             return Response.error(new ParseError(e));         } catch (JSONException je) {             return Response.error(new ParseError(je));         }     } }  | 
					
Multipart用HurlStack
Multipartの場合だけ独自の処理を行うようにしたHurlStakを継承したクラスを作成します。
Out of memoryを避けるために、MultipartRequestクラスでMultipartEntityを返すメソッド(getEntity())を実装し、そのメソッドを使ってMultipartEntityを設定します。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48  | 
						public class MyHurlStack extends HurlStack {     private static final String HEADER_CONTENT_TYPE = "Content-Type";     public MyHurlStack(UrlRewriter urlRewriter, javax.net.ssl.SSLSocketFactory sslSocketFactory) {         super(urlRewriter, sslSocketFactory);     }     @Override     public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)         throws AuthFailureError, IOException {         if ( ! (request instanceof MultipartRequest) ) {             return super.performRequest(request, additionalHeaders);         }         HttpPost httpRequest = new HttpPost(request.getUrl());         httpRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());         setMultipartBody(httpRequest, request);         addHeaders(httpRequest, additionalHeaders);         addHeaders(httpRequest, request.getHeaders());         HttpParams httpParams = httpRequest.getParams();         int timeoutMs = request.getTimeoutMs();         HttpConnectionParams.setConnectionTimeout(httpParams, 5000);         HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);         SchemeRegistry registry = new SchemeRegistry();         registry.register(new Scheme("http", new PlainSocketFactory(), 80));         registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));         ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(httpParams, registry);         HttpClient httpClient = new DefaultHttpClient(manager, httpParams);         return httpClient.execute(httpRequest);     }     private void addHeaders(HttpPost httpRequest, Map<String, String> headers) {         for ( String key : headers.keySet() ) {             httpRequest.setHeader(key, headers.get(key));         }     }     private static void setMultipartBody(HttpEntityEnclosingRequestBase httpRequest, Request<?> request)             throws AuthFailureError {         if ( request instanceof MultipartRequest ) {             httpRequest.setEntity(((MultipartRequest) request).getEntity());         }     } }  | 
					
リクエスト発行
MultipartRequestを使用したリクエストを発行します。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  | 
						    Context context = getActivity().getApplicationContext();     RequestQueue rQueue = VolleyHelper.getRequestQueue(context);     Map<String, String> stringParts = new HashMap<String, String>();     stringParts.put("key", "value");     Map<String, File> fileParts = new HashMap<String, File>();     File file = new File("imagefile.jpg");     fileParts.put("file", file);     MultipartRequest postRequest = new MultipartRequest("https://example.com/fileupload.php", stringParts, fileParts, new Listener<JSONObject>() {             @Override             public void onResponse(JSONObject jsonResponse) {             }         },         new Response.ErrorListener() {             @Override             public void onErrorResponse(VolleyError error) {             }         });     rQueue.add(postRequest);     rQueue.start();  | 
					
<参考にしたサイト>
・FLY1TKG BLOG 「Volleyでmultipart/form-dataを送信する」