资讯专栏INFORMATION COLUMN

读源码学设计 - volley

txgcwm / 933人阅读

摘要:在大行其道的今天,已经略显过时。我们按照上面的思路,从和作为出发点,由浅入深来阅读源码。在最后看类源码时我们可以看到这个过程。负责分发,对来说,就是通过将发送到了主线程在中调用了。的个数,决定了网络请求的最大并发数。

Volley

在 retrofit+okhttp 大行其道的今天,volley 已经略显过时。使用 volley,我们无法避免写一些样板代码,但在它刚出现时,曾经很大程度降低了 android 开发中网络请求这部分的难度,简洁、轻量、容易定制扩展,我们依然能从它的源码中感受到这些。

看一个使用 volley 的小 demo

    

StringRequest request = new StringRequest("http://example.com", new Response
            .Listener() {
        @Override
        public void onResponse(String s) {

        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError volleyError) {

        }
    });
    
    RequestQueue requestQueue = Volley.newRequestQueue(context);
             
    requestQueue.add(request);

三步走

创建一个 Request

创建一个 RequestQueue

联结两者

这就是 volley 作为一个网络请求库提供给我们的用户界面,超级简洁有没有。Request 是一个数据,是一个相对静态的概念,我们使用它来说我们的网络请求要发送到哪里、带有什么样的参数、返回的数据是什么对象、请求成功了怎样处理、请求失败了又要怎么办,等等等等。RequestQueue 呢,是我们的执行引擎,吃掉一个个 Request,消费 Request 的属性,同时返回 Response。

我们按照上面的思路,从 Request 和 RequestQueue 作为出发点,由浅入深来阅读 volley 源码。

Request

前面说到,Request 在 volley 中是相对静态的概念,静态是好的,我们喜欢静态的东西,所以我们从 Request 开始。看 Request 的定义

public abstract class Request implements Comparable> {
}

我们看到,Request 是个抽象类,有泛型,实现了 Comparable 接口。看到这里你也许可以大胆推测,这个 Request 是可以大大扩展的,大多数网络请求以表单的方式作为参数,十分多变,那么这个泛型来指名返回结果的类型应该是十分合理的。没错,就是这样。实现 Comparable 呢,Request 的比较有什么含义?想到上面有 Queue 的概念,这个 Comaparable 也许和请求的优先级有关,对的,事实上 RequestQueue 中的确使用了 PriorityBlockingQueue 来达到管理 Request 优先级的效果,后文再细说。

上面简单的一行代码已经向我们透漏了大量信息,让我们更近一步。看看 Request 有哪些属性

public abstract class Request implements Comparable> {
    /**
     * Request method of this request.  Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
     * TRACE, and PATCH.
     */
    private final int mMethod;

    /** URL of this request. */
    private final String mUrl;

    /** Default tag for {@link TrafficStats}. */
    private final int mDefaultTrafficStatsTag;

    /** Listener interface for errors. */
    private final Response.ErrorListener mErrorListener;

    /** Sequence number of this request, used to enforce FIFO ordering. */
    private Integer mSequence;

    /** The request queue this request is associated with. */
    private RequestQueue mRequestQueue;

    /** Whether or not responses to this request should be cached. */
    private boolean mShouldCache = true;

    /** Whether or not this request has been canceled. */
    private boolean mCanceled = false;

    /** Whether or not a response has been delivered for this request yet. */
    private boolean mResponseDelivered = false;

    /** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
    private boolean mShouldRetryServerErrors = false;

    /** The retry policy for this request. */
    private RetryPolicy mRetryPolicy;

    /**
     * When a request can be retrieved from cache but must be refreshed from
     * the network, the cache entry will be stored here so that in the event of
     * a "Not Modified" response, we can be sure it hasn"t been evicted from cache.
     */
    private Cache.Entry mCacheEntry = null;

    /** An opaque token tagging this request; used for bulk cancellation. */
    private Object mTag;
}

我们简单的分下类:

mMethod、mUrl:用什么 Http 方法给谁发请求,mUrl 中可以附带参数(如果想在 body 中传递参数呢,想定制 Header 呢)

mShouldCache、mCanceled、mCacheEntry:缓存相关

