Volley源码解析

作者: cgzysan | 来源:发表于2016-10-30 17:28 被阅读102次

Volley介绍

Volley 是 Google 在 2013 Google I/O 大会上发布推出的 Android 异步网络请求框架和图片加载框架,其最明显的一个优点就是特别适合数据量不大但是通信频繁的场景,最明显的缺点就是大数据传输表现的很糟糕。(适合于数据量小,通信频繁的网络操作)

Volley提供了以下的便利功能

  • JSON数据和图像等的异步下载;
  • 网络请求排序(scheduling);
  • 网络请求优先级处理;
  • 缓存;
  • 多级别取消请求;
  • 与Activity生命周期联动(Activity结束时同时取消所有网络请求);

newRequestQueue的使用

Volley的用法很简单,发起一条HTTP GET请求,然后接收HTTP响应。首先需要获取到一个RequestQueue对象,可以调用如下方法获取到:

RequestQueue newQueue = Volley.newRequestQueue(context);

这里拿到的RequestQueue是一个请求队列对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。

RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的,基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。

Request的使用

Request是请求的基类,拥有以下子类:

  • ClearCacheRequest
  • JsonRequest
  • ImageRequest
  • StringRequest

在基类中定义了一个内部接口类 Method 分别定义了集中常见的请求方式

public interface Method {
    int DEPRECATED_GET_OR_POST = -1;
    int GET = 0;
    int POST = 1;
    int PUT = 2;
    int DELETE = 3;
    int PATCH = 4;
}

同时还定义了一个枚举 Priority 定义了一些优先级的常量

public static enum Priority {
    LOW,
    NORMAL,
    HIGH,
    IMMEDIATE;

    private Priority() {
    }
}

StringRequestd的使用

StringRequest的构造函数需要传入四个参数:

public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
    super(method, url, errorListener);
    this.mListener = listener;
}
  • 第一个参数是表示请求的方式 默认是0 也就是 GET 请求
  • 第二个参数是目标服务器的URL地址
  • 第三个参数是服务器响应成功的回调
  • 第四个参数是服务器响应失败的回调

将这个StringRequest对象添加到RequestQueue里面就可以了。使用Volley时,可以从任何线程开始请求,但响应始终传递到了主线程上。
以下是阅读Android Developer文档中的VolleyDemo:

final TextView mTextView = (TextView) findViewById(R.id.text);
...

// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
// Add the request to the RequestQueue.
queue.add(stringRequest);

以上就用Volley发起了一个简单的HTTP 的 GET 请求。

Volley发出Request

Volley发出网络请求,阻塞I/O和解析数据都是在子线程中完成的,您可以在任何线程添加网络请求,但是响应始终都是传递到主线程上。
分析一下 Android Developer 上的这幅图:

volley-request.png
  • RequestQueue会维护一个缓存调度线程(CacheDispatcher)
  • 同时还会维护一网络调度线程池(NetworkDispatcher[])->线程池默认大小为4
  • Volley.newRequestQueue(context);的代码中在data/data/包名/cache/目录下创建了一个volley的文件夹用于存放缓存(本地缓存)
  • 同时创建一个 RequestQueue 类,并调用了其start() 方法
BasicNetwork network1 = new BasicNetwork((HttpStack)stack);//将在后文提到
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
  • start()方法看上去像是线程开启的方法,但是 RequestQueue 并不是一个Thread类,不过在当中它干的就是开启线程的事。
