简析开源网络模块_volley

简析开源网络模块_volley

从这里开始

    RequestQueue requestQueue = Volley.newRequestQueue(this);        
    String url = "http://www.baidu.com"; 
    StringRequest stringRequest = new StringRequest(Request.Method.GET,url,new Response.Listener<String>() {                    
        @Override                    
        public void onResponse(String response) {  
             resultTextView.setText(response);                      
        }                
    },                
    @Override                    
    public void onErrorResponse(VolleyError error) {                        
        resultTextView.setText(error.getMessage());                    
    });  

    requestQueue.add(stringRequest); 
  • 1、Volley的回调为什么运行在主线程里?

    在以上调用时,并没有传递主线程handler到volley,为什么回调直接就运行在主线程中呢?


原因:
Volley中RequestQueue的构造函数:
public RequestQueue(Cache cache, Network network, int threadPoolSize,ResponseDelivery delivery)
其中ResponseDelivery从名称就可以看出,是请求返回的代理类。 我们一般不自定义,使用默认delivery:
new ExecutorDelivery(new Handler(Looper.getMainLooper())
在这里获取的主线程的looper。
public class ExecutorDelivery implements ResponseDelivery {     
    ....    
    public ExecutorDelivery(final Handler handler) {    
        // Make an Executor that just wraps the handler.        
        mResponsePoster = new Executor() {            
            @Override            
            public void execute(Runnable command) {                
                handler.post(command);            
            }        
        };    
    }     
    @Override    
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {        
        request.markDelivered();        
        request.addMarker("post-response");        
        mResponsePoster.execute(new 
        ResponseDeliveryRunnable(request, response, runnable));    
    }     
    ....
} 

这里使用了自定义的Excutor,在MainLooper中执行回调方法。

  • 2、requestQueue.add之后,volley内部都做了些什么?
    做了一堆比较优雅的乱七八糟的操作:
    这里写图片描述

volley里概念上是基于线程池的,支持缓存的并发网络请求框架。线程池、缓存从上面流程图大概可以看出来。

  • a、线程池。
    使用一个数组实现:NetworkDispatcher[] mDispatchers;在RequestQueue初始化之后,调用start时,启动所有dispatcher。
    dispatcher负责从mNetworkQueue中取request任务,取到之后执行网络请求,得到请求后通过上面说得delivery回调发送请求结果。

  • b、mNetworkQueue。
    请求任务队列。使用优先级阻塞队列实现。最终要执行的request任务都从这个队列中取出。

  • c、mCacheQueue。
    缓存任务队列。也是优先级缓存队列,当一个request任务支持缓存的时候,addRequest不会直接加入mNetworkQueue中,会先查看mWaitingRequests中有没有正在等待的cacheKey相同的任务,如果有,放入mWaitingRequests中。否则,放入key,request放入mCacheQueue中。

  • d、CacheDispatcher。
    从1、2、3可以看出,如果request使用缓存,那么是不会直接放入mNetworkQueue中的。而线程池又只从mNetworkQueue中取任务执行。CacheDispatcher的任务就是先查看Cache中是否有该Request的缓存。有的话直接调用Delivery返回结果。 Cache miss的话,将request加入mNetworkQueue。

  • e、在ResponseDelivery中,response回调时,会同时调用RequestQueue中的finish方法。
    finish时,会根据CacheKey查看mWaitingRequests中是否存在正在等待的request。如果存在,将所有request取出,都添加到mCacheQueue中。之后按3的逻辑继续。

总结:

功能丰富、实现优雅、基于接口编程,可随意扩展。

3、都缓存了什么东西?
Volley里定义的缓存接口是这样的:

public interface Cache {    
    public Entry get(String key);    
    public void put(String key, Entry entry);    
    public void initialize();    
    public void invalidate(String key, boolean fullExpire);    
    public void remove(String key);    
    public void clear();     

    public static class Entry {        
        public byte[] data;        
        public String etag;        
        public long serverDate;        
        public long lastModified;        
        public long ttl;        
        public long softTtl;        
        public Map<String, String> responseHeaders = Collections.emptyMap();         
        public boolean isExpired() {            
            return this.ttl < System.currentTimeMillis();  
        }         
        public boolean refreshNeeded() {            
            return this.softTtl < System.currentTimeMillis();        
        }    
    } 

就是一般概念上,一个cache应该有的东西。

调用Volley.newRequestQueue时,默认使用的是DiskBasedCache。一个磁盘缓存的实现。具体的cache实现有很多种,不过重要的是实现了上述Cache接口。我们可以使用任何cache策略替换这个DiskCache。参照策略模式。

Cache的缓存对象是一个byte[]数组和一个存Header字段的Map。缓存的并不是一个response对象。一个组件只做自己应该做的事情,不参与外部操作,是很不容易做到的。。

Response<?> response = request.parseNetworkResponse(                        new NetworkResponse(entry.data, entry.responseHeaders));                

request.addMarker("cache-hit-parsed"); 

在CacheDispatcher里,如果缓存命中,根据缓存内容生成response对象,然后返回。

  • 4、Volley在哪里发起的网络请求?
    初始化RequestQueue
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {        
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);         
    String userAgent = "volley/0";        
    try {            
        String packageName = context.getPackageName();            
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);            
        userAgent = packageName + "/" + info.versionCode;        
    } catch (NameNotFoundException e) {        
    }    

    if (stack == null) {            
        if (Build.VERSION.SDK_INT >= 9) {                
            stack = new HurlStack();            
        } else {                
        // Prior to Gingerbread, HttpUrlConnection was unreliable.                
        // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html               
         stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));            
    }         
    Network network = new BasicNetwork(stack);         
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);        
    queue.start();         
    return queue;    
} 

