Android网络访问Volley源码解析

lutz8325 8年前

来自: http://blog.csdn.net/u014486880/article/details/50703666


很早之前就想写下关于Volley的源码解析。一开始学android网络访问都是使用HttpClient,刚接触么Volley的时候就瞬间爱不释手,虽说现在项目中使用OkHttp多些(Volley更新慢),但是作为google自家推出的网络框架,Volley还是有很多值得学习的地方。这篇博客是我对Volley源码分析后的一个总结。

Volley的使用

Volley的使用非常简单,相信大家都很熟悉。首先需要获取到一个RequestQueue对象。

RequestQueue mQueue = Volley.newRequestQueue(context); 

如果想通过网络获取json,如下:

StringRequest stringRequest = new StringRequest("http://www.baidu.com",                            new Response.Listener<String>() {                                @Override                                public void onResponse(String response) {                                    Log.d("TAG", response);                                }                            }, new Response.ErrorListener() {                                @Override                                public void onErrorResponse(VolleyError error) {                                    Log.e("TAG", error.getMessage(), error);                                }                            }); 

只要在onResponse中处理返回的response即可。如果访问出错,则会调用onErrorResonse方法。 注意Volley是异步,是在子线程中进行网络访问,而onResponse里的代码是在主线程中执行。所以使用Volley的地方切记不要把它当成单线程,这是初学者经常犯错的地方。最后,将这个StringRequest对象添加到RequestQueue里面就可以了。

mQueue.add(stringRequest); 

如果要加载图片,则首先要定义一个ImageCache,用于定义图片的缓存。通过ImageLoader来加载图片,ImageListener则用于指定ImageView以及加载失败和加载过程中默认图片 :

            private final LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>(                      (int) (Runtime.getRuntime().maxMemory() / 10))              {                  @Override                  protected int sizeOf(String key, Bitmap value)                  {                      return value.getRowBytes() * value.getHeight();                  }              };                @Override              public void putBitmap(String url, Bitmap bitmap)              {                  mLruCache.put(url, bitmap);              }                @Override              public Bitmap getBitmap(String url)              {                  return mLruCache.get(url);              }          });  ImageListener listener = ImageLoader.getImageListener(imageView,            R.drawable.default, R.drawable.failed);           imageLoader.get(imageurl, listener);  

介绍完简单用法之后,就来分析源代码了。

Volley源码分析

先看下官网给出的介绍图:
这里写图片描述
这里我们先有个大概的介绍,蓝色是主线程,绿色是CacheDispatcher(硬盘缓存)线程,红色是NetworkDispatcher(网络请求线程)。我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。接下来详细的进行分析。
不用说,入口肯定是Volley.newRequestQueue(context)。先看下newRequestQueue的代码:

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;      }

首先封装得到userAgent,User-Agent 字段设置为 App 的packageName/{versionCode},如果异常则使用 “volley/0”。上面代码主要是实例化stack ,如果SDK版本大于9,使用HurlStack,否则使用HttpClientStack。实际上HurlStack的内部就是使用HttpURLConnection进行网络通讯的,而HttpClientStack的内部则是使用HttpClient进行网络通讯的。也就是说android2.2以上的都是使用HttpURLConnection,否则使用HttpClient。接着new了一个RequestQueue,并调用它的start方法。来看下它的RequestQueue构造方法:

/** Number of network request dispatcher threads to start. */      private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;        /** Cache interface for retrieving and storing responses. */      private final Cache mCache;        /** Network interface for performing requests. */      private final Network mNetwork;        /** Response delivery mechanism. */      private final ResponseDelivery mDelivery;        /** The network dispatchers. */      private NetworkDispatcher[] mDispatchers;     public RequestQueue(Cache cache, Network network, int threadPoolSize,              ResponseDelivery delivery) {          mCache = cache;          mNetwork = network;          mDispatchers = new NetworkDispatcher[threadPoolSize];          mDelivery = delivery;      }