public void start() {
    this.stop();
    this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
    this.mCacheDispatcher.start();

    for(int i = 0; i < this.mDispatchers.length; ++i) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
        this.mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

没错,它逐一开启了缓存调度线程和所有的网络调度线程

  • 每一条线程就开始不断的循环读取cache队列或者net队列的头

  • 当一个Request被添加到队列中的时候,cache线程会首先对这个请求进行筛选,如果这个请求的内筒可以在缓存中找到,并且没有过期,cache线程会自己解析响应的内容,并分发到主线程(UI);

  • 如果缓存中没有,这个request就会被加入到另一个NetworkQueue,所有真正准备进行网络通信的request都在这里,第一个可用的net线程会从NetworkQueue中拿出一个request扔向服务器。当响应获得数据之后,这个net线程会解析原始响应数据,写入缓存,并把解析后的结果返回给主线程。

取消一个Request

要取消一个请求,调用cancel()即可。一旦取消,Volley保证你的响应处理程序将永远不会被调用。这意味着在实践中,你可以取消所有待定的请求在你Activity的onStop()方法中,你不必乱抛垃圾的响应处理程序或者检查getActivity() == NULL,无论onSaveInstanceState()是否已经被调用。

你发出去的请求必须要自己保证是可控的,在这里还有一个更简单的方法:你可以标记发送的每个请求对象,然后你可以使用这个标签来提供请求取消的范围。

下面是一个使用字符串标签的例子:

  1. 定义您的标签同时将其添加到Request中
    public static final String TAG = "MyTag";
    StringRequest stringRequest; // Assume this exists.
    RequestQueue mRequestQueue; // Assume this exists.

     // Set the tag on the request.
     stringRequest.setTag(TAG);
     
     // Add the request to the RequestQueue.
     mRequestQueue.add(stringRequest);
    
  2. 在您的Activity的 onStop() 方法中,取消所有被标记的Request

     @Override
     protected void onStop () {
         super.onStop();
         if (mRequestQueue != null) {
             mRequestQueue.cancelAll(TAG);
         }
     }
    

取消请求时要小心。如果你是根据你的响应处理程序来推进一个状态或启动另一个进程,你需要考虑到这个。同样,响应处理程序将不会被调用。

BasicNetwork

现在我们来说说 BasicNetwork,前文在讲 RequestQueue 的时候有所提到,其中 BasicNetwork 是创建 RequestQueue 对象时当中的一个参数

  • BasicNetwork 是Volley的一个默认网络实现,App连接HTTP客户端必须要对其初始化。最主要的是实现了 Network 接口中的 NetworkResponse performRequest(Request<?> var1) throws VolleyError; 方法,其实这个接口中就只有这一个方法。
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
    long requestStart = SystemClock.elapsedRealtime();

          while(true) {
              HttpResponse httpResponse = null;
              Object responseContents = null;
              HashMap responseHeaders = new HashMap();
    
              try {
                  HashMap e = new HashMap();
                  this.addCacheHeaders(e, request.getCacheEntry());
                  httpResponse = this.mHttpStack.performRequest(request, e);
                  StatusLine statusCode2 = httpResponse.getStatusLine();
                  int networkResponse1 = statusCode2.getStatusCode();
                  Map responseHeaders1 = convertHeaders(httpResponse.getAllHeaders());
                  if(networkResponse1 == 304) {
                      return new NetworkResponse(304, request.getCacheEntry().data, responseHeaders1, true);
                  }
    
                  byte[] responseContents1 = this.entityToBytes(httpResponse.getEntity());
                  long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                  this.logSlowRequests(requestLifetime, request, responseContents1, statusCode2);
                  if(networkResponse1 != 200 && networkResponse1 != 201 && networkResponse1 != 202 && networkResponse1 != 204) {
                      throw new IOException();
                  }
    
                  return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false);
              } catch (SocketTimeoutException var12) {
                  attemptRetryOnException("socket", request, new TimeoutError());
              } catch (ConnectTimeoutException var13) {
                  attemptRetryOnException("connection", request, new TimeoutError());
              } catch (MalformedURLException var14) {
                  throw new RuntimeException("Bad URL " + request.getUrl(), var14);
              } catch (IOException var15) {
                  boolean statusCode = false;
                  NetworkResponse networkResponse = null;
                  if(httpResponse == null) {
                      throw new NoConnectionError(var15);
                  }
    
                  int statusCode1 = httpResponse.getStatusLine().getStatusCode();
                  VolleyLog.e("Unexpected response code %d for %s", new Object[]{Integer.valueOf(statusCode1), request.getUrl()});
                  if(responseContents == null) {
                      throw new NetworkError(networkResponse);
                  }
    
                  networkResponse = new NetworkResponse(statusCode1, (byte[])responseContents, responseHeaders, false);
                  if(statusCode1 != 401 && statusCode1 != 403) {
                      if(statusCode1 != 400 && statusCode1 != 404 && statusCode1 != 406 && statusCode1 != 501) {
                          throw new ServerError(networkResponse);
                      }
      
                          throw new ClientError(networkResponse);
                      }
      
                  attemptRetryOnException("auth", request, new AuthFailureError(networkResponse));
              }
              }
         }
    
  • 这个方法是网络请求的具体实现,以一个while大循环包裹,其中httpResponse = this.mHttpStack.performRequest(request, e);是主要的网络请求代码,由HttpStack发出。在创建对象的时候传入的对象。以下具体说明

  • 创建 BasicNetwork 对象的时候,需要传入一个HttpStack的对象。
    HttpStack stack;
    ...
    if(stack == null) {
    if(VERSION.SDK_INT >= 9) {
    stack = new HurlStack();
    } else {
    stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
    }
    }

      BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
    
  • 可以看出在 Froyo(2.2) 之前,采用基于 HttpClient 的 HttpClientStack,之后使用基于 HttpURLConnection 的 HurlStack,原因呢是因为Froyo(2.2) HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive。

      private void disableConnectionReuseIfNecessary() {    
      // 这是一个2.2版本之前的bug    
      if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {    
          System.setProperty("http.keepAlive", "false");    
              }    
      }
    
  • 另外 Gingerbread(2.3) HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了请求结果缓存。再加上 HttpURLConnection 本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。下面我们来具体说说网络请求接口HttpStack。