mRequestQueue:关联的 RequestQueue,结束 Request 时使用

mErrorListener:错误回调,想想上面的 demo,还有一个成功的回调,但这里并没有,下文再说

mShouldRetryServerErrors、mRetryPolicy:控制重试

mTag:不同的 Request 可以设置相关的 tag,这样我们可以用 tag 来批量取消 Request

mSequence、mDefaultTrafficStatsTag:忽略

想一想当我们自定义一个 Request 的时候会关心那些属性呢,Method、Url、Cache、ErrorListener,大概就这样吧。

在上面的分类中还有一些问题要解决,一个一个来。

Body
    public byte[] getBody() throws AuthFailureError {
        Map params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

这是个 public 方法,我们可以 override 它。不急,可以看到它使用了 getParams()、encodeParameters()、getParamsEncoding() 方法,我们 override 这三个方法也可以达到间接影响 Body 的效果,encodeParameters() 是 private 的,还好一般没有改变它的必要。

Header
public Map getHeaders() throws AuthFailureError {
    return Collections.emptyMap();
}

override!

请求成功的回调
abstract protected void deliverResponse(T response);
另外一个比较重要的方法
   

     abstract protected Response parseNetworkResponse(NetworkResponse response);

将 NetworkResponse 转化为客户端的类型,这是一个 abstract 方法,我们自定义的 Request 子类必须实现。事实上,demo 中的 StringRequest 就是 volley 中的一个 Request 的具体实现,我们可以从中学习:

@Override
protected Response parseNetworkResponse(NetworkResponse response) {
    String parsed;
    try {
        parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
    } catch (UnsupportedEncodingException e) {
        parsed = new String(response.data);
    }
    return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}

可以看到,Request 定义了一个网络请求的细节问题,服务、参数、重试策略、缓存策略、返回数据处理等。其中 Header、参数、返回值类型的变化可以通过继承 Request,实现不同的子类来实现。在 volley 中内置了一些实现,包括 StringRequest、JsonObjectRequest、JsonArrayRequest、ImageRequest等,继承的方式显得不太灵活,需要定义一堆的 Request。我们可以稍作扩展,用组合来避免 Request 类的爆炸。

public interface ResponseParser {
    Response parseNetworkResponse(NetworkResponse networkResponse);
}

public  class MyRequest extends Request {

    final private Map params;
    final private Map headers;
    final private ResponseParser responseParser;
    final private Response.Listener listener;

    public MyRequest(int method,
                     String url,
                     Response.ErrorListener errorListener,
                     Map params,
                     Map headers,
                     ResponseParser responseParser,
                     Response.Listener listener) {
        super(method, url, errorListener);
        this.params = params;
        this.headers = headers;
        this.responseParser = responseParser;
        this.listener = listener;
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse networkResponse) {
        return responseParser.parseNetworkResponse(networkResponse);
    }

    @Override
    protected void deliverResponse(T t) {
        listener.onResponse(t);
    }

    @Override
    public Map getHeaders() throws AuthFailureError {
        return headers;
    }

    @Override
    protected Map getParams() throws AuthFailureError {
        return params;
    }
}

在使用 volley 的过程中,我们大部分时间会是处理 Request 相关的内容。将 Request 设计的简单直白,降低使用者的难度,这是 volley 非常友好的地方,同时,Volley 的 Request 创建过程也许并不是太有趣,比如请求参数的创建,无聊且不太直观。根据具体的业务,写一些 Builder 可以一定程度弥补这一点。接下来是 RequestQueue 的部分,RequestQueue 在逻辑上是动态的,是一台机器运转的部分,稍微复杂一些,但是,在某种意义上它又是稳定的,它提供了很好的默认实现,多数情况下我们不需要定制,不需要扩展,不需要理解具体细节,它在那里,它完美的 work。

RequestQueue

RequestQueue 这台机器是如何运转的呢?使用 add 方法,一个 Request 首先会被放入 mCacheQueuemCacheQueue 是一个 PriorityBlockingQueue,CacheDispatchermCacheQueue 中取出 Request,查看 cache 中是否已经有相同之前相同的请求得到的有效缓存,有就直接使用 ResponseDelivery 将缓存的结果分发到 Request 中的 deliverResponse 方法,从而传递到用户手中;如果没有有效的缓存结果,则将 Request 添加到 mNetworkQueueNetworkDispatchermNetworkQueue 中读取 Request,使用 Network 执行 Request 中的请求,成功后将结果放入 Cache,同时用 ResponseDelivery 分发结果。这就是一个 Request 的周期。
流程图如下:
上面我们提到了一些关键的类或属性。

mCacheQueue、mNetworkQueue:两个都是 PriorityBlockingQueue,结合 Request 实现的 Comparable 接口,Request 的优先级在这里实现。

CacheDispatcher、NetworkDispatcher:负责从 Request 到 Response 的转化

Cache、Network:将 Request 转化为 Response 的实际执行者

ResponseDelivery:分发 Response

有了这些概念后,我们分别分析这些类的代码,试着由低向上来理解 RequestQueue。

CacheDispatcher
public class CacheDispatcher extends Thread {
    
    public CacheDispatcher(
            BlockingQueue> cacheQueue, BlockingQueue> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }
    
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request request = mCacheQueue.take();
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    mNetworkQueue.put(request);
                    continue;
                }
                Response response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                mDelivery.postResponse(request, response);
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
}