初始化主要就是4个参数:mCache、mNetwork、mDispatchers、mDelivery。第一个是硬盘缓存;第二个主要用于Http相关操作;第三个用于转发请求的;第四个参数用于把结果转发到UI线程,通过它来对外声明接口。接下来看下start方法。

 private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;        /** Cache interface for retrieving and storing responses. */      private final Cache mCache;        /** Network interface for performing requests. */      private final Network mNetwork;        /** Response delivery mechanism. */      private final ResponseDelivery mDelivery;        /** The network dispatchers. */      private NetworkDispatcher[] mDispatchers;     /** * Starts the dispatchers in this queue. */      public void start() {          stop();  // Make sure any currently running dispatchers are stopped.          // Create the cache dispatcher and start it.          mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);          mCacheDispatcher.start();            // Create network dispatchers (and corresponding threads) up to the pool size.          for (int i = 0; i < mDispatchers.length; i++) {              NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,                      mCache, mDelivery);              mDispatchers[i] = networkDispatcher;              networkDispatcher.start();          }      }        /** * Stops the cache and network dispatchers. */      public void stop() {          if (mCacheDispatcher != null) {              mCacheDispatcher.quit();          }          for (int i = 0; i < mDispatchers.length; i++) {              if (mDispatchers[i] != null) {                  mDispatchers[i].quit();              }          }      }

首先调用stop()方法,确保此时所有转发器都处于停止状态。接下来就new了一个CacheDispatcher转发器,它其实就是一个线程,用于硬盘缓存。再new了四个NetworkDispatcher转发器,用于网络请求。并分别调用这些线程的start()方法。如果是加载图片,我们还需定义一个imageLoader,来看看Volley中为我们定义的ImageLoader,主要看它的get方法:

 public ImageContainer get(String requestUrl, ImageListener imageListener,              int maxWidth, int maxHeight) {          // only fulfill requests that were initiated from the main thread.          throwIfNotOnMainThread();            final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);            // Try to look up the request in the cache of remote images.          Bitmap cachedBitmap = mCache.getBitmap(cacheKey);          if (cachedBitmap != null) {              // Return the cached bitmap.              ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);              imageListener.onResponse(container, true);              return container;          }            // The bitmap did not exist in the cache, fetch it!          ImageContainer imageContainer =                  new ImageContainer(null, requestUrl, cacheKey, imageListener);            // Update the caller to let them know that they should use the default bitmap.          imageListener.onResponse(imageContainer, true);            // Check to see if a request is already in-flight.          BatchedImageRequest request = mInFlightRequests.get(cacheKey);          if (request != null) {              // If it is, add this request to the list of listeners.              request.addContainer(imageContainer);              return imageContainer;          }            // The request is not already in flight. Send the new request to the network and          // track it.          Request<?> newRequest =              new ImageRequest(requestUrl, new Listener<Bitmap>() {                  @Override                  public void onResponse(Bitmap response) {                      onGetImageSuccess(cacheKey, response);                  }              }, maxWidth, maxHeight,              Config.RGB_565, new ErrorListener() {                  @Override                  public void onErrorResponse(VolleyError error) {                      onGetImageError(cacheKey, error);                  }              });            mRequestQueue.add(newRequest);          mInFlightRequests.put(cacheKey,                  new BatchedImageRequest(newRequest, imageContainer));          return imageContainer;      }

上面代码具体流程是这样,首先通过throwIfNotOnMainThread()方法限制必须在UI线程调用;然后根据传入的参数计算cacheKey,获取缓存;如果存在cache,直接将返回结果封装为一个ImageContainer,然后直接回调imageListener.onResponse(container, true);这时我们就可以设置图片了。如果不存在,那就初始化一个ImageContainer,然后直接回调imageListener.onResponse(imageContainer, true),这里是为了让我们设置默认图片。所以,在实现listener的时候,要先判断resp.getBitmap()是否为null;接下来检查该url是否早已加入了请求对了,如果已加入,则将刚初始化的ImageContainer加入BatchedImageRequest。这就是加载图片时的内存缓存。
然后调用RequestQueue的add()方法将Request传入就可以完成网络请求操作了,让我们来看看add方法中到底做了什么事。

 private final Map<String, Queue< Request<?>>> mWaitingRequests =              new HashMap<String, Queue< Request<?>>>();        /** * The set of all requests currently being processed by this RequestQueue. A Request * will be in this set if it is waiting in any queue or currently being processed by * any dispatcher. */      private final Set<Request <?>> mCurrentRequests = new HashSet<Request<?>>();        /** The cache triage queue. */      private final PriorityBlockingQueue< Request<?>> mCacheQueue =          new PriorityBlockingQueue< Request<?>>();        /** The queue of requests that are actually going out to the network. */      private final PriorityBlockingQueue< Request<?>> mNetworkQueue =          new PriorityBlockingQueue< Request<?>>();   /** * Adds a Request to the dispatch queue. * @param request The request to service * @return The passed -in request */      public <T> Request<T> add(Request<T> request) {          // Tag the request as belonging to this queue and add it to the set of current requests.          request.setRequestQueue( this);          synchronized ( mCurrentRequests) {              mCurrentRequests.add(request);          }            // Process requests in the order they are added.          request.setSequence(getSequenceNumber());          request.addMarker( "add-to-queue");            // If the request is uncacheable, skip the cache queue and go straight to the network.          if (!request.shouldCache()) {              mNetworkQueue.add(request);              return request;          }            // Insert request into stage if there's already a request with the same cache key in flight.          synchronized ( mWaitingRequests) {              String cacheKey = request.getCacheKey();              if ( mWaitingRequests.containsKey(cacheKey)) {                  // There is already a request in flight. Queue up.                  Queue<Request<?>> stagedRequests = mWaitingRequests .get(cacheKey);                  if (stagedRequests == null) {                      stagedRequests = new LinkedList<Request<?>>();                  }                  stagedRequests.add(request);                  mWaitingRequests.put(cacheKey, stagedRequests);                  if (VolleyLog. DEBUG) {                      VolleyLog. v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);                  }              } else {                  // Insert 'null' queue for this cacheKey, indicating there is now a request in                  // flight.                  mWaitingRequests.put(cacheKey, null);                  mCacheQueue.add(request);              }              return request;          }      }

这里首先将请求加入mCurrentRequests,这个mCurrentRequests是一个HashSet,它保存了所有需要处理的Request,主要为了提供cancel的入口。如果该请求不应该被缓存则直接加入mNetworkQueue,然后返回。request.shouldCache()在默认情况下,每条请求都是可以缓存的,当然我们也可以调用Request的setShouldCache(false)方法来改变这一默认行为。
接下来判断该请求是否有相同的请求正在被处理,如果有则加入mWaitingRequests;如果没有,则加入mWaitingRequests.put(cacheKey, null),并将request加入到CacheQueue中。
有了队列,我们就来看看线程是如何执行的。先看CacheDispatcher。

public class CacheDispatcher extends Thread {        private static final boolean DEBUG = VolleyLog.DEBUG;        /** The queue of requests coming in for triage. */      private final BlockingQueue<Request<?>> mCacheQueue;        /** The queue of requests going out to the network. */      private final BlockingQueue<Request<?>> mNetworkQueue;        /** The cache to read from. */      private final Cache mCache;        /** For posting responses. */      private final ResponseDelivery mDelivery;        /** Used for telling us to die. */      private volatile boolean mQuit = false;        /** * Creates a new cache triage dispatcher thread. You must call {@link #start()} * in order to begin processing. * * @param cacheQueue Queue of incoming requests for triage * @param networkQueue Queue to post requests that require network to * @param cache Cache interface to use for resolution * @param delivery Delivery interface to use for posting responses */      public CacheDispatcher(              BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,              Cache cache, ResponseDelivery delivery) {          mCacheQueue = cacheQueue;          mNetworkQueue = networkQueue;          mCache = cache;          mDelivery = delivery;      }        /** * Forces this dispatcher to quit immediately. If any requests are still in * the queue, they are not guaranteed to be processed. */      public void quit() {          mQuit = true;          interrupt();      }        @Override      public void run() {          if (DEBUG) VolleyLog.v("start new dispatcher");          Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);            // Make a blocking call to initialize the cache.          mCache.initialize();            while (true) {              try {                  // Get a request from the cache triage queue, blocking until                  // at least one is available.                  final Request<?> request = mCacheQueue.take();                  request.addMarker("cache-queue-take");                    // If the request has been canceled, don't bother dispatching it.                  if (request.isCanceled()) {                      request.finish("cache-discard-canceled");                      continue;                  }                    // Attempt to retrieve this item from cache.                  Cache.Entry entry = mCache.get(request.getCacheKey());                  if (entry == null) {                      request.addMarker("cache-miss");                      // Cache miss; send off to the network dispatcher.                      mNetworkQueue.put(request);                      continue;                  }                    // If it is completely expired, just send it to the network.                  if (entry.isExpired()) {                      request.addMarker("cache-hit-expired");                      request.setCacheEntry(entry);                      mNetworkQueue.put(request);                      continue;                  }                    // We have a cache hit; parse its data for delivery back to the request.                  request.addMarker("cache-hit");                  Response<?> response = request.parseNetworkResponse(                          new NetworkResponse(entry.data, entry.responseHeaders));                  request.addMarker("cache-hit-parsed");                    if (!entry.refreshNeeded()) {                      // Completely unexpired cache hit. Just deliver the response.                      mDelivery.postResponse(request, response);                  } else {                      // Soft-expired cache hit. We can deliver the cached response,                      // but we need to also send the request to the network for                      // refreshing.                      request.addMarker("cache-hit-refresh-needed");                      request.setCacheEntry(entry);                        // Mark the response as intermediate.                      response.intermediate = true;                        // Post the intermediate response back to the user and have                      // the delivery then forward the request along to the network.                      mDelivery.postResponse(request, response, new Runnable() {                          @Override                          public void run() {                              try {                                  mNetworkQueue.put(request);                              } catch (InterruptedException e) {                                  // Not much we can do about this.                              }                          }                      });                  }                } catch (InterruptedException e) {                  // We may have been interrupted because it was time to quit.                  if (mQuit) {                      return;                  }                  continue;              }          }      }  }

我们要知道CacheDispatcher是硬盘缓存,到此可知Volley也是有二级缓存的。重点看它的run方法。看到while(true)时,我们就知道,它是在不断的执行的。首先从mCacheQueue中取出缓存,如果没有取到,就把它加入mNetworkQueue中,再判断缓存是否过期,如果过期,也放入mNetworkQueue中。否则就取到了可用的缓存了,再调用request.parseNetworkResponse解析从缓存中取出的data和responseHeaders通过mDelivery.postResponse转发,然后回调到UI线程;我们看下mDelivery.postResponse方法:

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

主要看ResponseDeliveryRunnable。

 public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {              mRequest = request;              mResponse = response;              mRunnable = runnable;          }            @SuppressWarnings("unchecked")          @Override          public void run() {              // If this request has canceled, finish it and don't deliver.              if (mRequest.isCanceled()) {                  mRequest.finish("canceled-at-delivery");                  return;              }                // Deliver a normal response or error, depending.              if (mResponse.isSuccess()) {                  mRequest.deliverResponse(mResponse.result);              } else {                  mRequest.deliverError(mResponse.error);              }                // If this is an intermediate response, add a marker, otherwise we're done              // and the request can be finished.              if (mResponse.intermediate) {                  mRequest.addMarker("intermediate-response");              } else {                  mRequest.finish("done");              }                // If we have been provided a post-delivery runnable, run it.              if (mRunnable != null) {                  mRunnable.run();              }         }

在它的run方法中,如果访问成功会调用mRequest.deliverResponse(mResponse.result)方法,到这里就很明了了,因为每个request子类中都要重写deliverResponse,最后我们再在这个方法中将响应的数据回调到Response.Listener的onResponse()方法中就可以了。以StringRequest为例:

 @Override      protected void deliverResponse(String response) {          mListener.onResponse (response);      }

分析完缓存,我们来看下网络加载。它是在NetworkDispatcher线程中实现的。

public class NetworkDispatcher extends Thread {      /** The queue of requests to service. */      private final BlockingQueue<Request<?>> mQueue;      /** The network interface for processing requests. */      private final Network mNetwork;      /** The cache to write to. */      private final Cache mCache;      /** For posting responses and errors. */      private final ResponseDelivery mDelivery;      /** Used for telling us to die. */      private volatile boolean mQuit = false;        /** * Creates a new network dispatcher thread. You must call {@link #start()} * in order to begin processing. * * @param queue Queue of incoming requests for triage * @param network Network interface to use for performing requests * @param cache Cache interface to use for writing responses to cache * @param delivery Delivery interface to use for posting responses */      public NetworkDispatcher(BlockingQueue<Request<?>> queue,              Network network, Cache cache,              ResponseDelivery delivery) {          mQueue = queue;          mNetwork = network;          mCache = cache;          mDelivery = delivery;      }   @Override      public void run() {          Process.setThreadPriority(Process. THREAD_PRIORITY_BACKGROUND);          Request<?> request;          while ( true) {              try {                  // Take a request from the queue.                  request = mQueue.take();              } catch (InterruptedException e) {                  // We may have been interrupted because it was time to quit.                  if ( mQuit) {                      return;                  }                  continue;              }                try {                  request.addMarker( "network-queue-take" );                    // If the request was cancelled already, do not perform the                  // network request.                  if (request.isCanceled()) {                      request.finish( "network-discard-cancelled" );                      continue;                  }                    addTrafficStatsTag(request);                    // Perform the network request.                  NetworkResponse networkResponse = mNetwork.performRequest(request);                  request.addMarker( "network-http-complete" );                    // If the server returned 304 AND we delivered a response already,                  // we're done -- don't deliver a second identical response.                  if (networkResponse. notModified && request.hasHadResponseDelivered()) {                      request.finish( "not-modified");                      continue;                  }                    // Parse the response here on the worker thread.                  Response<?> response = request.parseNetworkResponse(networkResponse);                  request.addMarker( "network-parse-complete" );                    // Write to cache if applicable.                  // TODO: Only update cache metadata instead of entire record for 304s.                  if (request.shouldCache() && response. cacheEntry != null ) {                      mCache.put(request.getCacheKey(), response.cacheEntry );                      request.addMarker( "network-cache-written" );                  }                    // Post the response back.                  request.markDelivered();                  mDelivery.postResponse(request, response);              } catch (VolleyError volleyError) {                  parseAndDeliverNetworkError(request, volleyError);              } catch (Exception e) {                  VolleyLog. e(e, "Unhandled exception %s", e.toString());                  mDelivery.postError(request, new VolleyError(e));              }          }      }  }

首先取出请求;然后通过mNetwork.performRequest(request)处理我们的请求,拿到NetworkResponse。看下performRequest方法:

 public NetworkResponse performRequest(Request<?> request) throws VolleyError {            long requestStart = SystemClock.elapsedRealtime();            while (true) {                HttpResponse httpResponse = null;                byte[] responseContents = null;                Map<String, String> responseHeaders = new HashMap<String, String>();                try {                    // Gather headers.                   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());                    // Handle cache validation.                   if (statusCode == HttpStatus.SC_NOT_MODIFIED) {                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,                                request.getCacheEntry() == null ? null : request.getCacheEntry().data,                                responseHeaders, true);                    }                    // Some responses such as 204s do not have content. We must check.                   if (httpResponse.getEntity() != null) {                      responseContents = entityToBytes(httpResponse.getEntity());                    } else {                      // Add 0 byte response as a way of honestly representing a                     // no-content request.                     responseContents = new byte[0];                    }                    // if the request is slow, log it.                   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);                } catch (Exception e) {                    ……                }            }        }  

上面方法主要是网络请求的一些细节,所以如果要修改请求的细节就要到此处修改(后面会讲到)。
在这里服务器会返回的数据组装成一个NetworkResponse对象进行返回。在NetworkDispatcher中收到了NetworkResponse这个返回值后又会调用Request的parseNetworkResponse()方法来解析NetworkResponse中的数据,再将数据写入到缓存。parseNetworkResponse的实现是交给Request的子类来完成的,不同种类的Request解析的方式不同。如json与gson就有区别。最后与CacheDispatcher一样调用mDelivery.postResponse(request, response)返回回调,这里就不再分析了。
到这里volley的源码就分析完了,总结一下:

  • 首先初始化RequestQueue,主要就是开启CacheDispatcher和NetworkDispatcher线程,线程会不断读取请求,没有消息则阻塞。
  • 当我们发出请求以后,会根据url,ImageView属性等,构造出一个cacheKey,然后首先从LruCache中获取,这个缓存我们自己构建的,这就是内存缓存;如果没有取到,则判断是否存在硬盘缓存,这一步是从getCacheDir里面获取(默认5M);如果没有取到,则从网络请求;

Volley的扩展

添加cookie头

volley跟httpClient不一样,它是不会自动添加cookie头的。但是cookie在应用中却很重要,它会保证登陆后的操作都处于一个会话中,有效的增加了安全性。那么如何在volley中自动添加cookie呢。
首先在新建Appliaction,当成全局的Application,然后在里面编写在http头参数中识别出cookie和添加cookie到Http头代码。

/** * Checks the response headers for session cookie and saves it * if it finds it. * @param headers Response Headers. */      public static final void checkSessionCookie(Map<String, String> headers) {          Log.e("TAG", "checkSessionCookie->headers:" + headers);            if (headers.containsKey(GlobalParams.SET_COOKIE_KEY) && headers.get(GlobalParams.SET_COOKIE_KEY).startsWith(GlobalParams.SESSION_COOKIE)) {              String cookie = headers.get(GlobalParams.SET_COOKIE_KEY);              if (cookie.length() > 0) {                  //形如Set-Cookie:JSESSIONID=18D6BCC01453C6EB39BB0C4208F389EE; Path=/smdb                  //进行解析,取出JSESSIONID的value                  String[] splitCookie = cookie.split(";");                  String[] splitSessionId = splitCookie[0].split("=");                  cookie = splitSessionId[1];                  Editor prefEditor = preferences.edit();                  prefEditor.putString(GlobalParams.SESSION_COOKIE, cookie);                  prefEditor.commit();              }          }else {              if (null != httpclient.getCookieStore()) {                  List<Cookie> cookies = httpclient.getCookieStore().getCookies();                  for (Cookie cookie : cookies) {                      if ("JSESSIONID".equals(cookie.getName())) {//取得session的value                          String sessionId = cookie.getValue();                          Editor prefEditor = preferences.edit();                          prefEditor.putString(GlobalParams.SESSION_COOKIE, sessionId);                          prefEditor.commit();                          break;                      }                  }                  if (!cookies.isEmpty()) {                      for (int i = 0; i < cookies.size(); i++) {                          cookie = cookies.get(i);//保存cookie的信息使得HttpClient和WebView共享同一个cookie                      }                  }              }          }      }

接着就要在Request的子类中合适地方添加头信息,哪个地方合适。我们来看下HurlStack的performRequest方法。

@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);          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;      }

重点看到map.putAll(request.getHeaders());所以我们考虑到如果要给它添加头信息可以在request的getHeaders()方法中添加。至此我们以StringRequest为例,重写一个类叫MyStringRequest:

public class MyStringRequest extends StringRequest {        private final Map<String, String> mParams;      /** * @param method * @param url * @param params * A {@link HashMap} to post with the request. Null is allowed * and indicates no parameters will be posted along with request. * @param listener * @param errorListener */      public MyStringRequest(int method, String url, Map<String, String> params, Listener<String> listener,              ErrorListener errorListener) {          super(method, url, listener, errorListener);          mParams = params;      }        @Override      protected Map<String, String> getParams() {          return mParams;      }        /* (non-Javadoc) * @see com.android.volley.toolbox.StringRequest#parseNetworkResponse(com.android.volley.NetworkResponse) */      @Override      protected Response<String> parseNetworkResponse(NetworkResponse response) {          // since we don't know which of the two underlying network vehicles          // will Volley use, we have to handle and store session cookies manually          Log.e("TAG", "parseNetworkResponse->response.headers:" + response.headers);          GlobalApplication.checkSessionCookie(response.headers);          return super.parseNetworkResponse(response);      }        /* (non-Javadoc) * @see com.android.volley.Request#getHeaders() */      @Override      public Map<String, String> getHeaders() throws AuthFailureError {          Map<String, String> headers = super.getHeaders();              if (headers == null || headers.equals(Collections.emptyMap())) {                  headers = new HashMap<String, String>();              }              GlobalApplication.addSessionCookie(headers);          return headers;      }    }

在parseNetworkResponse中调用checkSessionCookie解析头信息中的cookie,然后重写getHeaders方法,调用addSessionCookie添加cookie。

添加重定向功能

网络访问经常要用到重定向,虽说在客户端中用得比较少。那Volley能不能进行自动重定向,答案是可以的,重要修改下源码。既然要重定向,那就要在请求返回的进行判断,毫无疑问要在BasicNetwork的performRequest中修改,先看下修改后的代码:

 @Override      public NetworkResponse performRequest(Request<?> request) throws VolleyError {          long requestStart = SystemClock.elapsedRealtime();          while (true) {              HttpResponse httpResponse = null;              byte[] responseContents = null;              Map<String, String> responseHeaders = new HashMap<String, String>();              try {                  // Gather headers.                  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());                  // Handle cache validation.                  if (statusCode == HttpStatus.SC_NOT_MODIFIED) {                      return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,                              request.getCacheEntry() == null ? null : request.getCacheEntry().data,                              responseHeaders, true);                  }                    // Handle moved resources                  //Line143-148为解决301/302重定向问题增加的代码。                  //参考见https://github.com/elbuild/volley-plus/commit/4a65a4099d2b1d942f4d51a6df8734cf272564eb#diff-b4935f77d9f815bb7e0dba85e55dc707R150                  if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {                      String newUrl = responseHeaders.get("Location");                      request.setRedirectUrl(newUrl);                  }                    // Some responses such as 204s do not have content. We must check.                  if (httpResponse.getEntity() != null) {                    responseContents = entityToBytes(httpResponse.getEntity());                  } else {                    // Add 0 byte response as a way of honestly representing a                    // no-content request.                    responseContents = new byte[0];                  }                    // if the request is slow, log it.                  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);              } 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);                  }                  //Line143-148为解决301/302重定向问题增加的代码。                  if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||                           statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {                      VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());                  } else {                      VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());                  }                  if (responseContents != null) {                      networkResponse = new NetworkResponse(statusCode, responseContents,                              responseHeaders, false);                      if (statusCode == HttpStatus.SC_UNAUTHORIZED ||                              statusCode == HttpStatus.SC_FORBIDDEN) {                          attemptRetryOnException("auth",                                  request, new AuthFailureError(networkResponse));                      } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||                                   statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {                          //else if语句为解决301/302重定向问题增加的代码。设置重连请求。                           attemptRetryOnException("redirect",                                  request, new AuthFailureError(networkResponse));                      } else {                          // TODO: Only throw ServerError for 5xx status codes.                          throw new ServerError(networkResponse);                      }                  } else {                      throw new NetworkError(networkResponse);                  }              }          }      }

其实重点添加了以下的代码:

 if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {                      String newUrl = responseHeaders.get("Location");                      request.setRedirectUrl(newUrl);                  }

上面的代码就是判断返回code是否是301或302,如果是就获取重定向的Url,再设置重定向,很简单。到此Volley常见的扩展功能就讲完了。
源码解析的文章都会有点长,写完也要有耐心。继续坚持。