HttpStack接口

HttpStack接口中也只有一个方法HttpResponse performRequest(Request<?> var1, Map<String, String> var2) throws IOException, AuthFailureError;
在前文我们提到了HttpStack的两个实现类 HttpClientStack 和 HurlStack,在实现的具体方法中真正进行了网络请求。

  • HurlStack中封装了HttpURLConnection

    ...
    URL parsedUrl1 = new URL(url);
    HttpURLConnection connection = this.openConnection(parsedUrl1, request);
    Iterator responseCode = map.keySet().iterator();
    ...

  • HttpClientStack中封装了HttpClient,也就是传进来的AndroidHttpClient。

Request的parseNetworkResponse方法

以上步骤,在NetworkDispatcher中收到了NetworkResponse这个返回值后又会调用Request的parseNetworkResponse()方法来解析NetworkResponse中的数据,同时将数据写入到缓存,这个方法的实现是交给Request的子类来完成的,因为不同种类的Request解析的方式也肯定不同。

ResponseDelivery接口

在获取了解析后的数据之后,NetworkDispatcher的run()方法最后调用了this.mDelivery.postResponse(request, response);将数据post到主线程(UI线程),其中的 mDelivery 就是 ResponseDelivery接口的具体实现类 ExecutorDelivery对象,具体的方法如下:

@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

ExecutorDelivery 的构造方法如下:

public ExecutorDelivery(final Handler handler) {
    this.mResponsePoster = new Executor() {
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}

在创建其实例的时候传入了 RequestQueue 实例的 mDelivery :

NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);

而 mDelivery 就是创建 RequestQueue 时创建的主线程Handler:

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

如此就保证了 ExecutorDelivery 对象中的 run() 方法在主线程中运行。

而在 run() 方法中最核心的代码就是:

if(this.mResponse.isSuccess()) {
    this.mRequest.deliverResponse(this.mResponse.result);
} else {
    this.mRequest.deliverError(this.mResponse.error);
}

调用了Request的 deliverResponse(this.mResponse.result)mRequest.deliverError(this.mResponse.error) 方法,其中就调用了需要重写的实现Listener接口方法,将反馈发送到回调到UI线程。

这是后来想起加的一张NetworkDispatcher的工作流程图:

NetworkDispatcher.png

有什么不对还请指出,谢谢指教。

相关文章

  • 每日一题:Volley源码问题分析

    每日一题:Volley源码问题分析 学习推荐_Volley源码解析 面试率: ★★★☆☆ 面试提醒 Volley是...

  • Volley

    Android Volley完全解析(四),带你从源码的角度理解Volley

  • 【源码解析】 Volly

    【源码解析】 Volley的用法及源码解析 前言 Volley是谷歌在2013年I/O大会上推出的新的网络请求框架...

  • Volley 源码解析及对 Volley 的扩展(三)

    在 Volley 源码解析及对 Volley 的扩展系列的第二篇文章中对 Volley 的部分源码进行了分析,也知...

  • Volley源码解析

    Volley是一款轻量级的网络访问框架,适合小批量的数据传输。Volley的使用通过newRequestQueue...

  • Volley源码解析

    源码下载地址:https://github.com/code-study/android-volley-analy...

  • Volley源码解析

    Volley作为轻量级网络请求框架已经被广泛使用,这篇文章就从源码角度深层次了解Volley的构成,立足于熟练使用...

  • Volley源码解析

    抽时间看了下Volley的源码,感觉这个框架还是很不错的,这里对其源码进行分析。 GitHub链接 主要从以下几个...

  • Volley源码解析

    Volley已是老牌的网络请求库了(虽然也就3岁不到),虽然是Google官方的库,但是目前OkHttp才正是大行...

  • Volley源码解析

    通过Volley.newReuqestQueue新建一个请求队列(VolleyQueue),在创建请求队列之后,会...

网友评论

    本文标题:Volley源码解析

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