美文网首页
Volley源码分析(二)对请求的处理

Volley源码分析(二)对请求的处理

作者: 小川君 | 来源:发表于2018-02-04 19:28 被阅读0次

Volley源码分析(一)流程

上篇文章我们走了一遍Volley的大致流程,本章着重对请求的多级取消和缓存作讲解。

请求的取消


    /**
     * Cancels all requests in this queue with the given tag. Tag must be non-null
     * and equality is by identity.
     */
    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;
            }
        });
    }

通过标记取消指定的请求


    /**
     * Cancels all requests in this queue for which the given filter applies.
     * @param filter The filtering function to use
     */
    public void cancelAll(RequestFilter filter) {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
                if (filter.apply(request)) {
                    request.cancel();
                }
            }
        }
    }

这两段可以看出,通过遍历mCurrentRequests,取出标识为tag的request,然后将此调用此request的取消方法。

    /**
     * Mark this request as canceled.
     *
     * <p>No callback will be delivered as long as either:
     * <ul>
     *     <li>This method is called on the same thread as the {@link ResponseDelivery} is running
     *     on. By default, this is the main thread.
     *     <li>The request subclass being used overrides cancel() and ensures that it does not
     *     invoke the listener in {@link #deliverResponse} after cancel() has been called in a
     *     thread-safe manner.
     * </ul>
     *
     * <p>There are no guarantees if both of these conditions aren't met.
     */
    // @CallSuper
    public void cancel() {
        synchronized (mLock) {
            mCanceled = true;
            mErrorListener = null;
        }
    }

    /**
     * Returns true if this request has been canceled.
     */
    public boolean isCanceled() {
        synchronized (mLock) {
            return mCanceled;
        }
    }

然后设置request的变量mCanceled=true,外界通过isCanceled来获取request的状态。
共有四个处方调用了isCanceled:CacheDispatcher、RequestFuture、ExecutorDelivery、NetworkDispatcher,抛开RequestFuture不讲。
CacheDispatcher和NetworkDispathcer是在请求前进行判断的,ExecutorDelivery是在请求成功后进行判断的。
CacheDispathcher和NetworkDispathcer中,当从缓存队列或网络请求队列中取出一个请求,如果这个请求被取消则不进行网络请求,以CacheDispathcher为例:

   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");
            return;
        }

ExecutorDelivery中,当请求成功后,判断当前请求时是否被取消,如果已经被取消,则网络请求返回的数据不返回到主线程:

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

我们看到这三个地方都调用了request的finish方法:

 /**
     * Notifies the request queue that this request has finished (successfully or with error).
     *
     * <p>Also dumps all events from this request's event log; for debugging.</p>
     */
    void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }
        if (MarkerLog.ENABLED) {
            final long threadId = Thread.currentThread().getId();
            if (Looper.myLooper() != Looper.getMainLooper()) {
                // If we finish marking off of the main thread, we need to
                // actually do it on the main thread to ensure correct ordering.
                Handler mainThread = new Handler(Looper.getMainLooper());
                mainThread.post(new Runnable() {
                    @Override
                    public void run() {
                        mEventLog.add(tag, threadId);
                        mEventLog.finish(Request.this.toString());
                    }
                });
                return;
            }

            mEventLog.add(tag, threadId);
            mEventLog.finish(this.toString());
        }
    }

每个请求都持有RequsetQueue对象,然后调用其finish方法:

  /**
     * Called from {@link Request#finish(String)}, indicating that processing of the given request
     * has finished.
     */
    <T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
    }

将requestQueue中的请求集合删掉此请求,至此请求的取消完全结束。

缓存

数据缓存

在实例化requestQueue的时候 volley创建了一个缓存对象:


    private static RequestQueue newRequestQueue(Context context, Network network) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();
        return queue;
    }

我们可以去看下DisBasedCache的内部结构:

