Android網路框架設計
一、構建思路
1、構建一個Request用來封裝 HTTP請求的型別、請求頭引數、請求體、優先順序、返回型別、等一些必要的屬性。 這個Request定義為抽象的,使得使用者可以擴充套件。
2、構建一個佇列(BlockingQueue) 用來存貯這些請求,使用者可以自己將請求新增到這個佇列中
3、建立多個執行緒NetworkExecutor,用來遍歷佇列(BlockingQueue)獲得Request,請求傳遞給 一個專門用來發送HTTP請求的類HttpStack
4、建立 HttpStack的實現類用來發送Http請求
5、HttpStack將請求結果分裝傳遞返回給NetworkExecutor,然後NetworkExecute呼叫ResponseDelivery.deliveryResponse(Response response) 將結果 轉換到UI執行緒,
最終將結果告知使用者。
二、實戰
1、構建Request
建立一個Request < T > 其中T 引數為返回的型別,可以為String,Json物件 或者xml物件,可由使用者擴充套件設定。
package com.blueberry.sample.module.http;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
/**
* Created by blueberry on 2016/8/16.
* 網路請求類,注意GET和DELETE不能傳遞請求引數,因為其請求的性質所致,使用者可以將引數構建到URL後傳進到Request
* 中。
*/
public abstract class Request<T> implements Comparable<Request<T>> {
//預設的編碼格式
private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
public static final String HEADER_CONTENT_TYPE = "Content-Type";
//請求序列紅啊
protected int mSerialNum = 0;
//優先順序預設為NORMAL
protected Priority mPriority = Priority.NORMAL;
//是否取消該請求
protected boolean isCancel = false;
//請求是否應該快取
private boolean mShouldCache = true;
//請求Listener
protected RequestListener<T> mRequestListener;
//請求的URL
private String mUrl = "";
//請求的方法
HttpMethod mHttpMethod = HttpMethod.GET;
//請求的header
private Map<String, String> mHeader = new HashMap<>();
//請求引數
private Map<String, String> mBodyParams = new HashMap<>();
public Request(HttpMethod httpMethod, String url, RequestListener<T> listener) {
mHttpMethod = httpMethod;
mUrl = url;
mRequestListener = listener;
}
//從原生的網路請求中解析結果,子類必須覆寫
public abstract T parseResponse(Response response);
//處理Response ,該方法需要執行在UI執行緒
public final void deliveryResponse(Response response) {
//解析得到請求結果
T result = parseResponse(response);
if (mRequestListener != null) {
int stCode = response != null ? response.getStatusCode() : -1;
String msg = response != null ? response.getMessage() : "unknown error";
mRequestListener.onComplete(stCode, result, msg);
}
}
protected String getParamsEncoding() {
return DEFAULT_PARAMS_ENCODING;
}
public String getBodyContentType() {
return "application/x-www-form-urlencoded;charset=" + getParamsEncoding();
}
//返回POST或者PUT請求時的Body引數位元組陣列
public byte[] getBody() {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
//將引數轉換為 URL編碼的引數串,格式key1=value&key2=value2
private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
StringBuilder encodeParams = new StringBuilder();
try {
for (Map.Entry<String, String> entry : params.entrySet()) {
encodeParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
encodeParams.append('=');
encodeParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
encodeParams.append('&');
}
return encodeParams.toString().getBytes(paramsEncoding);
} catch (Exception e) {
throw new RuntimeException("Encoding not supported: " + paramsEncoding, e);
}
}
//用於物件的排序處理,根據優先順序加入到佇列的序號進行排序
@Override
public int compareTo(Request<T> another) {
Priority myPriority = this.getPriority();
Priority anotherPriority = another.getPriority();
return myPriority.equals(anotherPriority)
? this.getSerialNumber() - another.getSerialNumber()
: myPriority.ordinal() - anotherPriority.ordinal();
}
public Map<String, String> getParams() {
return mBodyParams;
}
public Priority getPriority() {
return mPriority;
}
public int getSerialNumber() {
return mSerialNum;
}
public void setSerialNumber(int serialNumber) {
this.mSerialNum = serialNumber;
}
public void setShouldCache(boolean shouldCache) {
this.mShouldCache = shouldCache;
}
public String getUrl() {
return mUrl;
}
public boolean shouldCache(){
return mShouldCache;
}
public Map<String, String> getHeaders() {
return mHeader;
}
public HttpMethod getHttpMethod() {
return mHttpMethod;
}
public static enum HttpMethod {
GET("GET"), POST("POST"), PUT("PUT"), DELETE("DELETE");
private String mHttpMethod = "";
private HttpMethod(String method) {
this.mHttpMethod = method;
}
@Override
public String toString() {
return mHttpMethod;
}
}
public static enum Priority {
LOW, NORMAL, HIGH, IMMEDIATE
}
public static interface RequestListener<T> {
//請求完成回撥
public void onComplete(int stCode, T response, String errMsg);
}
}
它的實現:
package com.blueberry.sample.module.http;
/**
* Created by blueberry on 2016/8/16.
*/
public class StringRequest extends Request<String> {
public StringRequest(HttpMethod httpMethod, String url, RequestListener<String> listener) {
super(httpMethod, url, listener);
}
@Override
public String parseResponse(Response response) {
return new String(response.getRawData());
}
}
或者:
package com.blueberry.sample.module.http;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by blueberry on 2016/8/16.
*/
public class JsonRequest extends Request<JSONObject> {
public JsonRequest(HttpMethod httpMethod, String url, RequestListener<JSONObject> listener) {
super(httpMethod, url, listener);
}
@Override
public JSONObject parseResponse(Response response) {
String jsonString = new String(response.getRawData());
try {
return new JSONObject(jsonString);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
}
即重寫parseResponse 將或返回的資料轉化成對應的型別。
2、構建請求佇列
package com.blueberry.sample.module.http;
import android.util.Log;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by blueberry on 2016/8/16.
*/
public final class RequestQueue {
private static final String TAG = "RequestQueue";
//執行緒安全的請求佇列
private BlockingQueue<Request<?>> mRequestQueue = new PriorityBlockingQueue<>();
//請求的序列化生成器
private AtomicInteger mSerialNumGenerator = new AtomicInteger(0);
//預設的核心數 為CPU 個數+1
public static int DEFAULT_CORE_NUMS = Runtime.getRuntime().availableProcessors()+1;
// cpu核心數+1分發執行緒
private int mDispatchNums = DEFAULT_CORE_NUMS;
//NetworkExecutor[],執行網路請求的執行緒
private NetworkExecutor[] mDispatchers =null;
// Http 請求的真正執行者
private HttpStack mHttpStack;
protected RequestQueue(int coreNums,HttpStack httpStack){
mDispatchNums = coreNums;
mHttpStack = httpStack!=null?httpStack:HttpStackFactory.createHttpStack();
}
// 啟動NetworkExecutor
private final void startNetworkExecutors(){
mDispatchers = new NetworkExecutor[mDispatchNums];
for (int i = 0; i < mDispatchNums; i++) {
mDispatchers[i] = new NetworkExecutor(mRequestQueue,mHttpStack);
mDispatchers[i].start();
}
}
public void start(){
stop();
startNetworkExecutors();
}
/**
* 停止NetworkExecutor
*/
private void stop() {
if(mDispatchers!=null && mDispatchers.length>0){
for (int i = 0; i < mDispatchers.length; i++) {
mDispatchers[i].quit();
}
}
}
public void addRequest(Request<?> request){
if(!mRequestQueue.contains(request)){
//為請求設定序列號
request.setSerialNumber(this.generateSerialNumber());
mRequestQueue.add(request);
}else{
Log.d(TAG,"請求佇列中已經含有了") ;
}
}
private int generateSerialNumber() {
return mSerialNumGenerator.incrementAndGet();
}
}
其中NetworkExecutor 是一個執行緒,佇列開始時會有 cpu核數+1 個執行緒請求BlockingQueue< Request >這個佇列
3、NetworkExecutor
package com.blueberry.sample.module.http;
import android.util.Log;
import java.util.concurrent.BlockingQueue;
/**
* Created by blueberry on 2016/8/16.
*/
public class NetworkExecutor extends Thread {
private static final String TAG = "NetworkExecutor";
//網路請求佇列
private BlockingQueue<Request<?>> mRequestQueue;
//網路請求棧
private HttpStack mHttpStack;
//結果分發器,將結果投遞到主執行緒
private static ResponseDelivery mResponseDelivery = new ResponseDelivery();
//請求快取
private static Cache<String, Response> mReqCache = new Cache.LruMemCache<String, Response>();
//是否停止
private boolean isStop = false;
public NetworkExecutor(BlockingQueue<Request<?>> mRequestQueue, HttpStack mHttpStack) {
this.mRequestQueue = mRequestQueue;
this.mHttpStack = mHttpStack;
}
@Override
public void run() {
try {
while (!isStop) {
final Request<?> request = mRequestQueue.take();
if (request.isCancel) {
continue;
}
Response response = null;
if (isUseCache(request)) {
//從快取中讀取
response = mReqCache.get(request.getUrl());
} else {
// 從網路上獲取資料
response = mHttpStack.performRequest(request);
//如果該請求需要快取,那麼請求成功快取到mResponseCache中
if (request.shouldCache() && isSuccess(response)) {
mReqCache.put(request.getUrl(), response);
}
}
mResponseDelivery.deliveryResponse(request, response);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private boolean isSuccess(Response response) {
return response != null && response.getStatusCode() == 200;
}
private boolean isUseCache(Request<?> request) {
return request.shouldCache() && mReqCache.get(request.getUrl()) != null;
}
public void quit() {
isStop =true;
}
}
這裡加了一個快取,如果使用快取存在,就讀取快取中的,負責使用mHttpStack.performRequest(request);請求資料,然後通過 mResponseDelivery.deliveryResponse(request, response);將請求結果切換到主執行緒
4、Cache
package com.blueberry.sample.module.http;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.Log;
import android.util.LruCache;
/**
* Created by blueberry on 2016/8/16.
*/
public interface Cache<K, V extends Response> {
V get(K key);
void put(K key,V value);
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
class LruMemCache<K, V extends Response> implements Cache<K, V> {
private static final String TAG = "LruMemCache";
private LruCache<K, V> cache = new LruCache<K, V>((int) (Runtime.getRuntime()
.freeMemory() / 10)) {
@Override
protected int sizeOf(K key, V value) {
return value.getRawData().length;
}
};
@Override
public V get(K key) {
Log.i(TAG, "get: ");
return cache.get(key);
}
@Override
public void put(K key, V value) {
cache.put(key,value);
}
}
}
5、HttpStack
public interface HttpStack {
Response performRequest(Request<?> request);
}
它有2個實現 HttpClientStack和HttpUrlStack,其中在android2.3 之後使用HttpUrlStack。
因為 HttpClientStack使用 apache 的HttpClient ,HttpUrlStack使用HttpUrlConnection,因為在2.3之後google推薦HttpUrlConnection,所以我們根據平臺的版本選擇合適的實現。
package com.blueberry.sample.module.http;
import android.os.Build;
/**
* Created by blueberry on 2016/8/16.
*/
public class HttpStackFactory {
private static final int GINGERBREAD_SDK_NUM =9;
public static HttpStack createHttpStack() {
int runtimeSDKApi = Build.VERSION.SDK_INT ;
if(runtimeSDKApi>=GINGERBREAD_SDK_NUM){
return new HttpUrlConnStack();
}
return new HttpClientStack();
}
}
package com.blueberry.sample.module.http;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Created by blueberry on 2016/8/16.
*/
public class HttpUrlConnStack implements HttpStack {
private Config mConfig = new Config.Builder().build();
@Override
public Response performRequest(Request<?> request) {
HttpURLConnection urlConnection = null;
//構建HttpURLConnection
try {
urlConnection = createUrlConnection(request.getUrl());
//設定headers
setRequestHeaders(urlConnection, request);
//設定Body引數
setRequestParams(urlConnection, request);
return fetchResponse(urlConnection);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private Response fetchResponse(HttpURLConnection urlConnection) throws IOException {
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = urlConnection.getResponseCode();
if (responseCode == -1) {
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
// 狀態行資料
StatusLine responseStatus = new BasicStatusLine(protocolVersion, responseCode, urlConnection
.getResponseMessage());
// 構建response.
Response response = new Response(responseStatus);
//設定 response資料
response.setEntity(entityFromURLConnection(urlConnection));
addHeaderToResponse(response, urlConnection);
return response;
}
private void addHeaderToResponse(Response response, HttpURLConnection urlConnection) {
for (Map.Entry<String, List<String>> header : urlConnection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
}
private HttpEntity entityFromURLConnection(HttpURLConnection urlConnection) {
BasicHttpEntity entity = new BasicHttpEntity();
InputStream inputStream = null;
try {
inputStream = urlConnection.getInputStream();
} catch (IOException e) {
e.printStackTrace();
inputStream = urlConnection.getErrorStream();
}
entity.setContent(inputStream);
entity.setContentLength(urlConnection.getContentLength());
entity.setContentEncoding(urlConnection.getContentEncoding());
entity.setContentType(urlConnection.getContentType());
return entity;
}
private void setRequestParams(HttpURLConnection urlConnection, Request<?> request)
throws IOException {
Request.HttpMethod method = request.getHttpMethod();
urlConnection.setRequestMethod(method.toString());
// add params
byte[] body = request.getBody();
if (body != null) {
urlConnection.setDoOutput(true);
// set content type
urlConnection.addRequestProperty(Request.HEADER_CONTENT_TYPE,
request.getBodyContentType());
// write params data to connection.
DataOutputStream dataOutputStream =
new DataOutputStream(urlConnection.getOutputStream());
dataOutputStream.write(body);
dataOutputStream.close();
}
}
private void setRequestHeaders(HttpURLConnection urlConnection, Request<?> request) {
Set<String> headersKeys = request.getHeaders().keySet();
for (String headerName : headersKeys) {
urlConnection.addRequestProperty(headerName, request.getHeaders().get(headerName));
}
}
private HttpURLConnection createUrlConnection(String url) throws IOException {
URL newURL = new URL(url);
URLConnection urlConnection = newURL.openConnection();
urlConnection.setConnectTimeout(mConfig.connTimeOut);
urlConnection.setReadTimeout(mConfig.soTimeOut);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(true);
return (HttpURLConnection) urlConnection;
}
}
6、將結果切換到主執行緒。
package com.blueberry.sample.module.http;
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Executor;
/**
* Created by blueberry on 2016/8/16.
* 請求結果投遞類,將請求結果投遞給UI執行緒
*/
public class ResponseDelivery implements Executor{
/*關聯主執行緒訊息佇列的handler*/
Handler mResponseHandler = new Handler(Looper.getMainLooper());
public void deliveryResponse(final Request<?> request, final Response response) {
Runnable respRunnable = new Runnable() {
@Override
public void run() {
request.deliveryResponse(response);
}
};
execute(respRunnable);
}
@Override
public void execute(Runnable command) {
mResponseHandler.post(command);
}
}
最終將結果使用Request的工具方法deliveryResponse 轉化對應的實現,通知監聽器:
//處理Response ,該方法需要執行在UI執行緒
public final void deliveryResponse(Response response) {
//解析得到請求結果
T result = parseResponse(response);
if (mRequestListener != null) {
int stCode = response != null ? response.getStatusCode() : -1;
String msg = response != null ? response.getMessage() : "unknown error";
mRequestListener.onComplete(stCode, result, msg);
}
}
8、Response
package com.blueberry.sample.module.http;
import org.apache.http.HttpEntity;
import org.apache.http.ProtocolVersion;
import org.apache.http.ReasonPhraseCatalog;
import org.apache.http.StatusLine;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.Locale;
/**
* Created by blueberry on 2016/8/16.
*/
public class Response extends BasicHttpResponse {
//原始的response 主體資料
public byte[] rawData = new byte[0];
public Response(StatusLine statusline, ReasonPhraseCatalog catalog, Locale locale) {
super(statusline, catalog, locale);
}
public Response(StatusLine statusline) {
super(statusline);
}
public Response(ProtocolVersion ver, int code, String reason) {
super(ver, code, reason);
}
public void setEntity(HttpEntity entity){
super.setEntity(entity);
rawData = entityToBytes(getEntity());
}
private byte[] entityToBytes(HttpEntity entity) {
try {
return EntityUtils.toByteArray(entity);
} catch (IOException e) {
e.printStackTrace();
return new byte[0];
}
}
public byte[] getRawData() {
return rawData;
}
public String getMessage() {
return getReason(getStatusLine().getStatusCode());
}
public int getStatusCode() {
return getStatusLine().getStatusCode();
}
}
9、呼叫程式
private void setGetRequest() {
StringRequest stringRequest = new StringRequest(Request.HttpMethod.GET,
"http://www.baidu.com",
new Request.RequestListener<String>() {
@Override
public void onComplete(int stCode, String response, String errMsg) {
Logger.i("code = %d, response= %s, errMsg= %s", stCode, response, errMsg);
}
});
stringRequest.setShouldCache(true);
RequestQueue mQueue = NetManager.newRequestQueue();
mQueue.addRequest(stringRequest);
mQueue.start();
}
11、上傳檔案
我們構建一個上傳檔案的Request
package com.blueberry.sample.module.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* Created by blueberry on 2016/8/16.
*/
public class MultipartRequest extends Request<String> {
MultipartEntity multipartEntity = new MultipartEntity();
public MultipartRequest(HttpMethod httpMethod, String url, RequestListener<String> listener) {
super(HttpMethod.POST, url, listener);
}
public MultipartEntity getMultipartEntity() {
return multipartEntity;
}
@Override
public String parseResponse(Response response) {
if(response!=null && response.getRawData()!=null)
return new String (response.getRawData());
return null;
}
@Override
public byte[] getBody() {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
multipartEntity.writeTo(bos);
} catch (IOException e) {
e.printStackTrace();
}
return bos.toByteArray();
}
@Override
public String getBodyContentType() {
return multipartEntity.getContentType().getValue();
}
}
MultipartEntity的實現:
package com.blueberry.sample.module.http;
import android.text.TextUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Random;
/**
* Created by blueberry on 2016/8/16.
*
* 最終生成的報文格式大致如下:
*
* POST /api/ HTTP/1.1
* Content-Type: multipart/form-data; boundary=03fdafareqjk2-5542jkfda
* User-Agent:Dalvik/1.6.0 (Linux;U anroid 4.4.4;M040 Build/KTU84P)
* Host: www.myhost.com
* Connection: Keep:Alive
* Accept-Encoding: gzip
* Content-Length:168324
*
* --03fdafareqjk2-5542jkfda
* Content-Type: text/plain;charset=UTF-8
* Content-Disposition: form-data;name="type"
* Content-Transfer-Encoding: 8bit
*
* This is my type
*
* --03fdafareqjk2-5542jkfda
* Content-Type: application/octet-stream
* Content-Disposition: form-data; name="image";filename="no-file"
* Content-Transfer-Encoding:binary
*
* --03fdafareqjk2-5542jkfda
* Content-Type: application/octet-stream
* Content-Disposition: form-data; name="file";filename="image.jpg"
* Content-Transfer-Encoding:binary
*
* --03fdafareqjk2-5542jkfda--
*/
public class MultipartEntity implements HttpEntity {
private final static char[] MULTIPART_CHARS = ("-123456789abcdefghihkl" +
"mnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ").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();
}
private 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();
}
// 引數開頭的分割符
private void writeFirstBoundary() throws IOException {
mOutputStream.write(("--" + mBoundary + "\r\n").getBytes());
}
// 新增文字引數
public void addStringPart(final String paramName, final String value) {
writeToOutputString(paramName, value.getBytes(), TYPE_TEXT_CHARSET, BIT_ENCODING, "");
}
/**
* 新增位元組陣列引數,例如Bitmap的位元組流引數
* @param paramsName 引數名
* @param rawData 位元組陣列資料
*/
public void addByteArrayPart(String paramsName,final byte[] rawData){
writeToOutputString(paramsName,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(type.getBytes());
mOutputStream.write(getContentDispositionBytes(key,file.getName()));
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 (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
closeSilently(fin);
}
}
private void closeSilently(InputStream fin) {
if(fin!=null) try {
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 將資料寫出到輸出流中
*
* @param key 引數名
* @param rawData 原始的位元組資料
* @param type 型別
* @param encodingBytes 編碼型別
* @param fileName 檔名
*/
private void writeToOutputString(String key, byte[] rawData, String type,
byte[] encodingBytes, String fileName) {
try {
writeFirstBoundary();
mOutputStream.write(getContentDispositionBytes(key, fileName));
mOutputStream.write((CONTENT_TYPE + type + NEW_LINE_STR).getBytes());
mOutputStream.write(encodingBytes);
mOutputStream.write(rawData);
mOutputStream.write(NEW_LINE_STR.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
private byte[] getContentDispositionBytes(String key, String fileName) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(CONTENT_DISPOSITION + "form-data;name=\"" + key + "\"");
// 文字引數沒有filename引數 ,設定為空即可
if (!TextUtils.isEmpty(fileName)) {
stringBuilder.append("; filename=\"" + fileName + "\"");
}
return stringBuilder.append(NEW_LINE_STR).toString().getBytes();
}
@Override
public boolean isRepeatable() {
return false;
}
@Override
public boolean isChunked() {
return false;
}
@Override
public long getContentLength() {
return mOutputStream.toByteArray().length;
}
@Override
public Header getContentType() {
return new BasicHeader("Content-Type","multipart/form-data; boundary="+mBoundary);
}
@Override
public Header getContentEncoding() {
return null;
}
@Override
public InputStream getContent() throws IOException, IllegalStateException {
return new ByteArrayInputStream(mOutputStream.toByteArray());
}
@Override
public void writeTo(OutputStream outputStream) throws IOException {
final String endString = "\r\n--"+mBoundary+"--\r\n";
//寫入結束符
// mOutputStream.write(endString.getBytes());
// 將快取在mOutputStream 中的資料全部寫入到outputStream中
outputStream.write(mOutputStream.toByteArray());
outputStream.write(endString.getBytes());
outputStream.flush();
}
@Override
public boolean isStreaming() {
return false;
}
@Override
public void consumeContent() throws IOException {
if(isStreaming()){
throw new UnsupportedEncodingException("Streaming" +
" entity dose not implement #consumeContent()");
}
}
}
上傳檔案類似於html中的表單提交,(請求頭為 Content-Type: form-data)
最終生成的請求報文中 每個part都使用一個 boundary來分割
上傳檔案呼叫程式:
RequestQueue mQueue = NetManager.newRequestQueue();
MultipartRequest multipartRequest = new MultipartRequest(Request.HttpMethod.POST,
"http://192.168.10.142:8080/WebTest/hello", new Request.RequestListener<String>() {
@Override
public void onComplete(int stCode, String response, String errMsg) {
Logger.i("code = %d, response= %s, errMsg= %s", stCode, response, errMsg);
}
});
multipartRequest.setShouldCache(false);
MultipartEntity multipartEntity = multipartRequest.getMultipartEntity();
multipartEntity.addFilePart("imgFile",new File(getExternalCacheDir().getPath(),"test.jpg"));
// 4.將請求新增到佇列中
mQueue.addRequest(multipartRequest);
mQueue.start();
12、服務端接收
package com.blueberry.example;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Part;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by Administrator on 2016/8/16.
*/
@MultipartConfig
@WebServlet(name = "MultipartServlet", urlPatterns = "/hello")
public class MultipartServlet extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, IOException {
System.out.print("post 請求");
request.setCharacterEncoding("UTF-8");