Android网络框架设计

本文介绍了一种基于Android平台的高效网络请求框架的设计与实现,包括请求类Request、请求队列RequestQueue、线程NetworkExecutor、缓存机制Cache、HTTP请求处理HttpStack等组件,并详细解释了各部分的功能及交互流程。
摘要由CSDN通过智能技术生成

一、构建思路
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 请求");
    }
}

源码:http://download.csdn.net/detail/a992036795/9605642

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值