Volley框架使用与源码解析(一)

Volley是Google推出的异步网络请求和异步图片加载框架,Volley适合数据量小网络请求频繁的操作。
Volley的特点:
1,代码扩展性强,很多类都是基于接口设计的,自己可以重写
2,内部做了缓存处理,可以重写缓存策略
3,网络请求引擎主要用HttpURLConnection、HttpClient
4,可以实现小文件和图片加载
Android Developer上google对于volley框架的核心流程图
这里写图片描述
对于这个图大致解释下流程吧
Request添加到RequestQueue中,RequestQueue内部维护了两个线程调度一个缓存线程CacheDispatcher,对request请求在缓存中查找Response,如果有结果就解析成相应的数据类型然后通过ResponseDelivery将结果派发到主线程中,如果缓存中没有Response,那么通过NetworkDispatcher线程通过httpstack这个引擎去服务器请求数据,得到结果后进行解析,并且将结果写入到缓存中,最后通过ResponseDelivery将信息派发到主线程中。

接下来就直接上代码吧,跟着代码走可能比较好,先介绍一下volley的基本用法。

RequestQueue mQueue=Volley.newRequestQueue(context);
 StringRequest request=new StringRequest(url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
           //todo
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
               //todo
            }
        });
        mQueue.add(request);

这个就是Volley的最基本用法,是不是特别简单,在回调接口Listener中对结果做相应的业务处理就行了。虽然看似简单的几行代码,实际上这中间的过程还是挺复杂的,接下来就开始从源码角度慢慢深入去学习Volley的整个过程了。

RequestQueue mQueue=Volley.newRequestQueue(context);
接下来看Volley.java中的源码
  public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (HttpStack)null);
    }
    继续调用下面的重载构造函数
     public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), "volley");
        String userAgent = "volley/0";

        try {
            String network = context.getPackageName();
            PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);
            userAgent = network + "/" + queue.versionCode;
        } catch (NameNotFoundException var6) {
            ;
        }

        if(stack == null) {
            if(VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
        RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
        queue1.start();
        return queue1;
    }

构造RequestQueue的时候传进去了一个context,默认会将HttpStack设置为null,然后会构建一个缓存文件,路径是getCacheDir(),接下来根据Android版本构造httpstack,HurlStack,HttpClientStack,具体可以去看源码,前面那个是用HttpURLConnection,后面那个用HttpClient,然后将httpstack封装到BasicNetwork中,BasicNetwork是个Network,来看下Network这个接口,因为这个很重要,是做网络请求用的

Network.java    interface
public interface Network {
    NetworkResponse performRequest(Request<?> var1) throws VolleyError;
}
实现这个接口的类都要实现performRequest这个方法,看名字就知道根据request去服务器请求数据,返回NetworkResponse

后面会说到这个网络请求类的,现在继续往下看,接下来就构造一个RequestQueue了,将刚才构造的Network传到构造函数中

  RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
        queue1.start();
  看这两句代码,queue的构造函数中有个DiskBasedCache实例,看名字应该知道这是个本地缓存的类,看源码吧
  DiskBasedCache.java
  这个类是实现Cache接口的,而Cache接口内部维护了一个静态内部类Entry
  看下Cache接口里面的Entry内部类吧
   public static class Entry {
        public byte[] data;
        public String etag;
        public long serverDate;
        public long ttl;
        public long softTtl;
        public Map<String, String> responseHeaders = Collections.emptyMap();
        。。。。
        }
        这里的data实际上就存了Response的数据,这里是byte类型。

 接下来看下DiskBaseeCache类的构造函数吧,

    public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, 5242880);
    }

         public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        this.mEntries = new LinkedHashMap(16, 0.75F, true);
        this.mTotalSize = 0L;
        this.mRootDirectory = rootDirectory;
        this.mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

    这里说明了缓存文件大小默认是5M

接下来看下RequestQueue的构造函数
RequestQueue.java

    public RequestQueue(Cache cache, Network network) {
        this(cache, network, 4);
    }

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

       public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
        this.mSequenceGenerator = new AtomicInteger();
        this.mWaitingRequests = new HashMap();
        this.mCurrentRequests = new HashSet();
        this.mCacheQueue = new PriorityBlockingQueue();
        this.mNetworkQueue = new PriorityBlockingQueue();
        this.mCache = cache;
        this.mNetwork = network;
        this.mDispatchers = new NetworkDispatcher[threadPoolSize];
        this.mDelivery = delivery;
    }