为便于理解,删去了大量代码,只保留下核心部分。

在构造方法中,我们看到了 mCacheQueue,Request 在 RequestQueue 类中会先放到这里;还有mNetworkQueue,mCacheQueue 中的 Request 没有命中 Cache,会被再放到这里;mCache,存取 Response 的地方;mDelivery,Response 被分发的地方。

CacheDispatcher 继承了 Thread,我们可以 quit 它,看到 quit 中先标记,然后 interrupt,意味着 run 方法中会相应的处理。

run 中,在线程中开启了无限循环,每次循环从 mCacheQueue 读取 Request,然后进行 1 中提到的过程。也可以看到对于 quit 的处理。

NetworkDispatcher
public class NetworkDispatcher extends Thread {
    public NetworkDispatcher(BlockingQueue> queue,
            Network network, Cache cache,
            ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }

    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        while (true) {
            Request request;
            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;
            }
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                Response response = request.parseNetworkResponse(networkResponse);.
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                }
                mDelivery.postResponse(request, response);
        }
    }
}

NetworkDispatcher 和 CacheDispatcher 是不是非常相似?

Cache

一个接口类,和普通的 Cache 没有什么太大差别,不赘述。

Network

public interface Network {
    public NetworkResponse performRequest(Request request) throws VolleyError;
}

public interface HttpStack {
    public HttpResponse performRequest(Request request, Map additionalHeaders)
        throws IOException, AuthFailureError;
}

它的实现是 BasicNetwork,BasicNetwork 依赖 HttpStack,HttpStack 是实际发起网络请求的地方,volley 分别提供了 HttpClient 和 HttpURLConnection 的实现。我们可以定制这部分,比如基于 OKHttp 的实现。在最后看 Volley 类源码时我们可以看到这个过程。

ResponseDelivery

ResponseDelivery 负责分发 Response,对 Android 来说,就是通过 Handler 将 Response 发送到了主线程

public class ExecutorDelivery implements ResponseDelivery {
    private final Executor mResponsePoster;

    public ExecutorDelivery(final Handler handler) {
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }
    @Override
    public void postResponse(Request request, Response response, Runnable runnable) {
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }
}

在 ResponseDeliveryRunnable 中调用了 Request.deliverResponse。

RequestQueue

有了上面的内容,理解 RequestQueue 就很容易了。首先看构造方法。

    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

一共四个参数。

cache:一个实现了读写删功能的 Cache 对象,通过它我们可以实现自己的缓存策略。

Network:前面提到,这是一个接口,只有一个要实现的方法,传入一个 Request,传出一个 Response。或许我们也可以把缓存策略放到这里面来,让 Cache 对 RequestQueue 透明,也未尝不是一个不错的想法。

threadPoolSize:NetworkDispatcher 的个数,决定了网络请求的最大并发数。

