Volley拓展框架——Netroid,以及与Volley的差异

转自:http://blog.csdn.net/brian512/article/details/50499423?ref=myread

Netroid是一个基于Volley实现的Android Http库。提供执行网络请求、缓存返回结果、批量图片加载、大文件断点下载的常见Http交互功能。致力于避免每个项目重复开发基础Http功能,实现显著地缩短开发周期的愿景。

功能上的区别:

作为Volley的拓展框架,netroid增加了大文件断点下载,并且netroid的可定制性更强。

实现上的区别:

1. 缓存的处理;

在volley中,缓存的过期时间是通过 ttl 和 softTtl 控制

        /** 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();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

而这两个值的来源是HttpHeaderParser.parseCacheHeaders

public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
    long now = System.currentTimeMillis();    
    long serverDate = 0;
    long lastModified = 0;
    long serverExpires = 0;
    long softExpire = 0;
    long finalExpire = 0;
    long maxAge = 0;
    long staleWhileRevalidate = 0;
    boolean hasCacheControl = false;
    boolean mustRevalidate = false;    
    String serverEtag = null;
    headerValue = headers.get("Date");
    if (headerValue != null) {
        serverDate = parseDateAsEpoch(headerValue);
    }    headerValue = headers.get("Cache-Control");
    if (headerValue != null) {
        hasCacheControl = true;
        String[] tokens = headerValue.split(",");
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i].trim();
            if (token.equals("no-cache") || token.equals("no-store")) {
                return null;
            } else if (token.startsWith("max-age=")) {
                try {
                    maxAge = Long.parseLong(token.substring(8));
                } catch (Exception e) {
                }
            } else if (token.startsWith("stale-while-revalidate=")) {
                try {
                    staleWhileRevalidate = Long.parseLong(token.substring(23));
                } catch (Exception e) {
                }
            } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                mustRevalidate = true;
            }
        }
    }    
    headerValue = headers.get("Expires");
    if (headerValue != null) {
        serverExpires = parseDateAsEpoch(headerValue);
    }    
    headerValue = headers.get("Last-Modified");
    if (headerValue != null) {
        lastModified = parseDateAsEpoch(headerValue);
    // Cache-Control takes precedence over an Expires header, even if both exist and Expires is more restrictive.
    // 如果服务器返回的header中有Cache-Control字段,则可以按照制定的规则进行设定
    if (hasCacheControl) {
        softExpire = now + maxAge * 1000;
        finalExpire = mustRevalidate
                ? softExpire
                : softExpire + staleWhileRevalidate * 1000;
    } 
    // 若服务器返回的header中包含Expires和Date字段
    else if (serverDate > 0 && serverExpires >= serverDate) {
        // Default semantic for Expire header in HTTP specification is softExpire.
        softExpire = now + (serverExpires - serverDate);
        finalExpire = softExpire;
    }    
    Cache.Entry entry = new Cache.Entry();
    entry.data = response.data;
    entry.etag = serverEtag;
    entry.softTtl = softExpire;
    entry.ttl = finalExpire;
    entry.serverDate = serverDate;
    entry.lastModified = lastModified;
    return entry;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

根据上述代码中的中文注释看,若服务器返回的header中没有Cache-Control,Expires,Date等字段,则 ttl 和 softExpire 的值均为默认的0,从而使得缓存永远是过期的,其影响是缓存不仅不能起效,反而每次网络请求都需要更新缓存,最后就是拖累整体性能。

为此,netroid采用expireTime字段替代了 ttl 和 softExpire ,每次发起请求时,需指定过期时间

    // com.duowan.mobile.netroid.Request.java
    public void setCacheExpireTime(TimeUnit timeUnit, int amount) {
        this.mCacheExpireTime = System.currentTimeMillis() + timeUnit.toMillis(amount);
    }

    public final boolean shouldCache() {
        return mCacheExpireTime > 0;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

从上述代码看出,若没有设置过期时间时,不会产生缓存

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

        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            // still unimplemented, might be use a constant like 'refreshTime'?
            return this.expireTime < System.currentTimeMillis();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2. 网络数据处理;

首先贴一段带注释的代码:

    public NetworkResponse performRequest(Request request) throws VolleyError {
        // Determine if request had non-http perform.
        // 若该请求不需要访问网络,则直接复写perform方法。使用场景如,加载数据库的数据,或者加载本地图片,使用此框架可以统一处理此类耗时操作
        NetworkResponse networkResponse = request.perform();
        if (networkResponse != null) 
            return networkResponse;

        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            // If the request was cancelled already,
            // do not perform the network request.
            if (request.isCanceled()) {
                request.finish("perform-discard-cancelled");
                mDelivery.postCancel(request);
                throw new NetworkError(networkResponse);
            }

            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            try {
                // prepare to perform this request, normally is reset the request headers.
                // 此方法默认实现为空,若请求有需要预处理的话,该设计也是极好的。使用场景如,在进行大文件断点下载时,需要设置Range头字段,但是网络异常进行retry时就不太好处理range了,但是有这个方法就很简单了
                request.prepare();

                httpResponse = mHttpStack.performRequest(request);

                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                if (statusCode < 200 || statusCode > 299) throw new IOException();

                // 此方法的默认实现为volley的实现方法,但是可以复写该方法。volley的实现方式是直接把请求到的数据转为byte[],此方式会限制请求的数据量不能太大,否则会OOM。
                // 若下载大文件时,就得复写这个方法,将网络请求的数据流读写到文件,而不是内存
                responseContents = request.handleResponse(httpResponse, mDelivery);

                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                return new NetworkResponse(statusCode, responseContents, parseCharset(httpResponse));
            } 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) {
                。。。
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

3. 数据请求过程回调;

在volley的实现中,是通过ExecutorDelivery将数据请求的结果回调给调用者。

public interface ResponseDelivery {
    /**
     * Parses a response from the network or cache and delivers it.
     */
    public void postResponse(Request request, Response response);

    /**
     * Parses a response from the network or cache and delivers it. The provided
     * Runnable will be executed after delivery.
     */
    public void postResponse(Request request, Response response, Runnable runnable);

    /**
     * Posts an error for the given request.
     */
    public void postError(Request request, VolleyError error);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