RequestQueue构造函数指定了线程池大小为4,构造了两个队列,mCacheQueue缓存队列,mNetworkQueue网络请求队列,mCache就是之前那个DiskBasedCache实例,同时有个很重要的NetworkDispatcher类型的数组,这个NetworkDispatcher继承了Thread,所以他是个线程,不断从网络请求队列中获取request进行网络请求,如果队列为空就阻塞等待,这个线程很重要,后面会具体分析他是怎么工作的,mDelivery就是一个Response消息派发类,可以去看它的源码,里面有个handler,可以通过这个handler将消息派发到主线程的Runnable的run方法里处理。构造好RequestQueue后看下面的代码,就是那句queue1.start();我们继续RequestQueue的start函数吧

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

    }

这个start函数首先去执行stop函数,去停止正在执行的线程,主要有缓存线程和网络请求线程,看源码

    public void stop() {
        if(this.mCacheDispatcher != null) {
            this.mCacheDispatcher.quit();
        }

        for(int i = 0; i < this.mDispatchers.length; ++i) {
            if(this.mDispatchers[i] != null) {
                this.mDispatchers[i].quit();
            }
        }

    }

start函数主要做的事情其实很简单,就是将一些必要的信息传进到CacheDispatcher和NetworkDispatcher这两个线程调度类里面,然后start这两个线程去执行request的请求数据操作。这个RequestQueue实例完成后,就等着add进来request了,所以接下来就new一个request然后加到这个RequestQueue里面去。
接下来看下request的构造了,Volley提供了Request这个抽象类,Volley里面的请求都要是实现了Request这个抽象类才行,框架给我们实现了StringRequest,JsonRequest,ImageRequest等等,具体可以看源码,当然可以自己继承Request重写一些方法。这里暂时以StringRequest为例子,new StringRequest的时候传入了一个url和回调接口,具体源码后面我们再看,这里我们只要知道这是StringRequest类型请求内部有个回调接口就行,然后执行mQueue.add(request);这一步,我们看源码

public Request add(Request request) {
        request.setRequestQueue(this);
        Set var2 = this.mCurrentRequests;
        synchronized(this.mCurrentRequests) {
            this.mCurrentRequests.add(request);
        }

        request.setSequence(this.getSequenceNumber());
        request.addMarker("add-to-queue");
        if(!request.shouldCache()) {
            this.mNetworkQueue.add(request);
            return request;
        } else {
            Map var7 = this.mWaitingRequests;
            synchronized(this.mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                if(this.mWaitingRequests.containsKey(cacheKey)) {
                    Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
                    if(stagedRequests == null) {
                        stagedRequests = new LinkedList();
                    }

                    ((Queue)stagedRequests).add(request);
                    this.mWaitingRequests.put(cacheKey, stagedRequests);
                    if(VolleyLog.DEBUG) {
                        VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
                    }
                } else {
                    this.mWaitingRequests.put(cacheKey, (Object)null);
                    this.mCacheQueue.add(request);
                }

                return request;
            }
        }
    }

这个代码有点长,我们慢慢分析,对将当前的requesQueue设置到request中去,将当前的request添加到mCurrentRequest里,这个mCurrentRequst是个set集合,保存当前的request用的。然后这里有句判断语句
if(!request.shouldCache()) {
this.mNetworkQueue.add(request);
return request;
} else {。。。}
就是说如果request不需要缓存那么就直接将这个request添加到mNetworkQueue队列里面,这个mNetworkQueue是个AbstractQueue类型,线程NetworkDispatcher就是从这个队列取request的。这里我们不执行if里面的语句,因为request的shouldCache方法默认返回的true,所以执行else里面的代码,我们看else里面的源码。这个逻辑其实很简单,mWaitingRequests是个hashmap,key=url,value是个list类型,list里面存的就是request,如果mWaitingRequests里面有request对于的url且list为空表示当前request已经放入请求队列但是还没有finish完成,如果list不为空说明当前request还没有放入请求队列且重复请求该request了,如果mWaitingRequests里面没有该request的url说明这是第一次请求,直接将request放入线程调度的那个队列里面随时等待线程获取request。那么线程是如何获取队列里面的request然后又如何进行request请求,最后又是如何将结果返回给当初request里面设置的回调接口呢,接下来的是最最最核心的东西了。我们在前面的生成RequestQueue的时候调用了RequestQueue的start方法,还记得吗,里面就是启动CacheDispatcher和NetworkDispatcher这两个线程,我们先以CacheDispatcher这个线程为例,看下他的源码

CacheDispatcher.java
我们知道Thread.start()方法后回去执行线程的run()方法里面的程序,所以我们直接看这个类的run方法就行了
  public void run() {
  ...省略一些代码了
        Process.setThreadPriority(10);
        this.mCache.initialize();
        /*这里的mCache就是我们之前在new RequestQueue的时候传的
        DiskBasedCache实例,初始化获取缓存文件的一些头部信息
        以便后面用到,比如是否已经过期之类的*/
        下面的代码比较多省略了一些主要看关键的代码
       ....省略很多代码
     while(true) {
 final Request e = (Request)this.mCacheQueue.take();
 ...
  if(e.isCanceled()) {
 e.finish("cache-discard-canceled");} 
 else {
    Entry entry = this.mCache.get(e.getCacheKey());
         if(entry == null) {
              e.addMarker("cache-miss");
             this.mNetworkQueue.put(e);
             } 
         else if(entry.isExpired()) {
             e.addMarker("cache-hit-expired");
             e.setCacheEntry(entry);
             this.mNetworkQueue.put(e); }
         else {
              e.addMarker("cache-hit");
              Response response =e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
             e.addMarker("cache-hit-parsed");
             if(entry.refreshNeeded()) {
                e.addMarker("cache-hit-refresh-needed");
               e.setCacheEntry(entry);
               response.intermediate = true;
                     this.mDelivery.postResponse(e, response, new Runnable() {
   public void run() {
            try {
         CacheDispatcher.this.mNetworkQueue.put(e);
                 } catch (InterruptedException var2) {}
                     }
                        });else {
                  this.mDelivery.postResponse(e, response);
                                  }
                                    }
                                }
                            }

看上面的if else代码确实有点头疼,但是逻辑思路其实不复杂的。从mCacheQueue中取出request,然后判断该request是否取消了,如果取消了那么就会去调用RequestQueue的finish方法,该方法就是从mCurrentRequests删除,然后再去mWaitingRequests中查下该还有没有该url对应的request,如果有的就再次把这个request请求添加到mCacheQueue中,准备再次请求,所以就是说request 的取消操作只是针对该request而已。如果没有取消request那么就会先从缓存文件夹里面去获取是否存在该request的response,前面说了response被封装在Entry这个类里面,Cache里面有个内部类Entry用来存response的头部信息和主要信息的,如果这个entry为空说明缓存里面是没有的,那么就将该request添加到mNetworkQueue队列里面,让NetworkDispatcher线程去执行网络请求操作了,如果entry不为空,那么说明缓存里面有该request的response,那么这个时候就判断该response是否过期了,这个过期判断一般是个时间判断,默认的是根据头部信息的max-Age的值乘以1000s来算的,如果过期了那么也将request放到mNetworkQueue里面去执行网络请求操作,如果没有过期那么就会使用缓存里面的response,执行下面的关键代码

 Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));

调用request里面的parseNetworkResponse方法,这个方法作用是将NetworkResponse类型转成Response类型,NetworkResponse类型就是将response的data和头部信息封装起来了,我们看下StringRequest的相关方法

 StringRequest.java
  protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException var4) {
            parsed = new String(response.data);
        }

        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

 public static <T> Response<T> success(T result, Entry cacheEntry) {
        return new Response(result, cacheEntry);
    }

    public static <T> Response<T> error(VolleyError error) {
        return new Response(error);
    }


    private Response(T result, Entry cacheEntry) {
        this.result = result;
        this.cacheEntry = cacheEntry;
        this.error = null;
    }

通过StringRequest的解析Response之后返回一个Response类型的实例,里面存了我们所需要的数据类型T和数据的头部信息,而这个数据类型T可以自己定义比如String,Json,Image,XML等等,目前我们是String类型作为例子的。我们继续关注CacheDispatcher类run里面的方法,得到response之后,判断entry是否需要更新,如果需要更新那么将entry添加到当前request中去,更新的意思就是说目前的response暂时用缓存里面的response,但是可能需要去做网络请求从服务器上重新获得信息,着重看下这个代码

this.mDelivery.postResponse(e, response, new Runnable() {
  public void run() {
  try {  CacheDispatcher.this.mNetworkQueue.put(e);
} catch (InterruptedException var2) {} }
});