private final Map<String, CacheHeader> mEntries =
            new LinkedHashMap<String, CacheHeader>(16, .75f, true);

    /** Total amount of space currently used by the cache in bytes. */
    private long mTotalSize = 0;

    /** The root directory to use for the cache. */ 
    缓存根目录
    private final File mRootDirectory;

    /** The maximum size of the cache in bytes. */
    // 缓存的最大磁盘空间值
    private final int mMaxCacheSizeInBytes;

    /** Default maximum disk usage in bytes. */
    // 缓存的默认磁盘空间值
    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

    /** High water mark percentage for the cache */
    private static final float HYSTERESIS_FACTOR = 0.9f;

    /** Magic number for current version of cache file format. */
    private static final int CACHE_MAGIC = 0x20150306;


    //VisibleForTesting
    static class CacheHeader {
        /** The size of the data identified by this CacheHeader. (This is not
         * serialized to disk. */
        long size;

        /** The key that identifies the cache entry. */
        final String key;

        /** ETag for cache coherence. */
        缓存标签
        final String etag;

        /** Date of this response as reported by the server. */
        服务端返回数据的主要内容,当然也是要存储的内容
        final long serverDate;

        /** The last modified date for the requested object. */
         服务器返回数据的时间
        final long lastModified;

        /** TTL for this record. */
         缓存过期时间
        final long ttl;

        /** Soft TTL for this record. */
        final long softTtl;

        /** Headers from the response resulting in this cache entry. *
        服务端返回的头部信息
        final List<Header> allResponseHeaders;
    }


除此之外 还有基类Cache中的一个内部类:


    /**
     * Data and metadata for an entry returned by the cache.
     */
    class Entry {
        /** The data returned from cache. */
        public byte[] data;

        /** ETag for cache coherency. */
        public String etag;
        /** Date of this response as reported by the server. */
        public long serverDate;

        /** The last modified date for the requested object. */
       
        public long lastModified;

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

        /**
         * Response headers as received from server; must be non-null. Should not be mutated
         * directly.
         *
         * <p>Note that if the server returns two headers with the same (case-insensitive) name,
         * this map will only contain the one of them. {@link #allResponseHeaders} may contain all
         * headers if the {@link Cache} implementation supports it.
         */

        public Map<String, String> responseHeaders = Collections.emptyMap();

        /**
         * All response headers. May be null depending on the {@link Cache} implementation. Should
         * not be mutated directly.
         */
        public List<Header> allResponseHeaders;

        /** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }

DiskBasedCache中的内部类CacheHeader中的部分参数与基类Cache中的内部类Entity的参数是完全一样,具体原因下一篇文章会有提到,这里简单说一下,

    private CacheHeader(String key, String etag, long serverDate, long lastModified, long ttl,
                           long softTtl, List<Header> allResponseHeaders) {
            this.key = key;
            this.etag = ("".equals(etag)) ? null : etag;
            this.serverDate = serverDate;
            this.lastModified = lastModified;
            this.ttl = ttl;
            this.softTtl = softTtl;
            this.allResponseHeaders = allResponseHeaders;
        }

        /**
         * Instantiates a new CacheHeader object.
         * @param key The key that identifies the cache entry
         * @param entry The cache entry.
         */
        CacheHeader(String key, Entry entry) {
            this(key, entry.etag, entry.serverDate, entry.lastModified, entry.ttl, entry.softTtl,
                    getAllResponseHeaders(entry));
            size = entry.data.length;
        }

再cacheHeader的构造器中,通过传进来一个Entity类型的参数,然后将Entity中的参数全部都赋值到CacheHeader中的参数内。
在CacheHeader中有put和putEntity两个方法,顾名思义,都是传入Entity的方法,但是public类型的方法是put,所以put是从外部调用的最初方法:


    /**
     * Puts the entry with the specified key into the cache.
     */
    @Override
    public synchronized void put(String key, Entry entry) {
        pruneIfNeeded(entry.data.length);
        File file = getFileForKey(key);
        try {
            BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
            CacheHeader e = new CacheHeader(key, entry);
            boolean success = e.writeHeader(fos);
            if (!success) {
                fos.close();
                VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
                throw new IOException();
            }
            fos.write(entry.data);
            fos.close();
            putEntry(key, e);
            return;
        } catch (IOException e) {
        }
        boolean deleted = file.delete();
        if (!deleted) {
            VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
        }
    }

可以看到,进入put首先判断将eneity.data的字节长度做为参数传入了pruneIfNeeded方法,我们简单看下:


    /**
     * Prunes the cache to fit the amount of bytes specified.
     * @param neededSpace The amount of bytes we are trying to fit into the cache.
     */
    private void pruneIfNeeded(int neededSpace) {
        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
            return;
        }
        if (VolleyLog.DEBUG) {
            VolleyLog.v("Pruning old cache entries.");
        }

        long before = mTotalSize;
        int prunedFiles = 0;
        long startTime = SystemClock.elapsedRealtime();

        Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, CacheHeader> entry = iterator.next();
            CacheHeader e = entry.getValue();
            boolean deleted = getFileForKey(e.key).delete();
            if (deleted) {
                mTotalSize -= e.size;
            } else {
               VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
                       e.key, getFilenameForKey(e.key));
            }
            iterator.remove();
            prunedFiles++;

            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                break;
            }
        }
    }

这个方法主要是判断缓存空间是否足够放下本次的entity.data,如果能放下则直接返回,如果不能放下,则需要删除一些缓存文件,直到在放下entity.data之后还剩余至少10%的空间量,其中mTotalSize为缓存的总字节长度。
回到put方法我们继续看,首先根据key值也就是url创建一个file对象,然后以流的方式将cacheHeader中的标识参数写入到file中,完成之后再讲entity.data写入到文件中,最后通过putEntity计算刷新缓存文件的总字节长度。
除了put之外,还有get、remove、clear三个方法,内容不再多说。

相关文章

网友评论

      本文标题:Volley源码分析(二)对请求的处理

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