Android Volley の Request にマルチパートの処理を追加する


## Multipart
HTML などで、ファイルを送る時に行われる処理をご存知であれば、理解しやすいかも。。。

```
<form action="URL" method="post" enctype="multipart/form-data">
  <input type="file" name="file" />
</form>
```

このような処理を書いたこと一度はあるともいます。
enctype を指定することで、ファイルデーターをサーバー側に送信できるようになります。

## Volley は？
マルチパートを Volley にも標準で対応してほしいものですが、対応されていないようです。

[OkHttp](http://square.github.io/okhttp/)という便利なライブラリがあるが、今回は [Apatch HttpClient Mime](http://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime) と [HttpComponents Client For Android](http://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient-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);

```