Multipart
HTML などで、ファイルを送る時に行われる処理をご存知であれば、理解しやすいかも。。。
<form action="URL" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
</form>
このような処理を書いたこと一度はあるともいます。
enctype を指定することで、ファイルデーターをサーバー側に送信できるようになります。
Volley は?
マルチパートを Volley にも標準で対応してほしいものですが、対応されていないようです。
OkHttpという便利なライブラリがあるが、今回は Apatch HttpClient Mime と HttpComponents Client For Android を利用し対応します。
ライブラリを Gradle に設定
※ライブラリのバージョンによって挙動や実際の実装内容が異なります。今回のバージョンと違うバージョンを利用される時は注意ください。
dependencies {
...
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
compile 'org.apache.httpcomponents:httpclient-cache:4.3.6'
compile 'org.apache.httpcomponents:httpclient:4.3.6'
compile 'org.apache.httpcomponents:httpmime:4.3.6'
compile 'org.apache.httpcomponents:fluent-hc:4.3.6'
...
}
Android Studio の [Sync Project With Gradle File] ボタンを押すと、記述したライブラリのバージョンをインストールしてくれます。
これで、ライブラリを使う準備が整いました。
MultipartRequest を作る
まず、マルチパートの処理は、リクエストの処理の一環なので、Volley の Request<T> を拡張したいと思います。
/**
* Created by shun_nakahara on 7/9/15.
*
* Base class for all network multipart requests.
* ContentType MULTIPART_FORM_DATA 形式のファイルを送る為のクラスです。
*
* @author shun_nakahara
*
* @param <T> The type of parsed response this request expects.
*/
public abstract class MultipartRequest<T> extends Request<T> {
...
}
MultipartEntityBuilder
マルチパートのパラメータビルド処理を行うクラス
/**
* Multipart のパラメータビルド処理を担うクラス
*/
private final MultipartEntityBuilder mMultipartEntityBuilder = MultipartEntityBuilder.create();
こちらのパラメータは必ず利用するので、クラス作成されたタイミングで、.create() しておきます。
HttpEntity
コンテンツ情報など格納
/**
* コンテンツ設定に必要なデーターを格納
*/
public HttpEntity mHttpEntity;
MultipartEntityBuilder から作成される値を保持する。
MultipartRequest (コンストラクタ)
送りたい Request Params を MultipartEntityBuilder に追加していく。
今回は、テキストと画像のバイナリデーターを追加します。
/**
* {@link MultipartRequest} Constructor
*
* @param url {@link String}
* @param requestStringBody {@link LinkedHashMap}
* @param requestFileBody {@link LinkedHashMap}
* @param requestFileName {@link LinkedHashMap}
* @param listener {@link Response.Listener}
* @param errorListener {@link Response.ErrorListener}
*/
public MultipartRequest(@NonNull String url, @Nullable LinkedHashMap<String, String> requestStringBody, @Nullable LinkedHashMap<String, byte[]> requestFileBody, @Nullable LinkedHashMap<String, String> requestFileName, @NonNull Response.Listener<T> listener, @NonNull Response.ErrorListener errorListener) {
super(Method.POST, url, errorListener);
mistener = listener;
this.mMultipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
this.mMultipartEntityBuilder.setCharset(Charset.defaultCharset());
if (requestStringBody != null) {
ContentType contentType = ContentType.create("text/plain", Consts.UTF_8);
for (LinkedHashMap.Entry<String, String> entry : requestStringBody.entrySet()) {
this.mMultipartEntityBuilder.addTextBody(entry.getKey(), entry.getValue(), contentType);
}
}
if (requestFileBody != null && requestFileName != null) {
for (LinkedHashMap.Entry<String, byte[]> entry : requestFileBody.entrySet()) {
this.mMultipartEntityBuilder.addBinaryBody(entry.getKey(), entry.getValue(), ContentType.MULTIPART_FORM_DATA, requestFileName.get(entry.getKey()));
}
}
}
getBodyContentType
POST PUT の時の Body ContentType を MultipartEntityBuilder から取得する
/**
* Returns the content type of the POST or PUT body.<br>
* @see Request#getBodyContentType()
*/
@Override
public String getBodyContentType() {
return this.mHttpEntity.getContentType().getValue();
}
deliverResponse
リクエストのコンテンツをパースし終わった値をリスナーに返却する
/**
* Subclasses must implement this to perform delivery of the parsed<br>
* response to their listeners. The given response is guaranteed to<br>
* be non-null; responses that fail to parse are not delivered.<br>
*
* @param response The parsed response returned by
* {@link #parseNetworkResponse(NetworkResponse)}
* @see Request#deliverResponse(Object)
*/
@Override
protected void deliverResponse(T response) {
this.mVolleyResponseListener.onResponse(response);
}
getBody
POST PUT 時に送られる Body データーを MultipartEntityBuilder から取得する
/**
* Returns the raw POST or PUT body to be sent.
*
* <p>By default, the body consists of the request parameters in<br>
* application/x-www-form-urlencoded format. When overriding this method, consider overriding<br>
* {@link #getBodyContentType()} as well to match the new body format.
*
* @throws AuthFailureError in the event of auth failure
* @see Request#getBody()
*/
@Override
public byte[] getBody() throws AuthFailureError {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
this.mHttpEntity = this.mMultipartEntityBuilder.build();
try {
this.mHttpEntity.writeTo(byteArrayOutputStream);
} catch (IOException e) {
e.printStackTrace();
}
return byteArrayOutputStream.toByteArray();
}
使用例
Response.Listener<String> listener = new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// リクエスト成功時
}
};
Response.ErrorListener errorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// リクエスト失敗時
}
};
MultipartRequest<String> multipartRequest = new MultipartRequest<String>("https://www.google.co.jp", requestStringBody, requestFileBody, requestFileName, listener, errorListener) {
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
};
RequestQueue requestQueue = Volley.newRequestQueue(this.getActivity());
requestQueue.add(multipartRequest);