ResponseDelivery:分发 Response,默认实现 ExecutorDelivery 将 Response 分发到 Request.deliverResponse, 这就是我们 override deliverResponse 方法可以得到 Response 的原因,同时我们从上面 ExecutorDelivery 的代码中看到,这个分发是通过像一个 Handler post 一个 Runnable 实现的,Handler 的性质决定了数据分发到哪个线程。

下面看 start 方法
    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();
        }
    }

mCacheDispatcher、mDispatchers 初始化,从上面我们知道它们都是 Thread 的子类, start 后内部开始循环,从 mCacheQueue、mNetworkQueue 读取 Request 进行处理。

接下来是最重要的方法 add。
    public  Request add(Request 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> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList>();
                }
                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;
        }
    }

和我们开始讨论 RequestQueue 时的流程图多了一些细节的处理,比如如果一个 Request 被设置为不可缓存,则直接添加到 mNetworkQueue;有相等的 Request 时,会把重复的请求加到一个等待队列,分享请求结果。

RequestQueue 的内容就到这里了。RequestQueue 在并发上没有选择使用 Java 提供的线程池,而是使用固定数据的线程数组(NetworkDispatcher)配合阻塞的优先级队列来实现任务并发。同时在缓存、网络、分发方式上提供了可定制的接口,当然,多数时候使用默认实现就可以了。为了方便的使用默认的实现,就像文章开始的 demo 展示的那样,volley 提供了一个工具类 Volley。

Volley

Volley 只提供两个方法。用来创建默认的 RequestQueue,如下:

public class 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) {
                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;
    }

    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
}
扩展

Volley 自带的 NetworkImageView

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66937.html

相关文章

  • Android网络编程3之Volley用法全解析

    摘要:前言想必很多人都用过,为了建立网络编程的知识体系,是必须要讲的知识点,所以我这里有必要再次介绍一下的使用。简介在年大会上推出了一个新的网络通信框架。在使用前请下载库并放在目录下并到工程中。 前言 Volley想必很多人都用过,为了建立网络编程的知识体系,Volley是必须要讲的知识点,所以我这里有必要再次介绍一下Volley的使用。 1.Volley简介 在2013年Google I/...

    Code4App 评论0 收藏0
  • 网络相关 - 收藏集 - 掘金

    摘要:关于三请求流程,源码必知掘金前两两片我们介绍了,基本使用和如何查看源码,今天我们正式进入源码分析流程。掘金是一款上的组件。 一篇文章带你走通 OkHttp+Retrofit+Rxjava - Android - 掘金一篇文章带你走通 OkHttp+Retrofit+Rxjava @(Android)[android] ... 升级 Https 的那些事 - Android - 掘金一、...

    haobowd 评论0 收藏0
  • 网络相关 - 收藏集 - 掘金

    摘要:关于三请求流程,源码必知掘金前两两片我们介绍了,基本使用和如何查看源码,今天我们正式进入源码分析流程。掘金是一款上的组件。 一篇文章带你走通 OkHttp+Retrofit+Rxjava - Android - 掘金一篇文章带你走通 OkHttp+Retrofit+Rxjava @(Android)[android] ... 升级 Https 的那些事 - Android - 掘金一、...

    pf_miles 评论0 收藏0
  • 继续封装个 Volley 组件

    摘要:下一次的计划,也许是封装的组件,也许是封装个播放器的组件,也可能是封装常用的自定义,视情况而定吧。至于为什么需要二次封装,这篇就不扯了,反正每个组件的封装肯定是来源于有这方面的需求。 本篇文章已授权微信公众号 dasu_Android(大苏)独家发布 前面已经封装了很多常用、基础的组件了:base-module, 包括了: crash 处理 常用工具类 apk 升级处理 log 组件 l...

    李涛 评论0 收藏0
  • Android网络编程4之从源码解析Volley

    摘要:接下来看看网络调度线程。让我们再回到,请求网络后,会将响应结果存在缓存中,如果响应结果成功则调用来回调给主线程。我们用请求网络的写法是这样的将请求添加在请求队列中看到第行整个的大致流程都通了吧,好了关于的源码就讲到这里。 1.Volley结构图 showImg(https://segmentfault.com/img/remote/1460000011351317); 从上图可以看到V...

    fevin 评论0 收藏0

发表评论

0条评论

txgcwm

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<