mDelivery这个类型还记得吗,是ResponseDelivery类型,做response消息派发的,这个是在我们new一个RequestQueue的构造函数的时候实例化的,且传进去了一个拥有主线程looper的handler,这就说明这个类使会将数据post到主线程的message队列里面,在主线程完成回调接口的,这也符合当时我们new StringRequest的时候new的一个回调接口确实在主线程啊。我们看下实现了ResponseDelivery接口的ExecutorDelivery的postResponse方法源码

ExecutorDelivery.java
/*先看下ExecutorDelivery的构造函数mResponsePoster 是个Executor类型,重写了execute方法,执行handler的post方法,这个handler我之前博客里面说过,将runnable类型对象发到message队列,然后looper取出来执行runnable对象的run方法*/
   public ExecutorDelivery(final Handler handler) {
        this.mResponsePoster = new Executor() {
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

 public void postResponse(Request<?> request, Response<?> response) {
        this.postResponse(request, response, (Runnable)null);
    }

    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
    }
    /*我们看下ResponseDeliveryRunnable这个内部类的定义*/

   private class ResponseDeliveryRunnable implements Runnable {
   ....省略一些代码关键看run这个方法

        public void run() {
            if(this.mRequest.isCanceled()) {
                this.mRequest.finish("canceled-at-delivery");
            } else {
                if(this.mResponse.isSuccess()) {
                    this.mRequest.deliverResponse(this.mResponse.result);
                } else {
                    this.mRequest.deliverError(this.mResponse.error);
                }

                if(this.mResponse.intermediate) {
                    this.mRequest.addMarker("intermediate-response");
                } else {
                    this.mRequest.finish("done");
                }

                if(this.mRunnable != null) {
                    this.mRunnable.run();
                }

            }
        }
    }

上面的代码贴出来是更好的看,我继续从CacheDispatcher里面说到的entry需要更新开始,然后它就会调用mDelivery.postResponse这个方法,传进去了request,response,还有一个runnable,接着会调用ExecutorDelivery类里面的Executor类型的mResponsePoster的execute方法,就会将ResponseDeliveryRunnable 类型的对象作为参数通过handler的post方法传到主线程,接下来在主线程中通过looper获取该message,找到该handler然后执行handler里面的runnable类型对象的run方法了,这里就会在主线程执行ResponseDeliveryRunnable 的run里面的代码了,看下面代码

/*如果response成功了,就执行request的deliverResponse方法,将response的result类型是个泛型T,跟我们请求类型有关,这里是String,可以去看Response源码,不成功就执行deliverError方法,逻辑比较简单*/
  if(this.mResponse.isSuccess()) {
                    this.mRequest.deliverResponse(this.mResponse.result);
                } else {
                    this.mRequest.deliverError(this.mResponse.error);
                }
 /*因为deliverError这个方法是在Request里面的,我们先去看下request的方法你就很清楚了*/
 StringRequest.java
     protected void deliverResponse(String response) {
        this.mListener.onResponse(response);//是不是很眼熟,就是我们在构建StringRequest的时候new的回调接口
    }
    Request.java
      public void deliverError(VolleyError error) {
        if(this.mErrorListener != null) {
            this.mErrorListener.onErrorResponse(error);//这个也是设置的回调接口啊
        }
    }

这样代码就会执行到我们在一开始的时候设置的回调接口中了,我们可以将response取出来做相应的逻辑处理了。我们再回过头看ResponseDeliveryRunnable 这个类的run方法最后还有一句 if(this.mRunnable != null) {
this.mRunnable.run();
}

这个mRunnable就是postResponse的时候的第三个参数,如果设置了就要执行该参数的run方法,不设置就不管了,而刚才调这里的时候是传了一个参数的,当时的代码

 this.mDelivery.postResponse(e, response, new Runnable() {
        public void run() { try {                              CacheDispatcher.this.mNetworkQueue.put(e);} 
        catch (InterruptedException var2) {}
 }});
 这里还要去执行CacheDispatcher.this.mNetworkQueue.put(e)这句
 将request添加进mNetworkQueue队列取做网络请求操作。

这样关于一个request加入到一个RequestQueue然后从缓存文件中找到response然后进行解析成Response类型,最后通过ResponseDelivery类将response派发到主线程,调用回调接口进行我们的逻辑处理,这个整个过程已经讲完了,这里只讲了缓存线程的过程,还没有涉及到网络线程的东西,下一篇着重讲解NetworkDispatcher和httpstack的工作原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值