Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android網絡框架設計

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 implements Comparable> {

    //默認的編碼格式
    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 mRequestListener;
    //請求的URL
    private String mUrl = "";
    //請求的方法
    HttpMethod mHttpMethod = HttpMethod.GET;
    //請求的header
    private Map mHeader = new HashMap<>();
    //請求參數
    private Map mBodyParams = new HashMap<>();

    public Request(HttpMethod httpMethod, String url, RequestListener 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 params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

    //將參數轉換為 URL編碼的參數串,格式key1=value&key2=value2
    private byte[] encodeParameters(Map params, String paramsEncoding) {
        StringBuilder encodeParams = new StringBuilder();
        try {
            for (Map.Entry 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 another) {
        Priority myPriority = this.getPriority();
        Priority anotherPriority = another.getPriority();
        return myPriority.equals(anotherPriority)
                ? this.getSerialNumber() - another.getSerialNumber()
                : myPriority.ordinal() - anotherPriority.ordinal();
    }

    public Map 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 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 {
        //請求完成回調
        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 {
    public StringRequest(HttpMethod httpMethod, String url, RequestListener 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 {

    public JsonRequest(HttpMethod httpMethod, String url, RequestListener 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> 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> mRequestQueue;
    //網絡請求棧
    private HttpStack mHttpStack;
    //結果分發器,將結果投遞到主線程
    private static ResponseDelivery mResponseDelivery = new ResponseDelivery();
    //請求緩存
    private static Cache mReqCache = new Cache.LruMemCache();

    //是否停止
    private boolean isStop = false;

    public NetworkExecutor(BlockingQueue> 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 {

    V get(K key);

    void put(K key,V value);

    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
    class LruMemCache implements Cache {
        private static final String TAG = "LruMemCache";
        private LruCache cache = new LruCache((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> 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 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() {
                    @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 {
    MultipartEntity multipartEntity = new MultipartEntity();


    public MultipartRequest(HttpMethod httpMethod, String url, RequestListener 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() {
            @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 請求");
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved