Volley有如下优点
1. 自动调度网络请求
2. 多并发请求 (源于开了多个线程)
3. 本地Cache自动缓存网络请求结果
4. 支持设置请求优先级
5. 支持取消单个请求或者取消所有请求
6. 易于定制请求(比如:自定义重试机制,自定义Request请求等)
7. 提供完善的Log打印跟踪工具

Google的一张Volley原理图来简单解释下Volley的工作原理。

Volley请求处理是一个异步的过程:

1.在主线程中按照请求的优先级把Request添加到本地缓存队列CacheQueue中,
2.缓存分发器CacheDispatcher轮询本地是否已经缓存了这次请求的结果
3.如果命中,则从缓存中读取数据并且解析。解析完的结果被ResponseDelivery 分发到主线程中。
4.如果没有命中,则将这次请求添加到网络请求队列NetworkQueue中,
5.网络分发器NetworkDispatcher处理网络请求,获取请求结果并解析同时把结果写入缓存。解析完的结果被分发到主线程中。

  • 主线程:所有的请求结果都会被分发到主线程。
  • 缓存线程:专门有一个线程用于读取本地缓存。
  • 网络线程:Volley默认开启4个线程去处理网络请求。

两个分发器、两个队列、五个线程

Volley初始化以后就创建了5个后台线程(1个缓存线程和4个网络线程来处理Request请求)在处理请求。只要你没做处理,这5个线程一直在后台跑。为了节省资源,在同一个App中最好使用同一个单例Volley RequestQueue队列来处理所有请求,以免创建过多线程浪费资源。还有在退出这个应用时,应该调用 RequestQueue#stop方法来干掉所有Volley线程。如此才是使用Volley最优雅的方式

从源码角度理解Volley工作原理

Volley最基本的使用代码如下(开发者用法):

//创建请求队列
RequestQueue mQueue = Volley.newRequestQueue(context);
//构建一个Request请求
StringRequest request = new StringRequest(url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
//将请求添加到队列中
mQueue.add(request);

我们来看看Volley#newRequestQueue()方法如何实现的?

Volley类

public class Volley {

    /**默认缓存目录 */
private static final String DEFAULT_CACHE_DIR = "volley"; 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) {//API>=9使用HttpURLConnection访问网络
stack = new HurlStack();
} else {//API<9时使用HttpClient访问网络
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//创建网络访问类
Network network = new BasicNetwork(stack);
//创建请求队列
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
//开始执行队列中的任务
queue.start();
return queue;
} /**静态方法创建请求队列*/
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
}
---------------------

代码第8行: 创建一个默认的缓存文件目录,该路径在应用的私有目录data/data/your_package/cache/volley/ 下。

代码28行:创建一个网络请求队列RequestQueue 对象,然后调用start()方法启动执行队列中的任务。那么RequestQueue#start()方法到底做了什么?接下来分析下RequestQueue类的实现。

RequestQueue

public class RequestQueue {

    /** 用于标识Request的编号. */
private AtomicInteger mSequenceGenerator = new AtomicInteger(); /**保存添加到RequestQueue队列中相同key的请求*/
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>(); /**保存当前所有添加到RequestQueue队列中的请求*/
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>(); /** 带有优先级的缓存请求队列. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>(); /** 带有优先级的网络请求队列. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>(); /** 默认开启4个线程处理网络请求 */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; /** 本地缓存,用于保存网络请求结果 */
private final Cache mCache; /** 用于执行网络请求. */
private final Network mNetwork; /** 请求结果分发器. */
private final ResponseDelivery mDelivery; /** 网络处理请求分发器. */
private NetworkDispatcher[] mDispatchers; /** 本地缓存处理请求分发器. */
private CacheDispatcher mCacheDispatcher; public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
} public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
} public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
} /**
* 启动队列中的任务调度
*/
public void start() {
stop(); //停止当前所有任务.
// 创建缓存任务调度器,并且启动它
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start(); // 创建默认个数的网络任务调度器,并且启动它.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
} /**
* 停止缓存和网络调度
*/
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
} /**
* 获取队列编号.
*/
public int getSequenceNumber() {
return mSequenceGenerator.incrementAndGet();
} /**
* 获取本地缓存.
*/
public Cache getCache() {
return mCache;
} /**
完成一次请求,当该请求被执行结束或者该请求被取消时调用该方法
*/
<T> void finish(Request<T> request) {
// 从当前队列中移除该请求,标志着该请求得到执行。
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
synchronized (mFinishedListeners) {
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
//如果该请求允许有缓存,则将等待队列中的所有的请求任务全部添加到缓存队列中继续执行。
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
//处理等待队列中所有的请求.
mCacheQueue.addAll(waitingRequests);
}
}
}
}
---------------------

RequestQueue#start方法
有上面的代码可知:start方法中创建了一个CacheDispatcher缓存调度处理器和4个NetworkDispatcher网络调度处理器,而他们都是继承自Thread线程的,所以这里创建了1个缓存线程和4个网络线程来处理Request请求。相当于此处启动了5个线程来处理请求,这就是为什么Volley框架支持多并发请求了。那么我们看看它们都做了些什么??
--------------------- ------------------------------------------------------------

CacheDispatcher类(缓存分发器)

public class CacheDispatcher extends Thread {
/** 缓存阻塞队列. */
private final BlockingQueue<Request<?>> mCacheQueue; /** 网络阻塞队列. */
private final BlockingQueue<Request<?>> mNetworkQueue; /** 本地缓存. */
private final Cache mCache; /** 结果分发器. */
private final ResponseDelivery mDelivery; /** 标记当前线程是否死亡. */
private volatile boolean mQuit = false; public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
} /**退出当前线程*/
public void quit() {
mQuit = true;
interrupt();
} @Override
public void run() {
//设置该线程的优先级为后台线程 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //初始化本地缓存.
mCache.initialize();
//死循环
while (true) {
try {
//从阻塞的缓存队列中取出一个请求.
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take"); // 如果该请求被取消,就不处理该请求
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;//继续下次循环,等待下一个请求
} //试图从本地缓存中读取本次请求结果.
Cache.Entry entry = mCache.get(request.getCacheKey());
//本地没有命中则将该请求投放到网络请求队列中处理
if (entry == null) {
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;//结束本次循环
} // 本地命中,但是过期了,也需再次将请求投放到网络请求队列中
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;//结束本次循环
} // 本地缓存命中该请求以后解析该请求
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) {
// 本地缓存命中且没有过期,则将解析的结果发送到主线程中.
mDelivery.postResponse(request, response);
} else {
// 本地缓存命中,但需要刷新,重新将这次请求投放到网络请求队列中
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry); // Mark the response as intermediate.
response.intermediate = true;
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) {
// 此处用于退出当前线程,当该线程发生中断异常时执行.
if (mQuit) {
return;
}
continue;
}
}
}
}
---------------------

解析:CacheDispatcher类继承自Thread(缓存线程),实现了run方法,在run方法中写了一个while(true)死循环,用于一直读取缓存队列中的请求任务(轮询)。因为缓存队列mCacheQueue是一个阻塞队列,所以只有队列不为空时while循环才会取出下一个新的请求任务执行,否则while循环一直阻塞直到有新任务添加进来。

run方法实现的逻辑是:先从本地缓存中去读本次请求,如果该请求命中本地缓存且缓存未过期,则解析结果并且由分发器ResponseDelivery 将结果发送到主线程中。如果本地没有命中或者命中的请求过期了,则将该请求投放到网络请求队列中,由NetworkDispatcher来处理网络请求。

--------------------- ----- ------------------------------------------------------------

NetworkDispatcher类(网络分发器)

public class NetworkDispatcher extends Thread {

..................
@Override
public void run() {
//设置线程优先级为后天线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 从队列中取出一个请求任务.
request = mQueue.take();
} catch (InterruptedException e) {
// 当线程发生中断异常时,判断该线程是否死亡?如果死亡则结束循环,否则跳出本次循环继续等待下一个请求任务到来。
if (mQuit) {
return;
}
continue;
} try {
request.addMarker("network-queue-take"); // 该Request请求如果被取消,则跳出本次循环,结束本地请求处理
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
} addTrafficStatsTag(request); // 执行网络请求.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete"); // 相同的结果不发送第二次
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
} // 在工作线程中解析网络结果.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete"); // 将结果保存到缓存中.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
request.markDelivered();
//将结果发送到主线程中
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
//将错误结果发送到主线程中
mDelivery.postError(request, volleyError);
}
}
} ...........
---------------------

解析:NetworkDispatcher 类同样继承自Thread,实现了其run方法。在该方法中写了一个while(true)死循环,用于读取网络队列中的Request请求任务,同样由于网络队列也是一个阻塞队列,所以当队列不为空就取出一个Request任务,然后将该任务值执行网络请求,并且解析请求结果,在得到网络请求结果以后首先将结果保存到本地缓存,然看结果将由分发器ResponseDelivery发送到主线程中。
--------------------- ----------------------------------------------

到此,RequestQueue#start方法分析结束,总结起来如下:Volley会创建一个RequestQueue对象,该对象会创建一个Cache对象用于保存请求结果,创建一个带有优先级以及阻塞的缓存队列mCacheQueue用于保存用户添加的请求,创建一个CacheDispatcher线程调度器来轮询缓存队列mCacheQueue执行请求任务。创建了4个带有优先级和阻塞的网络队列mNetWorkQueue用于保存没有命中本地缓存的请求,匹配的也创建了4个NetworkDispatcher线程调度器来轮询mNetWorkQueue队列执行请求任务。
--------------------- -------------------- ----------------------------------------------

RequestQueue#add()

........

 /**添加一个请求到带有分发器的队列中*/
public <T> Request<T> add(Request<T> request) {
//Request请求和请求队列关联
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
//Request请求添加到当前请求队列中
mCurrentRequests.add(request);
} // 给该请求设置一个顺序编号.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue"); // 如果该请求不需要缓存,则将Request请求直接添加至网络请求队列中.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
} // 以下代码处理缓存请求等待队列.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//如果等待队列中已经存在该请求的key,则说明此时有一个相同的请求正在被处理,因此将该request放在等待队列中,等待前一个request处理完之后在finish方法中处理。
if (mWaitingRequests.containsKey(cacheKey)) {
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 {
//如果等待队列中没有改请求的key,则将之添加到等待队列,注意:添加到等待队列中的value 是一个null,目的用于标记该请求在等待队列中且处于正在被处理的状态。
mWaitingRequests.put(cacheKey, null);
//添加都缓存队列中让该请求得到相应的执行。
mCacheQueue.add(request);
}
return request;
}
}
........

代码第13行:给当前请求设置一个编号,后面将用于设置请求的优先级,这里暂且不详细讲解。
代码第17-20行:判断当前请求是否允许本地缓存,如果不允许,则直接将本次请求添加到网络请求队列中。否则添加到缓存请求队列中。
代码26-35行:判断请求等待队列中是否包含本次请求的key,如果包含,则说明有相同的请求正在被执行,此时将该请求放入到等待队列中,等待上一个请求被执行完成以后再来处理此次的请求,见方法 finish()。
代码38-39行:表示请求等待队列中并不包含本次请求的key,则先将本次请求在等待队列中置空,置空的目的是告诉别人该请求正在被执行,如果有其他相同的请求来时,请先等待我这次请求执行结束。然后将本次请求投放到缓存队列中,让CacheDispatcher调度器执行本次请求。
---------------------

RequestQueue#finish完成一次请求时会调用该方法。

该方法的调用,标志着一次请求的结束,结束一次请求包括:

  • 一个请求被完整的处理,得到请求的结果。
  • 一个请求在处理的过程中被取消了。
<T> void finish(Request<T> request) {
// 从当前队列中移除该请求
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
synchronized (mFinishedListeners) {
//请求结束的监听回调
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
} if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//从等待队列中移除该请求
Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
//将等待队列中所有相同的请求一次性添加到缓存队列中,让CacheDispatcher线程处理。此处不敢苟同,既然是相同的请求,为啥要全部放入队列中?放一个就好了嘛!!
mCacheQueue.addAll(waitingRequests);
}
}
}
}

这段代码的解释在注释里写的很清楚了。

取消请求

我们都知道,Volley有个优势就是可以取消指定的Tag标记请求或者取消所有请求,那么Volley是怎么做到的呢?还是从源码中找答案吧。

RequestQ ueue#cancleAll()

  /**
*定义的过滤请求接口,用于构建取消某个请求或者所有请求
*/
public interface RequestFilter {
public boolean apply(Request<?> request);
} /**
根据给定的过滤条件取消队列中所有的请求
*/
public void cancelAll(RequestFilter filter) {
synchronized (mCurrentRequests) {
//遍历当前请求队列中所有的请求,找到匹配的请求然后取消该请求。
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
} /**
根据给定的tag标签取消队列中所有的请求队列
*/
public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}

我们在添加请求的时候通常会给每个请求这是一个Tag,比如:

request.setTag("request1");

那么我们需要取消该请求的时候就简单了:

RequestQueue.cancelAll("request1");

如此就取消了tag=request1的请求。
如此一来每个请求都需要设置不同的tag来确定唯一的请求标记。那么问题来了,我在整个应用退出时该如何取消所有的请求呢?不可能我每个请求都去调用一次cancleAll吧?此时只要调用cancelAll(RequestFilter filter)方法就可以轻而易举的取消所有request请求啦,代码如下:

requestQueue.cancelAll(new RequestQueue.RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return true;
}
});

