[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;
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) {
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
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));
}
}
続きは近いうちに書きます。
参考サイト
Upload files by sending multipart request programmatically
HTTPマルチパートデータ送信 « androidnote