1. 程式人生 > >Android網路框架設計

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");