解析:以上代码仅仅是修改了RequestFilter接口中apply方法的返回值永远为true而已。如此一来就会导致如下方法会全部遍历一次当前请求队列。

 public void cancelAll(RequestFilter filter) {
synchronized (mCurrentRequests) {
//遍历当前请求队列中所有的请求,找到匹配的请求然后取消该请求。
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
}

总结

这篇博客主要介绍了Volley的整体工作机制,从整篇博客我们知道:

Volley默认创建1个cache Thread和4个network Thread来处理网络请求,当然你也可以创建更多的network Thread来处理更多的网络请求,正因为如此,Volley才支持多并发网络连接。
Volley创建1个本地缓存队列(cacheQueue)和1个网络请求队列(networkQueue)来保存所有网络请求,而随之对应的是一个cache Thread处理缓存队列,4个network thread处理网络请求队列,由于队列的实现都是带有优先级的阻塞队列,因此4个network thread是自动调度处理网络请求的。
Volley默认先将请求提交给cache Thread来处理,cache Thread会查找本地是否缓存了本次请求结果,如果缓存了且该结果未过期,则直接读取本地缓存结果,而无须再次请求网络。因此Volley默认自动缓存网络请求结果。
Volley支持取消某个或者所有的网络请求,一般在某个activity退出时调用Request#cancelAll()方法来取消所有网络请求以便出现内存泄漏。
Volley初始化以后就创建了5个后台线程在处理请求。只要你没做处理,这5个线程一直在后台跑。为了节省资源,在同一个App中最好使用同一个单例Volley RequestQueue队列来处理所有请求,以免创建过多线程浪费资源。还有在退出这个应用时,应该调用 RequestQueue#stop方法来干掉所有Volley线程。如此才是使用Volley最优雅的方式
后续博客会继续分析Volley是怎么实现RetryPolicy错误重试机制的,以及本地缓存的策略和请求优先级的设置。
---------------------

volley为什么不适合传输大数据:

volley中为了提高请求处理的速度,采用了ByteArrayPool进行内存中的数据存储的,如果下载大量的数据,这个存储空间就会溢出,所以不适合大量的数据,

但是由于他的这个存储空间是内存中分配的,当存储的时候优是从ByteArrayPool中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,

而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数 ,所以他比较适合大量的数据量少的网络数据交互情况。

Volley有用到线程池吗?

volley虽然没有用ThreadPoolExecutor,但volley 里面使用了一个数组来存放 NetworkDispatcher 这功能就相当于是线程池,只不过自己写了管理,默认开启4个线程。

------------------------------------------------------------

作者:废墟的树 
来源:CSDN 
原文:https://blog.csdn.net/feiduclear_up/article/details/52847017 
版权声明:本文为博主原创文章,转载请附上博文链接!

Volley框架原理的更多相关文章

  1. Java基础知识强化之网络编程笔记23:Android网络通信之 Volley(Google开源网络通信库)

    联合网上资料学习:http://www.open-open.com/lib/view/open1451223702339.html 一.Volley的介绍 1. Volley简介 在这之前,我们在程序 ...

  2. 快速Android开发系列网络篇之Volley

    Volley是Google推出的一个网络请求库,已经被放到了Android源码中,地址在这里,先看使用方法 RequestQueue mRequestQueue = Volley.newRequest ...

  3. Android Volley

    1.volley简单的介绍: Volley是一个HTTP库,使Android应用程序变得更加容易,最重要的是,网络 得更快. Vollry 提供以下好处: 1.自动调度的网络请求. 2.多个并发的网络 ...

  4. volley用法之 以post方式发送 json 参数

    需求是这样 我们需要发送一个post请求向服务器要参数.要求是发送的post参数也要是json格式. 简单一点的是这样的: 如果要发送的是这样简单的json格式,我们可以简单的使用map来实现: Re ...

  5. Volley框架使用笔记

    1.初始化请求队列 RequestQueue RequestQueue queue= Volley.newRequestQueue(context); 2.StringRequest 网络请求 Get ...

  6. android之volley学习

    Volley是android的平台通信库,一个新的网络通信框架.Volley 的特点:适合数据量小,通信频繁的网络操作. 获取Volley git 工具使用:git clone https://and ...

  7. Android开发学习之路-Volley源码解析

    从简单的StringRequest入手看看Volley的工作机制. 先简单说下Volley的用法: ① 获取一个RequestQueue mRequestQueue = Volley.newReque ...

  8. ym—— Android网络框架Volley(体验篇)

    VolleyGoogle I/O 2013推出的网络通信库,在volley推出之前我们一般会选择比较成熟的第三方网络通信库,如: android-async-http retrofit okhttp ...

  9. Google开源库-Volley的使用

    一.什么是Volley? Volley is an HTTP library that makes networking for Android apps easier and most import ...

随机推荐

  1. 各种Queue分析

    Queue主要方法的区别:   抛出异常 返回特殊值 插入 add(e)插入成功则返回true,没有可用空间则IllegalStateException offer(e) 移除 remove(e)获取 ...

  2. C# 利用反射调用类下的方法

    namespace TestReflection { public partial class Form1 : Form { public Form1() { InitializeComponent( ...

  3. 经典论文翻译导读之《Google File System》(转)

    [译者预读] GFS这三个字母无需过多修饰,<Google File System>的论文也早有译版.但是这不妨碍我们加点批注.重温经典,并结合上篇Haystack的文章,将GFS.TFS ...

  4. Dos命令快速设置ip、网关、dns地址

    netsh interface ip set address name="本地连接" source=static 192.168.1.8 255.255.255.0 192.168 ...

  5. Python爬虫示例

    #!/usr/bin/python #coding:utf8 import re import urllib def gethtml(url): page=urllib.urlopen(url) ht ...

  6. 如何查看虚拟机的ip地址,以及如何给虚拟机配置静态ip

    1 在命令行上敲ifconfig 如下图: 通过inet addr : 192.168.25.129就是你的虚拟机当前的ip 2. 我们一般在局域网内是通过dhcp协议向网关发送ip请求,因此获取的i ...

  7. docker仓库harbor搭建随笔

    docker除了自己的registry仓库工具外,还有vmware出品的harbor,harbor集成了ui界面,用户级别认证,重要的是对镜像管理比较全面,可以删除镜像,下面是 简单的部署指南 首先: ...

  8. LeetCode OJ 94. Binary Tree Inorder Traversal

    Given a binary tree, return the inorder traversal of its nodes' values. For example:Given binary tre ...

  9. 第七次Scrum冲刺

    第七次Scrum冲刺 1.今日完成的任务 队员 今日完成任务 刘佳 前端与后端对接 李佳 后端与数据库对接 周世元 数据库与后端对接 杨小妮 博客编写 许燕婷 管理团队当日及次日任务 陈水莲 综合测试 ...

  10. 18.2 不同用户 不同颜色光标 redis

    上次,我们完成了 change 这个event 通过 collaborationsrvice 与 server 进行 sockrt io 将 client端的监听的 change 发给 server ...