美文网首页http系列
Volley 之 Request篇

Volley 之 Request篇

作者: 大海孤了岛 | 来源:发表于2017-06-20 12:26 被阅读88次

    与Request相关的类

    • Request:实现一个请求的基类
    • HttpHeaderParse:实现对响应头的解析
    • StringRequest:实现String类型的请求类
    • JsonRequest:实现Json类型的请求基类
    • JsonObjectRequest:实现JsonObject类型的请求类
    • JsonArrayRequest:实现JsonArray类型的请求类

    1.Request类的实现

    • 常用变量的定义
    //定义默认的字符编码
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
    //定义请求的方式
    public interface Method {
        int DEPRECATED_GET_OR_POST = -1;
        int GET = 0;
        int POST = 1;
        int PUT = 2;
        int DELETE = 3;
        int HEAD = 4;
        int OPTIONS = 5;
        int TRACE = 6;
        int PATCH = 7;
    }
    
    //定义一个日志事件,用于记录请求
    private final VolleyLog.MarkerLog mEventLog = VolleyLog.MarkerLog.ENABLED ? new VolleyLog.MarkerLog() : null;
    
    private final int mMethod;
    
    private final String mUrl;
    //流量统计的默认标志
    private final int mDefaultTrafficStatsTag;
    
    /** Listener interface for errors. */
    private final Response.ErrorListener mErrorListener;
    
    //请求的序列号,在优先级相同的情况下,用于保证FIFO
    private Integer mSequence;
    
    //请求队列
    private RequestQueue mRequestQueue;
    
    //是否需要缓存
    private boolean mShouldCache = true;
    
    //该请求是否存在缓存
    private boolean mCanceled = false;
    
    //该请求事件是否被分发
    private boolean mResponseDelivered = false;
    
    /** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
    private boolean mShouldRetryServerErrors = false;
    
    //重试策略
    private RetryPolicy mRetryPolicy;
    //缓存内容
    private Cache.Entry mCacheEntry = null;
    //标志该请求,用于批量取消
    private Object mTag;
    
    public Request(String url, Response.ErrorListener listener){
        this(Method.DEPRECATED_GET_OR_POST, url, listener);
    }
    
    public Request(int method, String url, Response.ErrorListener listener){
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
        setRetryPolicy(new DefaultRetryPolicy());
    }
    
    • 针对POST类型,对请求体数据的解析:
    //获取提交内容
    public byte[] getPostBody() throws AuthFailureError{
        Map<String,String> postParams = getParams();
        if (postParams != null && postParams.size() > 0){
            return encodeParameter(postParams, getPostParamsEncoding());
        }
        return null;
    }
    
    //将提交内容进行解析,返回为byte数组
    private byte[] encodeParameter(Map<String,String> params, String paramEncoding){
        StringBuilder encodedParams = new StringBuilder();
        try {
            for (Map.Entry<String,String> entry : params.entrySet()){
                encodedParams.append(URLEncoder.encode(entry.getKey(),paramEncoding));
                encodedParams.append("=");
                encodedParams.append(URLEncoder.encode(entry.getValue(), paramEncoding));
                encodedParams.append("&");
            }
            return encodedParams.toString().getBytes(paramEncoding);
        } catch (UnsupportedEncodingException uee){
            throw new RuntimeException("Encoding not supported: " + paramEncoding, uee);
        }
    }
    
    • 实现请求的优先级
    public enum Priority{
        LOW,
        NORMAL,
        HIGH,
        IMMEDIATE
    }
    
    //默认为普通优先级
    public Priority getPriority(){
        return Priority.NORMAL;
    }
    
    //定义请求的“大小”:优先级优先,若优先级相同,则比较序列数
    @Override
    public int compareTo(Request<T> o) {
        //获取其对应的优先级
        Priority left = this.getPriority();
        Priority right = o.getPriority();
    
        // High-priority requests are "lesser" so they are sorted to the front.
        // Equal priorities are sorted by sequence number to provide FIFO ordering.
       // 注意序列数和优先级的前后顺序
        return left == right ?
                this.mSequence - o.mSequence :
                right.ordinal() - left.ordinal();
    }
    

    要注意的是这里对优先级高的线程对应的数值为较小。
    关于ordinal函数的使用,若没有给对应的变量赋值,会默认第一个为0,并逐个递增。如下:

    enum Priority{
        LOW,
        NORMAL,
        HIGH
    }
    
    public static void main(String[] args){
        System.out.print(Priority.LOW.ordinal());
        System.out.print(Priority.NORMAL.ordinal());
        System.out.println(Priority.HIGH.ordinal());
    }
    输出结果:
    0 1 2 
    
    • 取消请求事件
    //通知请求队列将该请求终止
    void finish(final String tag){
        if (mRequestQueue != null){
            mRequestQueue.finish(this);
        }
    
        if (VolleyLog.MarkerLog.ENABLED){
            final long threadId = Thread.currentThread().getId();
            //使用主线程来记录该事件,保证事件的有序性
            if (Looper.getMainLooper() != Looper.myLooper()){
                Handler mainThread = new Handler(Looper.getMainLooper());
                mainThread.post(new Runnable() {
                    @Override
                    public void run() {
                        mEventLog.add(tag, threadId);
                        mEventLog.finish(this.toString());
                    }
                });
                return;
            }
            mEventLog.add(tag, threadId);
            mEventLog.finish(this.toString());
        }
    }
    

    实际上,这里重点是要学习日志记录线程时间时,采用主线程提交方式,以保证事件的有序性。否则,若交给其他线程处理,无法确保事件的先后顺序。

    2.HttpHeaderParse的实现

    • 获取响应头的字符编码
    Content-Type: application/x-www-form-urlencoded;charset=utf-8;
    

    具体实例如上,那么我们要做的就是获取到charset中对应的内容

    //获取响应头的字符编码
    public static String parseCharset(Map<String,String> headers, String defaultCharset){
        //获取到该行数据
        String contentType = headers.get("Content-Type");
        if (contentType != null){
            String[] params = contentType.split(";");
            for (int i = 0; i < params.length; i ++){
                //trim清除空白字符
                String[] pair = params[i].trim().split("=");
                if (pair.length == 2 && pair[0].equals("charset")){
                    return pair[1];
                }
            }
        }
        return defaultCharset;
    }
    
    public static String parseCharset(Map<String,String> headers){
        return parseCharset(headers, HTTP.DEFAULT_CONTENT_CHARSET);
    }
    
    • 对响应头进行解析:
    HTTP/1.1 200 OK
    Date: Mon, 27 Jul 2009 12:28:53 GMT
    Server: Apache
    Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
    ETag: "34aa387-d-1568eb00"
    Accept-Ranges: bytes
    Content-Length: 51
    Vary: Accept-Encoding
    Content-Type: text/plain
    Cache-Control: no-cache
    

    具体实例如上,我们要获取的信息主要是用于构造Entry对象

    //解析响应头,并返回一个Entry对象
    public static Cache.Entry parseCacheHeaders(NetworkResponse response){
        //获取当前时间
        long now = System.currentTimeMillis();
        Map<String,String> headers = response.headers;
    
        //服务器返回数据的时间
        long serverDate = 0;
        //上一次修改的时间
        long lastModified = 0;
        //服务器过期时间
        //三者之间的关系:第一个为服务器获取的事件,后两个对应为Entry对象中的softTtl和ttl
        long serverExpires = 0;
        long softExpire = 0;
        long finalExpire = 0;
        //缓存最大存活时间
        long maxAge = 0;
        //服务器对客户端重新验证的时间
        long staleWhileRevalidate = 0;
        //存在缓存信息
        boolean hasCacheControl = false;
        //必须马上重新验证
        boolean mustRevalidate = false;
    
        String serverETag = null;
        String headValue;
    
        headValue = headers.get("Date");
        if (headValue != null){
            serverDate = parseDateAsEpoch(headValue);
        }
    
        headValue = headers.get("Cache-Control");
        //获取服务器缓存有关信息
        if (headValue != null){
            hasCacheControl = true;
            String[] tokens = headValue.split(",");
            for (int i = 0; i < tokens.length; i ++){
                String token = tokens[i].trim();
                //不使用缓存,则直接返回null
                if (token.equals("no-cache") || token.equals("no-store")){
                    return null;
                } else if (token.startsWith("max-age=")){
                    try {
                        maxAge = Long.parseLong(token.substring(8));
                    } catch (Exception e){
    
                    }
                } else if (token.startsWith("stale-with-revalidate=")){
                    try {
                        staleWhileRevalidate = Long.parseLong(token.substring(23));
                    } catch (Exception e){
    
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")){
                    mustRevalidate = true;
                }
            }
        }
    
        headValue = headers.get("Expires");
        if (headValue != null){
            serverExpires = parseDateAsEpoch(headValue);
        }
    
        headValue = headers.get("last-Modified");
        if (headValue != null){
            lastModified = parseDateAsEpoch(headValue);
        }
    
        serverETag = headers.get("ETag");
    
        //计算获取Entry对象中的ttl和softTtl
        if (hasCacheControl){
            //注意:这里的单位为毫秒
            softExpire = now + maxAge * 1000;
            //若不需要立马重新验证,那么缓存还可以再存活一段时间
            finalExpire = mustRevalidate ?
                    softExpire : softExpire + staleWhileRevalidate * 1000;
        } else if (serverDate > 0 && serverExpires >= serverDate){
            softExpire = now + (serverExpires - serverDate);
            finalExpire = softExpire;
        }
        //返回Entry对象
        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.eTag = serverETag;
        entry.softTtl = softExpire;
        entry.ttl = finalExpire;
        entry.serverDate = serverDate;
        entry.lastModified = lastModified;
        entry.responseHeaders = headers;
    
        return entry;
    }
    
    //将时间格式化为Long类型
    private static long parseDateAsEpoch(String headValue) {
        try {
            return DateUtils.parseDate(headValue).getTime();
        } catch (DateParseException e){
            return 0;
        }
    }
    

    要注意的一点是对日期的处理,转换为Long类型保存

    3. 实现JsonRequest

    public abstract class JsonRequest<T> extends Request<T>{
        //定义默认字符编码
        protected static final String PROTOCOL_CHARSET = "utf-8";
        //定义默认请求类型
        private static final String PROTOCOL_CONTENT_TYPE =
                String.format("application/json;charset=%s",PROTOCOL_CHARSET);
    
        private final Response.Listener<T> mListener;
        //提交的请求体
        private final String mRequestBody;
    
        public JsonRequest(String url, String requestBody, Response.Listener<T> listener,
                           Response.ErrorListener errorListener){
            this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener);
        }
    
        public JsonRequest(int method, String url, String requestBody, Response.Listener<T> listener,
                           Response.ErrorListener errorListener) {
            super(method, url, errorListener);
            mListener = listener;
            mRequestBody = requestBody;
        }
    
        @Override
        protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
    
        @Override
        protected void deliverResponse(T response) {
            mListener.onResponse(response);
        }
    
        @Override
        public String getPostBodyContentType() {
            return getBodyContentType();
        }
    
        //重新定义了请求体的Content-Type
        @Override
        public String getBodyContentType() {
            return PROTOCOL_CONTENT_TYPE;
        }
    
        @Override
        public byte[] getPostBody() throws AuthFailureError {
            return getBody();
        }
    
        //若请求体不为空,则将请求体转换为byte数组返回
        @Override
        public byte[] getBody() throws AuthFailureError {
            try {
                return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
            } catch (UnsupportedEncodingException e){
                VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
                        mRequestBody, PROTOCOL_CHARSET);
                return null;
            }
        }
    }
    

    要注意的几点:

    1. 重新定义了其请求体的Content-Type类型,因为Request基类中定义的Content-Type并不包含json数据:
    public String getBodyContentType(){
        return "application/x-www-form-urlencoded;charset=" + getParamsEncoding();
    }
    
    1. 重新了请求体的getBody内容,保证是在Json定义下的字符编码下进行转换为byte数组类型

    4.实现JsonObjectRequest

    public class JsonObjectRequest extends JsonRequest<JSONObject> {
    
        //传入JsonObject数据
        public JsonObjectRequest(int method, String url, JSONObject jsonRequest, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) {
            super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(),
                    listener, errorListener);
        }
    
        //根据JsonObject数据判断请求方式
        public JsonObjectRequest(String url, JSONObject jsonRequest, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) {
            this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
                    listener, errorListener);
        }
    
        @Override
        protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
            try {
                //转换为String类型
                String jsonString = new String(response.data,
                        HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
                //返回为JsonObject类型
                return Response.success(new JSONObject(jsonString),
                        HttpHeaderParser.parseCacheHeaders(response));
            } catch (UnsupportedEncodingException e){
                return Response.error(new ParseError(e));
            } catch (JSONException je){
                return Response.error(new ParseError(je));
            }
        }
    }
    

    实际上,基于JsonRequest实现,所需添加的内容非常简单。不过要注意的是其构造函数的使用,根据传入的JSONObject类型参数判断是否为空,来决定其请求方式。

    5. 实现JsonArrayRequest

    public class JsonArrayRequest extends JsonRequest<JSONArray> {
    
        public JsonArrayRequest(String url,  Response.Listener<JSONArray> listener,
                                Response.ErrorListener errorListener){
            this(Method.GET, url, null, listener, errorListener);
        }
    
        public JsonArrayRequest(int method, String url, JSONArray jsonRequest, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener) {
            super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener);
        }
    
        @Override
        protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
            try {
                String jsonString = new String(response.data,
                        HttpHeaderParser.parseCharset(response.headers));
                //返回JsonArray类型的结果
                return Response.success(new JSONArray(jsonString),
                        HttpHeaderParser.parseCacheHeaders(response));
            } catch (UnsupportedEncodingException e) {
                return Response.error(new ParseError(e));
            } catch (JSONException je){
                return Response.error(new ParseError(je));
            }
        }
    }
    

    实际上,该实现和JsonObjectRequest类似,只不过就是在解析响应时,返回的数据类型不同。

    6. 实现StringRequest

    public class StringRequest extends Request<String> {
        private final Response.Listener<String> mListener;
    
        public StringRequest(String url, Response.Listener<String> listener,
                             Response.ErrorListener errorListener) {
            super(url, errorListener);
            mListener = listener;
        }
    
        public StringRequest(int method, String url, Response.Listener<String> listener,
                             Response.ErrorListener errorListener){
            super(method, url, errorListener);
            mListener = listener;
        }
    
        //对数据进行解析,并返回
        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            String parsed;
            try {
                parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            } catch (UnsupportedEncodingException e){
                parsed = new String(response.data);
            }
            return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response ));
        }
    
        @Override
        protected void deliverResponse(String response) {
            mListener.onResponse(response);
        }
    }
    

    实际上,就做了两步,一步是用listener去分发结果,一步是将响应解析为String类型

    7. 实现ImageRequest

    • 对ImageRequest中常见变量的定义
    //默认获取图片时间
    public static final int DEFAULT_IMAGE_TIMEOUT_MS = 1000;
    //默认尝试次数
    public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;
    
    public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;
    
    private final Response.Listener<Bitmap> mListener;
    //图片保存格式,比如ARGB_8888
    private final Bitmap.Config mDecodeConfig;
    //传入的所需图片的宽高
    private final int mMaxWidth;
    private final int mMaxHeight;
    //图片的缩放类型
    private final ImageView.ScaleType mScaleType;
    
    //图片请求的锁,保证图片在解析过程中的线程安全性,避免多个线程对图片进行解析
    private static final Object sDecodeLock = new Object();
    
    public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
                        ImageView.ScaleType scaleType, Bitmap.Config decodeConfig, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(new DefaultRetryPolicy(DEFAULT_IMAGE_TIMEOUT_MS,DEFAULT_IMAGE_MAX_RETRIES
                , DEFAULT_IMAGE_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxHeight = maxHeight;
        mMaxWidth = maxWidth;
        mScaleType = scaleType;
    }
    
    public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
                        Bitmap.Config decodeConfig, Response.ErrorListener errorListener) {
        this(url, listener, maxWidth, maxHeight, ImageView.ScaleType.CENTER_INSIDE,
                decodeConfig, errorListener);
    }
    
    • 解析响应,并返回Bitmap对象
    @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
    //保证单一线程进行解析图片
    synchronized (sDecodeLock){
        try {
            return doParse(response);
        } catch (OutOfMemoryError e){
            VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length);
            return Response.error(new ParseError(e));
        }
    }
    

    我们可以看到解析过程是保证单线程进行的,防止同一张图进行多次解析,带来的不必要的浪费。

    //对图片进行解析
    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        //若图片所需的显示宽高设置都为0,则显示原图即可
        if (mMaxWidth == 0 && mMaxHeight == 0){
            //设置图片格式
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            //保证只解析图片的宽高
            decodeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            //获取到原图的宽高
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;
    
            //获取图片预期的宽高
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth,
                    actualHeight, mScaleType);
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight,
                    actualWidth, mScaleType);
    
            //对图片进行解析,并转换为Bitmap对象
            decodeOptions.inJustDecodeBounds = false;
            //获取图片的缩放比
            decodeOptions.inSampleSize = findBestSimpleSize(actualWidth, actualHeight,
                    desiredWidth, desiredHeight);
            //???是否需要添加
            decodeOptions.inPreferredConfig = mDecodeConfig;
            //创建一个临时的Bitmap对象
            Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length,
                    decodeOptions);
            //若该对象超过预期的宽高,则需要进一步缩小
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)){
                bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
                //回收临时Bitmap对象
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }
        //返回解析结果
        if (bitmap == null){
            return Response.error(new ParseError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }
    

    如上为图片的解析过程,主要流程为:
    获取图片预期的宽高 —— 获取图片的缩放比 —— 获取到对应的Bitmap对象

    获取图片预期的宽高

    //获取预期宽高
    private int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                                    int actualSecondary, ImageView.ScaleType scaleType) {
        //无需处理,直接返回原图尺寸
        if (maxPrimary == 0 && maxSecondary == 0){
            return actualPrimary;
        }
    
        //FIT_XY:不需要进行等比缩放,但要求图片铺满矩阵
        if (scaleType == ImageView.ScaleType.FIT_XY){
            if (maxPrimary == 0){
                return actualPrimary;
            }
            return maxPrimary;
        }
    
        // If primary is unspecified, scale primary to match secondary's scaling ratio.
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }
    
        if (maxSecondary == 0){
            return maxPrimary;
        }
    
        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
    
        //CENTER_CROP:要求等比缩放,且不留空白,不要求图片完全显示
        if (scaleType == ImageView.ScaleType.CENTER_CROP){
            if ((resized * ratio) < maxSecondary){
                resized = (int)(maxSecondary / ratio);
            }
            return resized;
        }
    
        //保证图片完全显示,允许留空白
        if ((resized * ratio) > maxSecondary){
            resized = (int)(maxSecondary / ratio);
        }
    
        return resized;
    }
    

    注意FIT_XY和CENTER_CROP两种类型的不同处理

    获取图片的最适压缩比:

    static int findBestSimpleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio){
            n *= 2;
        }
        return (int)n;
    }
    

    如上,保证了其压缩比为2的幂次方数

    相关文章

      网友评论

        本文标题:Volley 之 Request篇

        本文链接:https://www.haomeiwen.com/subject/yptoqxtx.html