netroid在此基础上增加了一些回调:

public interface Delivery {

    /** Posts request finished callback for the given request. */
    void postFinish(Request request);

    /** Parses a response from the network or cache and delivers it. */
    public void postResponse(Request request, Response response);

    /**
     * Parses a response from the network or cache and delivers it. The provided
     * Runnable will be executed after delivery.
     */
    public void postResponse(Request request, Response response, Runnable runnable);

    /** Posts an error for the given request. */
    public void postError(Request request, VolleyError error);

    /** Posts a cancel callback for the given request. */
    void postCancel(Request request);

    /** Posts starting execute callback for the given request. */
    void postPreExecute(Request request);

    /** Posts cache used callback for the given request. */
    void postUsedCache(Request request);

    /** Posts networking callback for the given request. */
    void postNetworking(Request request);

    /** Posts request retry callback for the given request. */
    void postRetry(Request request);

    /** Posts file download progress stat. */
    void postDownloadProgress(Request request, long fileSize, long downloadedSize);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

可以看出,这些回调基本覆盖了请求过程中的关键点,主要是有postDownloadProgress方法,进行回调文件下载进度。

                // com.duowan.mobile.netroid.NetworkDispatcher.java
                request.addMarker("network-queue-take");
                mDelivery.postPreExecute(request);

                // If the request was cancelled already,
                // do not perform the network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    mDelivery.postCancel(request);
                    mDelivery.postFinish(request);
                    continue;
                }

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // Parse the response here on the worker thread.
                Response response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                if (mCache != null && request.shouldCache() && response.cacheEntry != null) {
                    response.cacheEntry.expireTime = request.getCacheExpireTime();
                    mCache.putEntry(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back.
                request.markDelivered();
                mDelivery.postResponse(request, response);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

从上述代码看出,回调确实很多,若在这些回调中添加太多操作的话,肯定会影响数据请求的速度。

总的来说,netroid相对volley的改进还是不错的,这也是这两天看代码的总结,如有遗漏,后面再补充!

你可能感兴趣的:(Android)