在之前的逻辑里,NetworkDispatcher从mNetworkQueue中取出任务,然后调用mNetwork.performRequest发起请求,得到NetworkResponse。performRequest是一个接口方法。具体的实现类还是在初始化RequestQueue时指定的。在初始化RequestQueue时,设置了UA,由HttpStack生成了NetWork,然后设置给了RequestQueue。

这里的NetWork也是一个接口(面向接口编程随处可见)。它只有一个方法javaperformRequest(Request<?> request),就是发起网络请求。

BasicNetwork是它的一个基础实现。

@Override    
public NetworkResponse performRequest(Request<?> request) throws VolleyError {        
    long requestStart = SystemClock.elapsedRealtime();        
    while (true) {            
        HttpResponse httpResponse = null;            
        byte[] responseContents = null;            
        Map<String, String> responseHeaders = Collections.emptyMap();            
        try {                
            Map<String, String> headers = new HashMap<String, String>();                
            addCacheHeaders(headers, request.getCacheEntry());                
            httpResponse = mHttpStack.performRequest(request, headers);                
            StatusLine statusLine = httpResponse.getStatusLine();                
            int statusCode = statusLine.getStatusCode();                
            responseHeaders = convertHeaders(httpResponse.getAllHeaders());                
            if (statusCode == HttpStatus.SC_NOT_MODIFIED) {                    
                Entry entry = request.getCacheEntry();                    
                if (entry == null) {                        
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,                                responseHeaders, true,                                SystemClock.elapsedRealtime() - requestStart);                    
                } 
                                                                    entry.responseHeaders.putAll(responseHeaders);                    
            return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,                            entry.responseHeaders, true,                            SystemClock.elapsedRealtime() - requestStart);                
            }                
            if (httpResponse.getEntity() != null) {                  
                responseContents = entityToBytes(httpResponse.getEntity());                
            } else {                  
                responseContents = new byte[0];                
            }                
            long requestLifetime = SystemClock.elapsedRealtime() - requestStart;                
            logSlowRequests(requestLifetime, request, responseContents, statusLine);                 
            if (statusCode < 200 || statusCode > 299) {                    
                throw new IOException();                
            }                
            return new NetworkResponse(statusCode, responseContents, responseHeaders, false,                        SystemClock.elapsedRealtime() - requestStart);            
        } catch (SocketTimeoutException e) {                
            attemptRetryOnException("socket", request, new TimeoutError());            
        } catch (ConnectTimeoutException e) {                
            attemptRetryOnException("connection", request, new TimeoutError());            
        } catch (MalformedURLException e) {                
            throw new RuntimeException("Bad URL " + request.getUrl(), e);            
        } catch (IOException e) {                
            int statusCode = 0;                
            NetworkResponse networkResponse = null;                
            if (httpResponse != null) {                    
                statusCode = httpResponse.getStatusLine().getStatusCode();                
            } else {                    
                throw new NoConnectionError(e);                
            }                
            VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());                
            if (responseContents != null) {                    
                networkResponse = new NetworkResponse(statusCode, responseContents,                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);                    
                if (statusCode == HttpStatus.SC_UNAUTHORIZED ||                            statusCode == HttpStatus.SC_FORBIDDEN) {                        
                    attemptRetryOnException("auth",                                request, new AuthFailureError(networkResponse));                    
                } else {                        
                    throw new ServerError(networkResponse);                    
                }                
        } else {                    
            throw new NetworkError(networkResponse);                
        }            
    }  
}       

BasicNetwork对请求返回的httpResponse做了一系列的处理。包括转换httpResponse到NetworkResponse、responseHeader转换、异常封装(将各种异常封装为VolleyError)、请求重试等逻辑。合理的抛出Exception是library里异常比较好的处理方式。用合理的方式告诉调用者他的错误,而不是全部帮他处理。NetworkDispatcher捕获到异常后自己处理。

  • 5、Volley到底怎么发的网络请求?

在BaseNetwork中,通过调用HttpStack的performRequest发起网络请求。而HttpStack则是在newRequestQueue时,实例化的HurlStack。HttpStack还是一个接口。。。。

Volley网络请求逻辑里所有使用接口的地方,我们都可以使用自己的实现来扩展、自定义具体的操作。而不是直接修改源码。

在4中可以看到,HttpStack有两个实现。根据SDK_VERSION区分。2.3之前使用的HttpClientStack是HttpClient,之后HurlStack使用的是HttpUrlConnection。原因见上层文章末尾。

请求网络:

HurlStack.java      
@Override    
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)            throws IOException, AuthFailureError {        
    String url = request.getUrl();        
    HashMap<String, String> map = new HashMap<String, String>();        
    map.putAll(request.getHeaders());        
    map.putAll(additionalHeaders);        
    if (mUrlRewriter != null) {            
        String rewritten = mUrlRewriter.rewriteUrl(url);            
        if (rewritten == null) {                
            throw new IOException("URL blocked by rewriter: " + url);            
        }            
        url = rewritten;        
    }        
    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 is returned by getResponseCode() 
if the response code could not be retrieved.            
        // Signal to the caller that something was wrong with the connection.            
        throw new IOException("Could not retrieve response code from HttpUrlConnection.");        
    }        
    StatusLine responseStatus = new BasicStatusLine(protocolVersion,                connection.getResponseCode(), connection.getResponseMessage());        
    BasicHttpResponse response = new BasicHttpResponse(responseStatus);        
    if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {           
        response.setEntity(entityFromConnection(connection));        
    }        
    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;    
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值