[Draft] 前回、multipart/form-dataでファイルなどをPOSTする方法を書きましたが、
Androidでサーバーに画像データをPOSTする - kakueki61's dev history
アップロード中に進捗表示をしてあげた方がユーザーフレンドリーだろうということで、 進捗表示を実装しましたので書いておきたい思います。
やったこととしては、
- multipart/form-dataの形式に則ってデータを構築
- HttpUrlConnectionからOutputStreamを取得
- データをOutputStreamに流す
- どれだけ流しおえたかをActivityに通知する。
前回のおさらい
org.apache.http.entity.mime.MultipartEntityBuilderでmultipart/form-data用のデータを作成。
自作のHurlStack派生クラスを作成し、performRequest()をoverride.
performRequest()内で、HttpClient#execute(HttpPost request)によってMultipartFormEntityをPOST。
今回、データアップロード中の進捗を表示をするために問題だったのは、
HttpClientがネットワークへのデータ書き出しの部分を隠蔽してしまっているので
そこをこちらでハンドリングするのが困難だという点でした。
ので、HttpClientを使うのはやめてHttpURLConnectionを使うようにします。
実際、Volleyでデフォルトで使われているHurlStackではHttpURLConnectionを使っています。
HttpURLConnectionを使うと大変なこと
MultipartEntityBuilderが使えません。
つまり、multipart/form-data用のデータをHTTPの仕様に沿って自前で構築する必要があります。
実際にどのような形式のデータを送る必要があるかはこちらを参考にして下さい。
MultipartJsonRequest.java
public class MultipartJsonRequest extends JsonRequest<JSONObject> { private static final String TAG = MultipartJsonRequest.class.getSimpleName(); private static final String BOUNDARY = "___________________" + Long.toString(System.currentTimeMillis()); private Activity mActivity; private Map<String, String> mStringParams; private Map<String, InputStream> mBinaryParams; private INetworkProgressListener mProgressListener; private Handler mHandler; public MultipartJsonRequest(Activity activity, String url, Map<String, String> stringParams, Map<String, InputStream> bynaryParams, INetworkProgressListener progressListener, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) { super(activity, url, null, false, listener, errorListener); mStringParams = stringParams; mBinaryParams = bynaryParams; mProgressListener = progressListener; // Activityに通知するためのHandler mHandler = new Handler(Looper.getMainLooper(), null); } public Map<String, String> getStringParams() { return mStringParams; } public Map<String, InputStream> getBinaryParams() { return mBinaryParams; } @Override public String getBodyContentType() { return "multipart/form-data; boundary=" + BOUNDARY + "; charset=UTF-8"; } public String getBoundaryString() { return BOUNDARY; } public void deliverProgress(final int numCompleted) { mHandler.post(new Runnable() { @Override public void run() { mProgressListener.onNetworkProgress(numCompleted); } }); } @Override protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { // 省略 } }
MultipartHurlStack.java
@Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { if (!(request instanceof MultipartJsonRequest)) { return super.performRequest(request, additionalHeaders); } mMultipartRequest = (MultipartJsonRequest) request; mBoundary = mMultipartRequest.getBoundaryString(); URL url = new URL(mMultipartRequest.getUrl()); HttpURLConnection connection = openConnection(url, request); addHeaders(connection, additionalHeaders); addHeaders(connection, request.getHeaders()); connection.setChunkedStreamingMode(0); DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); try { writeStringParams(outputStream, mMultipartRequest.getStringParams()); writeBinaryParams(outputStream, mMultipartRequest.getBinaryParams()); finishWriting(outputStream); } catch (IOException e) { e.printStackTrace(); throw new IOException("IOException: during writing requests"); } ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); int responseCode = connection.getResponseCode(); if (responseCode == -1) { // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromConnection(connection)); for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response; } private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); int timeoutMs = request.getTimeoutMs(); //TODO いる? LogHelper.i(TAG, "timeoutMs: " + timeoutMs); connection.setConnectTimeout(timeoutMs); connection.setReadTimeout(timeoutMs); connection.setUseCaches(false); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", mMultipartRequest.getBodyContentType()); return connection; } private void writeStringParams(DataOutputStream outputStream, Map<String, String> stringParams) throws IOException { StringBuilder builder = new StringBuilder(); for (Map.Entry<String, String> entry : stringParams.entrySet()) { builder.append(TWO_HYPHENS + mBoundary + LINE_END); builder.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINE_END); builder.append(LINE_END); builder.append(entry.getValue()); builder.append(LINE_END); } LogHelper.d(TAG, "stringParams: \n" + builder.toString()); outputStream.write(builder.toString().getBytes(Constants.DEFAULT_CHARSET)); outputStream.flush(); } private void writeBinaryParams(DataOutputStream outputStream, Map<String, InputStream> binaryParams) throws IOException { int numCompleted = 0; for (Map.Entry<String, InputStream> entry : binaryParams.entrySet()) { StringBuilder builder = new StringBuilder(); builder.append(TWO_HYPHENS + mBoundary + LINE_END); builder.append("Content-Disposition: form-data; " + "name=\"uploadFiles[]\"; " + "filename=\"" + entry.getKey() + "\"" + LINE_END); builder.append("Content-Type: multipart/form-data" + LINE_END); //TODO content-type確認 builder.append(LINE_END); LogHelper.d(TAG, "binaryParams: \n" + builder.toString()); outputStream.writeBytes(builder.toString()); outputStream.flush(); InputStream inputStream = entry.getValue(); byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int readBytes = -1; while((readBytes = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, readBytes); } outputStream.flush(); inputStream.close(); outputStream.writeBytes(LINE_END); outputStream.flush(); LogHelper.i(TAG, "finished!!!!"); numCompleted++; mMultipartRequest.deliverProgress(numCompleted); } } private void finishWriting(DataOutputStream outputStream) throws IOException { StringBuilder builder = new StringBuilder(); builder.append(TWO_HYPHENS + mBoundary + TWO_HYPHENS + LINE_END); outputStream.writeBytes(builder.toString()); outputStream.flush(); outputStream.close(); } private void addHeaders(HttpURLConnection conn, Map<String, String> headers) { for (String key : headers.keySet()) { conn.setRequestProperty(key, headers.get(key)); } }
続きは近いうちに書きます。
参考サイト