編輯:關於Android編程
在開發當中,我們常常需要實現文件上傳,比較常見的就是圖片上傳,比如修改個頭像什麼的。但是這個功能在Android和iOS中都沒有默認的實現類,對於Android我們可以使用Apache提供的HttpClient.jar來實現這個功能,其中依賴的類就是Apache的httpmime.jar中的MultipartEntity這個類。我就是要實現一個文件上傳功能,但是我還得下載一個jar包,而這個jar包幾十KB,這尼瑪仿佛並非人間!今天我們就來自己實現文件上傳功能,並且弄懂它們的原理。
在上一篇文章HTTP POST請求報文格式分析與Java實現文件上傳中我們介紹了HTTP POST報文格式,如果有對POST報文格式不了解的同學可以先閱讀這篇文章。
我們知道,使用網絡協議傳輸數據無非就是要遵循某個協議,我們在開發移動應用時基本上都是使用HTTP協議。HTTP協議說白了就是基於TCP的一套網絡請求協議,你根據該協議規定的格式傳輸數據,然後服務器返回給你數據。你的協議參數要是傳遞錯了,那麼服務器只能給你返回錯誤。
這跟間諜之間對暗號有點相似,他們有一個規定的暗號,雙方見面,A說: 天王蓋地虎,B對: 寶塔鎮河妖。對上了,說事;對不上,弄死這B。HTTP也是這樣的,在HTTP請求時添加header和參數,服務器根據參數進行解析。形如 :
POST /api/feed/ HTTP/1.1 這裡是header數據 --分隔符 參數1 --分隔符 參數2只要根據格式來向服務器發送請求就萬事大吉了!下面我們就來看MultipartEntity的實現:
public class MultipartEntity implements HttpEntity {
private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.toCharArray();
/**
* 換行符
*/
private final String NEW_LINE_STR = "\r\n";
private final String CONTENT_TYPE = "Content-Type: ";
private final String CONTENT_DISPOSITION = "Content-Disposition: ";
/**
* 文本參數和字符集
*/
private final String TYPE_TEXT_CHARSET = "text/plain; charset=UTF-8";
/**
* 字節流參數
*/
private final String TYPE_OCTET_STREAM = "application/octet-stream";
/**
* 二進制參數
*/
private final byte[] BINARY_ENCODING = "Content-Transfer-Encoding: binary\r\n\r\n".getBytes();
/**
* 文本參數
*/
private final byte[] BIT_ENCODING = "Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes();
/**
* 分隔符
*/
private String mBoundary = null;
/**
* 輸出流
*/
ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();
public MultipartEntity() {
this.mBoundary = generateBoundary();
}
/**
* 生成分隔符
*
* @return
*/
private final String generateBoundary() {
final StringBuffer buf = new StringBuffer();
final Random rand = new Random();
for (int i = 0; i < 30; i++) {
buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
return buf.toString();
}
/**
* 參數開頭的分隔符
*
* @throws IOException
*/
private void writeFirstBoundary() throws IOException {
mOutputStream.write(("--" + mBoundary + "\r\n").getBytes());
}
/**
* 添加文本參數
*
* @param key
* @param value
*/
public void addStringPart(final String paramName, final String value) {
writeToOutputStream(paramName, value.getBytes(), TYPE_TEXT_CHARSET, BIT_ENCODING, "");
}
/**
* 將數據寫入到輸出流中
*
* @param key
* @param rawData
* @param type
* @param encodingBytes
* @param fileName
*/
private void writeToOutputStream(String paramName, byte[] rawData, String type,
byte[] encodingBytes,
String fileName) {
try {
writeFirstBoundary();
mOutputStream.write((CONTENT_TYPE + type + NEW_LINE_STR).getBytes());
mOutputStream
.write(getContentDispositionBytes(paramName, fileName));
mOutputStream.write(encodingBytes);
mOutputStream.write(rawData);
mOutputStream.write(NEW_LINE_STR.getBytes());
} catch (final IOException e) {
e.printStackTrace();
}
}
/**
* 添加二進制參數, 例如Bitmap的字節流參數
*
* @param key
* @param rawData
*/
public void addBinaryPart(String paramName, final byte[] rawData) {
writeToOutputStream(paramName, rawData, TYPE_OCTET_STREAM, BINARY_ENCODING, "no-file");
}
/**
* 添加文件參數,可以實現文件上傳功能
*
* @param key
* @param file
*/
public void addFilePart(final String key, final File file) {
InputStream fin = null;
try {
fin = new FileInputStream(file);
writeFirstBoundary();
final String type = CONTENT_TYPE + TYPE_OCTET_STREAM + NEW_LINE_STR;
mOutputStream.write(getContentDispositionBytes(key, file.getName()));
mOutputStream.write(type.getBytes());
mOutputStream.write(BINARY_ENCODING);
final byte[] tmp = new byte[4096];
int len = 0;
while ((len = fin.read(tmp)) != -1) {
mOutputStream.write(tmp, 0, len);
}
mOutputStream.flush();
} catch (final IOException e) {
e.printStackTrace();
} finally {
closeSilently(fin);
}
}
private void closeSilently(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
private byte[] getContentDispositionBytes(String paramName, String fileName) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(CONTENT_DISPOSITION + "form-data; name=\"" + paramName + "\"");
// 文本參數沒有filename參數,設置為空即可
if (!TextUtils.isEmpty(fileName)) {
stringBuilder.append("; filename=\""
+ fileName + "\"");
}
return stringBuilder.append(NEW_LINE_STR).toString().getBytes();
}
@Override
public long getContentLength() {
return mOutputStream.toByteArray().length;
}
@Override
public Header getContentType() {
return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + mBoundary);
}
@Override
public boolean isChunked() {
return false;
}
@Override
public boolean isRepeatable() {
return false;
}
@Override
public boolean isStreaming() {
return false;
}
@Override
public void writeTo(final OutputStream outstream) throws IOException {
// 參數最末尾的結束符
final String endString = "--" + mBoundary + "--\r\n";
// 寫入結束符
mOutputStream.write(endString.getBytes());
//
outstream.write(mOutputStream.toByteArray());
}
@Override
public Header getContentEncoding() {
return null;
}
@Override
public void consumeContent() throws IOException,
UnsupportedOperationException {
if (isStreaming()) {
throw new UnsupportedOperationException(
"Streaming entity does not implement #consumeContent()");
}
}
@Override
public InputStream getContent() {
return new ByteArrayInputStream(mOutputStream.toByteArray());
}
}writeTo(final OutputStream outstream)方法將所有參數的字節流數據寫入到與服務器建立的TCP連接的輸出流中,這樣就將我們的參數傳遞給服務器了。當然在此之前,我們需要按照格式來向ByteArrayOutputStream對象中寫數據。
MultipartEntity multipartEntity = new MultipartEntity();
// 文本參數
multipartEntity.addStringPart("type", "我的文本參數");
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
// 二進制參數
multipartEntity.addBinaryPart("images", bitmapToBytes(bmp));
// 文件參數
multipartEntity.addFilePart("images", new File("storage/emulated/0/test.jpg"));
// POST請求
HttpPost post = new HttpPost("url") ;
// 將multipartEntity設置給post
post.setEntity(multipartEntity);
// 使用http client來執行請求
HttpClient httpClient = new DefaultHttpClient() ;
httpClient.execute(post) ;POST /api/feed/ HTTP/1.1 Content-Type: multipart/form-data; boundary=o3Fhj53z-oKToduAElfBaNU4pZhp4- User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.4; M040 Build/KTU84P) Host: www.myhost.com Connection: Keep-Alive Accept-Encoding: gzip Content-Length: 168518 --o3Fhj53z-oKToduAElfBaNU4pZhp4- Content-Type: text/plain; charset=UTF-8 Content-Disposition: form-data; name="type" Content-Transfer-Encoding: 8bit This my type --o3Fhj53z-oKToduAElfBaNU4pZhp4- Content-Type: application/octet-stream Content-Disposition: form-data; name="images"; filename="no-file" Content-Transfer-Encoding: binary 這裡是bitmap的二進制數據 --o3Fhj53z-oKToduAElfBaNU4pZhp4- Content-Type: application/octet-stream Content-Disposition: form-data; name="file"; filename="storage/emulated/0/test.jpg" Content-Transfer-Encoding: binary 這裡是圖片文件的二進制數據 --o3Fhj53z-oKToduAElfBaNU4pZhp4---
/** * MultipartRequest,返回的結果是String格式的 * @author mrsimple */ public class MultipartRequest extends Request{ MultipartEntity mMultiPartEntity = new MultipartEntity(); public MultipartRequest(HttpMethod method, String url, Map params, RequestListener listener) { super(method, url, params, listener); } /** * @return */ public MultipartEntity getMultiPartEntity() { return mMultiPartEntity; } @Override public String getBodyContentType() { return mMultiPartEntity.getContentType().getValue(); } @Override public byte[] getBody() { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { // 將mMultiPartEntity中的參數寫入到bos中 mMultiPartEntity.writeTo(bos); } catch (IOException e) { Log.e("", "IOException writing to ByteArrayOutputStream"); } return bos.toByteArray(); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } @Override protected Response 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)); } }
MultipartRequest multipartRequest = new MultipartRequest(HttpMethod.POST,
"http://服務器地址",
null, new RequestListener() {
@Override
public void onStart() {
// TODO Auto-generated method stub
}
@Override
public void onComplete(int stCode, String response, String errMsg) {
}
});
// 獲取MultipartEntity對象
MultipartEntity multipartEntity = multipartRequest.getMultiPartEntity();
multipartEntity.addStringPart("content", "hello");
//
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
// bitmap參數
multipartEntity.addBinaryPart("images", bitmapToBytes(bitmap));
// 文件參數
multipartEntity.addFilePart("images", new File("storage/emulated/0/test.jpg"));
// 構建請求隊列
RequestQueue queue = RequestQueue.newRequestQueue(Context);
// 將請求添加到隊列中
queue.addRequest(multipartRequest); 
如何用Sencha Touch打包Android的APK
什麼是Sencha Touch前不久基於JavaScript編寫的Ajax框架ExtJS,將現有的ExtJS整合JQTouch、Rapha?l庫,推出適用於最前沿Touc
Android官方MVP架構解讀
綜述對於MVP (Model View Presenter)架構是從著名的MVC(Model View Controller)架構演變而來的。而對於Android應用的開
Android ListView構建支持單選和多選的投票項目
引言我們在android的APP開發中有時候會碰到提供一個選項列表供用戶選擇的需求,如在投票類型的項目中,我們提供一些主題給用戶選擇,每個主題有若干選項,用戶對這些主題的
Android設計模式之一個例子讓你徹底明白裝飾者模式(Decorator Pattern)
導讀這篇文章中我不會使用概念性文字來說明裝飾者模式,因為通常概念性的問題都很抽象,很難懂,使得讀者很難明白到底為什麼要使用這種設計模式,我們設計模式的誕生,肯定是前輩們在