一、构建思路
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");
Part part = request.getPart("imgFile");
//格式如:form-data; name="upload"; filename="YNote.exe"
String disposition = part.getHeader("content-disposition");
System.out.println(disposition);
String fileName = disposition.substring(disposition.lastIndexOf("=") + 2, disposition.length() - 1);
String fileType = part.getContentType();
long fileSize = part.getSize();
System.out.println("fileName: " + fileName);
System.out.println("fileType: " + fileType);
System.out.println("fileSize: " + fileSize);
// String uploadPath = request.getServletContext().getRealPath("/upload");
// System.out.println("uploadPath" + uploadPath);
// part.write(uploadPath + File.separator + fileName);
FileOutputStream fos =null;
InputStream inputStream = null;
inputStream = part.getInputStream();
File targetFile = new File("E:/test/upload", fileName);
if (!targetFile.getParentFile().exists()){
targetFile.getParentFile().mkdirs();
}
fos = new FileOutputStream(targetFile);
int len ;
byte[] bytes = new byte[8096];
while ((len= inputStream.read(bytes))!=-1){
fos.write(bytes,0,len);
}
fos.flush();
fos.close();
inputStream.close();
}
protected void doGet(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, IOException {
System.out.print("Get 请求");
}
}