zoukankan      html  css  js  c++  java
  • Android 学习笔记之Volley开源框架解析(五)

    学习内容:

    1.PoolingByteArrayOutputStream

    2.ByteArrayPool

    3.HttpStack

    4.HurlStack

    5.HttpHeaderParser

      前面整体的解释了网络请求——响应的整个过程,只是其中还是涉及到了一些其他的类,因此在这里都说一说,最后几篇会是Volley正式做一些请求,这篇仍然是进行源码解析...

    1.PoolingByteArrayOutputStream.java

      PoolingByteArrayOutputStream继承与ByteArrayOutputStream...当ByteArrayOutputStream进行写入数据操作时,需要通过缓冲buf来作为缓冲机制,如果缓存的空间不足,那么需要new一个更大数量级的buf来作为缓冲机制,这样会增加内存的分配个释放的过程,而PoolingByteArrayOutputStream的优势在于它内部实现了回收机制,可以对Byte进行回收和再次利用,减少了频繁分配内存和释放的操作..

      Byte回收池机制通过使用ArrayList,一共有两个ArrayList决定了,一个用于从小到大的顺序保存Byte[]...另一个按照时间顺序,用于缓存一旦满了时,按照时间顺序进行清除..

    package com.android.volley.toolbox;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    
    public class PoolingByteArrayOutputStream extends ByteArrayOutputStream {
      
        private static final int DEFAULT_SIZE = 256; //定义缓冲池的默认字节大小..
    
        private final ByteArrayPool mPool;  //定义比特回收池对象...
    
        //按照默认的方式去构造一个缓冲池对象...
        public PoolingByteArrayOutputStream(ByteArrayPool pool) {
            this(pool, DEFAULT_SIZE);
        }
    
        //按照人为指定的大小去构造一个缓冲池对象...
        public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) {
            mPool = pool;
            buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE));
        }
        //释放内存...关闭流的操作...
        @Override
        public void close() throws IOException {
            mPool.returnBuf(buf);
            buf = null;
            super.close();
        }
        //返回一个缓冲池...
        @Override
        public void finalize() {
            mPool.returnBuf(buf);
        }
    
        //写入数据前需要调用的函数,确保在缓存有足够大的空间来满足给定的字节大小...
        private void expand(int i) {
            /* Can the buffer handle @i more bytes, if not expand it */
            if (count + i <= buf.length) {
                return;
            }
            byte[] newbuf = mPool.getBuf((count + i) * 2);
            System.arraycopy(buf, 0, newbuf, 0, count);
            mPool.returnBuf(buf);
            buf = newbuf;
        }
        //写入数据函数...
        @Override
        public synchronized void write(byte[] buffer, int offset, int len) {
            expand(len);
            super.write(buffer, offset, len);
        }
    
        @Override
        public synchronized void write(int oneByte) {
            expand(1);
            super.write(oneByte);
        }
    }

      PoolingByteArrayOutputStream只是对流的一个封装,内部实现了写操作的实现方法...它基于缓冲回收机制,那么缓冲回收机制的实现类也必然是需要我们去清楚的...需要明确是如何实现的缓冲回收...

    2.ByteArrayPool.java

      实现回收机制的实现类...通过使用两个ArrayList实现了缓冲池回收机制,一个是按照大小顺序来保存使用过的Byte[]缓冲,另一个则是按照时间顺序对Byte[]进行保存...

    package com.android.volley.toolbox;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.LinkedList;
    import java.util.List;
    
    public class ByteArrayPool {
       
        private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();//以时间的先后顺序保存Byte[]
        private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);//以字节大小的顺序保存Byte[]
    
        /** The total size of the buffers in the pool */
        private int mCurrentSize = 0; //记录Bytep[]的数量...
    
        private final int mSizeLimit; //大小限制..
    
        /** Compares buffers by size */
        //对所有的Byte[]进行大小比较...
        protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {
            @Override
            public int compare(byte[] lhs, byte[] rhs) {
                return lhs.length - rhs.length;
            }
        };
    
        /**
         * @param sizeLimit the maximum size of the pool, in bytes
         */
        //设置允许的最大Byte[]...
        public ByteArrayPool(int sizeLimit) {
            mSizeLimit = sizeLimit;
        }
    
       //获取缓冲的过程,如果缓冲池当中存在一个合适的缓冲区,那么就return这个缓冲区,如果没有合适的,那么就需要新建立一个缓冲区...  
        public synchronized byte[] getBuf(int len) {
            for (int i = 0; i < mBuffersBySize.size(); i++) { //遍历过程...
                byte[] buf = mBuffersBySize.get(i); //获取每一个缓冲..
                if (buf.length >= len) {   //如果满足规格...
                    mCurrentSize -= buf.length;
                   //表示缓冲被占用,那么ArrayList就将其移除并返回...  
                    mBuffersBySize.remove(i); 
                    mBuffersByLastUse.remove(buf);
                    return buf;  //返回合适的buf...
                }
            }
            return new byte[len];   //如果没有,新建立一个缓冲...
        }
        //对使用过的Byte[]进行保存...根据大小插入到合适的ArrayList中...
        public synchronized void returnBuf(byte[] buf) {
            if (buf == null || buf.length > mSizeLimit) {
                return;
            }
            mBuffersByLastUse.add(buf); //加入这次使用过的缓冲...
            //比较完大小进行插入..
            int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
            if (pos < 0) {
                pos = -pos - 1;
            }
            mBuffersBySize.add(pos, buf);//插入过程...
            mCurrentSize += buf.length; //记录当前大小...
            trim();  //trim()函数调用...
        }
    
        //函数的目的是判断,如果使用过的缓冲大小超出了预先设定的大小,那么按照先进先出的原则,缓冲会移除每次都移除第一个Byte,当Byte[]满足了指定大小,就不用再删除字节了...
        private synchronized void trim() {
            while (mCurrentSize > mSizeLimit) {
                byte[] buf = mBuffersByLastUse.remove(0);
                mBuffersBySize.remove(buf);
                mCurrentSize -= buf.length;
            }
        }
    
    }

    3.HttpStack.java

      前面涉及到的网络请求都是通过Http协议来完成请求的,在new Request的时候需要建立一个栈区来保存所有请求,那么这个栈区则是通过HttpStack来实现的,而HttpStack只是一个抽象类的接口...

    package com.android.volley.toolbox;
    
    import com.android.volley.AuthFailureError;
    import com.android.volley.Request;
    
    import org.apache.http.HttpResponse;
    
    import java.io.IOException;
    import java.util.Map;
    public interface HttpStack {
      
        public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError;
    
    }

      我们知道Http请求一种是通过HttpURLConnection通过url来建立一个连接的,而另一种方式则是通过HttpClient,基于Apache的一种请求方式,而Android从2.3版本以后就推荐使用第一种连接方式来创建一个连接...因此我们就说一下HttpUrlConnectionStack,在Volley中实现类以HurlStack,创建一个url连接的栈区,来保存所有通过url来建立的网络连接...

    4.HurlStack.java

    package com.android.volley.toolbox;
    
    import com.android.volley.AuthFailureError;
    import com.android.volley.Request;
    import com.android.volley.Request.Method;
    
    import org.apache.http.Header;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    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.BasicHttpResponse;
    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.URL;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLSocketFactory;
    
    
    public class HurlStack implements HttpStack {
    
        private static final String HEADER_CONTENT_TYPE = "Content-Type"; //Header内容的类型...
    
        public interface UrlRewriter {
            public String rewriteUrl(String originalUrl); //重写一个url...
        }
    
        private final UrlRewriter mUrlRewriter;  //url重写对象...
        private final SSLSocketFactory mSslSocketFactory;  //用于Https请求...
    
        public HurlStack() {
            this(null);
        }
    
        public HurlStack(UrlRewriter urlRewriter) {
            this(urlRewriter, null);
        }
    
        //创建一个栈区,保存url连接...
        public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
            mUrlRewriter = urlRewriter;
            mSslSocketFactory = sslSocketFactory;
        }
    
        @Override
        public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)  //执行请求的过程...
                throws IOException, AuthFailureError {
            //获取url...
            String url = request.getUrl();
            HashMap<String, String> map = new HashMap<String, String>();
            //为请求加上头部,以及我们传递的额外参数..
            map.putAll(request.getHeaders());
            map.putAll(additionalHeaders);
            //对url进行重写,重写的好处使url更加的保密,安全...
            if (mUrlRewriter != null) {
                String rewritten = mUrlRewriter.rewriteUrl(url);
                if (rewritten == null) {
                    throw new IOException("URL blocked by rewriter: " + url);
                }
                url = rewritten;
            }
            //创建url对象...
            URL parsedUrl = new URL(url);
            HttpURLConnection connection = openConnection(parsedUrl, request); //建立连接...
            for (String headerName : map.keySet()) {
                connection.addRequestProperty(headerName, map.get(headerName)); //设置请求的相关属性...
            }
            setConnectionParametersForRequest(connection, request);//根据请求的方式去执行相关的方法...
            // Initialize HttpResponse with data from the HttpURLConnection.
            ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); //获取协议版本...
            int responseCode = connection.getResponseCode(); //响应码获取...
            if (responseCode == -1) { //如果为-1,抛出异常...
                throw new IOException("Could not retrieve response code from HttpUrlConnection.");
            }
            StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                    connection.getResponseCode(), connection.getResponseMessage()); //获取响应信息...
            BasicHttpResponse response = new BasicHttpResponse(responseStatus);  //封装响应状态...
            response.setEntity(entityFromConnection(connection)); //获取响应中的实体...
            //对Header进行遍历...为响应添加相关的Header...
            for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
                if (header.getKey() != null) {
                    Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                    response.addHeader(h);
                }
            }
            return response;//返回响应...
        }
    
        private static HttpEntity entityFromConnection(HttpURLConnection connection) { //获取实体的方法..
            BasicHttpEntity entity = new BasicHttpEntity(); //实体封装对象的创建...
            InputStream inputStream;
            try {
                inputStream = connection.getInputStream(); //获取连接的I/O流..
            } catch (IOException ioe) {
                inputStream = connection.getErrorStream();
            }
            entity.setContent(inputStream); //实体的内容..
            entity.setContentLength(connection.getContentLength());//实体的长度..
            entity.setContentEncoding(connection.getContentEncoding()); //实体的编码...
            entity.setContentType(connection.getContentType());//实体内容的类型...
            return entity;
        }
        //创建连接...
        protected HttpURLConnection createConnection(URL url) throws IOException {
            return (HttpURLConnection) url.openConnection();
        }
        //打开连接方法,,,
        private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
            HttpURLConnection connection = createConnection(url); 
    
            int timeoutMs = request.getTimeoutMs(); //设置请求的超时时间..
            connection.setConnectTimeout(timeoutMs); //连接超时时间的设置..
            connection.setReadTimeout(timeoutMs); //读取时间超时...
            connection.setUseCaches(false); //是否设置缓存..
            connection.setDoInput(true); //是否有相关的输入..
    
            // use caller-provided custom SslSocketFactory, if any, for HTTPS
            if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
                ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);  //创建一个Https连接...
            }
    
            return connection;
        }
        //为请求设置连接方式...
        @SuppressWarnings("deprecation")
        /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection,
                Request<?> request) throws IOException, AuthFailureError {
            switch (request.getMethod()) {
                case Method.DEPRECATED_GET_OR_POST:
                    byte[] postBody = request.getPostBody();
                    if (postBody != null) {
                        // Prepare output. There is no need to set Content-Length explicitly,
                        // since this is handled by HttpURLConnection using the size of the prepared
                        // output stream.
                        connection.setDoOutput(true);
                        connection.setRequestMethod("POST");
                        connection.addRequestProperty(HEADER_CONTENT_TYPE,
                                request.getPostBodyContentType());
                        DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                        out.write(postBody);
                        out.close();
                    }
                    break;
                case Method.GET:
                    // Not necessary to set the request method because connection defaults to GET but
                    // being explicit here.
                    connection.setRequestMethod("GET");
                    break;
                case Method.DELETE:
                    connection.setRequestMethod("DELETE");
                    break;
                case Method.POST:
                    connection.setRequestMethod("POST");
                    addBodyIfExists(connection, request); //调用最后一个函数...
                    break;
                case Method.PUT:
                    connection.setRequestMethod("PUT");
                    addBodyIfExists(connection, request);
                    break;
                default:
                    throw new IllegalStateException("Unknown method type.");
            }
        }
        //如果一个请求存在实体数据,那么需要为其加上请求数据...
        private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
                throws IOException, AuthFailureError {
            byte[] body = request.getBody();
            if (body != null) {
                connection.setDoOutput(true);
                connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
                DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                out.write(body);
                out.close();
            }
        }
    }

     前面说到一个请求数据是否需要缓存是通过Header中封装的数据来判断一次请求后的数据是否需要进行缓存...那么是否需要缓存,以及新鲜度的验证等等头被封装在了Header中,那么我们是如何知道Header中的数据呢?前面已经可以获取到响应数据报中的实体部分(Body),那么Header还没有被获取..因此需要说一下HttpHeaderParser类...用来解析Header中的数据...

    5.HttpHeaderParser.java

    package com.android.volley.toolbox;
    
    import com.android.volley.Cache;
    import com.android.volley.NetworkResponse;
    
    import org.apache.http.impl.cookie.DateParseException;
    import org.apache.http.impl.cookie.DateUtils;
    import org.apache.http.protocol.HTTP;
    
    import java.util.Map;
    public class HttpHeaderParser {
    
        //解析缓存Header...
        public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
            long now = System.currentTimeMillis();
    
            Map<String, String> headers = response.headers;
            //一些基本数据的定义...
            long serverDate = 0;  
            long serverExpires = 0;
            long softExpire = 0; 
            long maxAge = 0;
            boolean hasCacheControl = false;
    
            String serverEtag = null;
            String headerValue;
            //获取请求——服务的整个时间,将RFC1123的格式解析成epoch方式...
            headerValue = headers.get("Date");
            if (headerValue != null) {
                serverDate = parseDateAsEpoch(headerValue);
            }
            
            headerValue = headers.get("Cache-Control");
            if (headerValue != null) {
                hasCacheControl = true; 
                String[] tokens = headerValue.split(",");
                for (int i = 0; i < tokens.length; i++) { 
                    String token = tokens[i].trim(); //判断是否有缓存...
                    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.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                        maxAge = 0; //如果不允许缓存,那么有效期为0...
                    }
                }
            }
    
            headerValue = headers.get("Expires"); 
            if (headerValue != null) {
                serverExpires = parseDateAsEpoch(headerValue); //设置缓存新鲜度时间...
            }
    
            serverEtag = headers.get("ETag");
    
           //设置缓存的过期时间...
            if (hasCacheControl) {
                softExpire = now + maxAge * 1000;
            } else if (serverDate > 0 && serverExpires >= serverDate) {
                // Default semantic for Expire header in HTTP specification is softExpire.
                softExpire = now + (serverExpires - serverDate);
            }
            //将Header数据保存在Entry当中..
            Cache.Entry entry = new Cache.Entry();
            entry.data = response.data;
            entry.etag = serverEtag;
            entry.softTtl = softExpire;
            entry.ttl = entry.softTtl;
            entry.serverDate = serverDate;
            entry.responseHeaders = headers;
    
            return entry;
        }
    
        //将RFC1123的时间格式转换成epoch格式...
        public static long parseDateAsEpoch(String dateStr) {
            try {
                // Parse date in RFC1123 format if this header contains one
                return DateUtils.parseDate(dateStr).getTime();
            } catch (DateParseException e) {
                // Date in invalid format, fallback to 0
                return 0;
            }
        }
    
        //解析字符集...
        public static String parseCharset(Map<String, String> headers) {
            String contentType = headers.get(HTTP.CONTENT_TYPE);
            if (contentType != null) {
                String[] params = contentType.split(";");
                for (int i = 1; i < params.length; i++) {
                    String[] pair = params[i].trim().split("=");
                    if (pair.length == 2) {
                        if (pair[0].equals("charset")) {
                            return pair[1];
                        }
                    }
                }
            }
    
            return HTTP.DEFAULT_CONTENT_CHARSET;
        }
    }

     Volley中还有一些其他类,不过基本都是一些简单的类,就不粘贴代码进行解析了,只是提一嘴就一笔带过就行了...

     Volley.java:工具类,用于实现一个请求队列...

     Authenticator.java:一个抽象接口,用于身份验证...用于基本认证和摘要认证...不过使用的不是非常的广泛.. 

     AndroidAuthenticator.java:基于Android AccountManager的认证交互类...实现了验证接口的抽象方法...

     VolleyLog.java:在Volley中用于显示Log信息...

     VolleyError.java:Volley内部所有异常类的父类...对异常的处理方式的一个超类...继承了Expection...

     TimeoutError.java

     ServerError.java

     NetWorkError.java

     ParseError.java

     NoConnection.java

     AuthFailureError.java
        都是异常发生如何处理的类,其中包括超时,服务端错误,网络错误,内容解析错误,无法连接错误,验证失败等异常处理...在这里就不一一介绍了..都比较简单...

  • 相关阅读:
    洛谷春季多校第四场
    HZNU Training 8 for Zhejiang Provincial Competition 2020
    HZNU Training 6 for Zhejiang Provincial Competition 2020
    二分图
    HZNU Training 5 for Zhejiang Provincial Competition 2020
    洛谷春季 ACM 多校训练第二周
    HZNU Training 2 for Zhejiang Provincial Competition 2020
    TestNG入门教程-12-Java代码执行testng.xml和失败后重跑
    eclipse导出可执行jar包步骤
    创建可执行的JAR包并运行
  • 原文地址:https://www.cnblogs.com/RGogoing/p/4907488.html
Copyright © 2011-2022 走看看