/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.volley.toolbox; import android.os.SystemClock; import com.android.volley.AuthFailureError; import com.android.volley.Cache; import com.android.volley.Network; import com.android.volley.NetworkError; import com.android.volley.NetworkResponse; import com.android.volley.NoConnectionError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.RetryPolicy; import com.android.volley.ServerError; import com.android.volley.TimeoutError; import com.android.volley.VolleyError; import com.android.volley.VolleyLog; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.impl.cookie.DateUtils; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * Modify by weiji.chen 2014/03/09 * A network performing Volley requests over an {@link com.android.volley.toolbox.HttpStack}. * 这才是管理网络请求.网络数据就是从这里获取的.由于原来直接存到内存,如果文件大了会OOM. * 而Volley.RequestQueue,NetworkDispatcher只不过是维护队列和分发. * 在原有的基础上增加了一个进度更新监听. */ public class BasicNetwork implements Network { protected static final boolean DEBUG = VolleyLog.DEBUG; private static int SLOW_REQUEST_THRESHOLD_MS = 3000; private static int DEFAULT_POOL_SIZE = 4096; protected final HttpStack mHttpStack; protected final ByteArrayPool mPool; /** * @param httpStack HTTP stack to be used */ public BasicNetwork(HttpStack httpStack) { // If a pool isn't passed in, then build a small default pool that will give us a lot of // benefit and not use too much memory. this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); } /** * @param httpStack HTTP stack to be used * @param pool a buffer pool that improves GC performance in copy operations */ public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { mHttpStack = httpStack; mPool = pool; } /** * 执行网络请求,最重要的获取数据线程.xUtils的请求思路有点像 * performRequest返回的是Volley定义的NetworkResponse. * 由于有HttpClient和HttpUrlConnect两种方法执行网络请求. * 从HttpStack.perfrormRequest()返回的是apache标准的httpresponse, * 所以获取到了httpResponse之后就是转换成NetworkResponse. * 是否直接下载到磁盘:指定了文件路径 +不使用缓存. * * @param request Request to process * @return * @throws com.android.volley.VolleyError */ @Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { //获取当前时间的新方法.以前都是用System.currentTimeMillis long requestStart = SystemClock.elapsedRealtime(); while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; Map<String, String> responseHeaders = new HashMap<>(); try { // Gather headers. Map<String, String> headers = new HashMap<>(); addCacheHeaders(headers, request.getCacheEntry()); // 尼玛,网络请求就一句话啊.难道是因为用了两种网络请求的方法才需要一个stack接口吗? 可能是吧 httpResponse = mHttpStack.performRequest(request, headers); // 以下从 HttpResponse --> NetworkResponse. // 响应状态码 StatusLine statusLine = httpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); responseHeaders = convertHeaders(httpResponse.getAllHeaders()); // Handle cache validation.只是数据使用旧的,响应头和状态码都是新的.但是,一定能确定有缓存吗? if (statusCode == HttpStatus.SC_NOT_MODIFIED) { return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, request.getCacheEntry().data, responseHeaders, true); } responseContents = entityToBytes(httpResponse.getEntity(), request.getProgressListener()); // if the request is slow, log it. long requestLifetime = SystemClock.elapsedRealtime() - requestStart; logSlowRequests(requestLifetime, request, responseContents, statusLine); // SC_NO_CONTENT表示没有新的数据. if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_NO_CONTENT) { throw new IOException(); } return new NetworkResponse(statusCode, responseContents, responseHeaders, false); } 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) { int statusCode = 0; NetworkResponse networkResponse = null; if (httpResponse != null) { statusCode = httpResponse.getStatusLine().getStatusCode(); } else { throw new NoConnectionError(e); } VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); if (responseContents != null) { networkResponse = new NetworkResponse(statusCode, responseContents, responseHeaders, false); if (statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN) { attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } else { // TODO: Only throw ServerError for 5xx status codes. throw new ServerError(networkResponse); } } else { throw new NetworkError(networkResponse); } } } } /** * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ private void logSlowRequests(long requestLifetime, Request<?> request, byte[] responseContents, StatusLine statusLine) { if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " + "[rc=%d], [retryCount=%s]", request, requestLifetime, responseContents != null ? responseContents.length : "null", statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount()); } } /** * Attempts to prepare the request for a retry. If there are no more attempts remaining in the * request's retry policy, a timeout exception is thrown. * * @param request The request to use. */ private static void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError { RetryPolicy retryPolicy = request.getRetryPolicy(); int oldTimeout = request.getTimeoutMs(); try { retryPolicy.retry(exception); } catch (VolleyError e) { request.addMarker(String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); throw e; } request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); } /** * 缓存头,不太明白 * * @param headers * @param entry */ private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) { // If there's no cache entry, we're done. if (entry == null) { return; } if (entry.etag != null) { headers.put("If-None-Match", entry.etag); } if (entry.serverDate > 0) { Date refTime = new Date(entry.serverDate); headers.put("If-Modified-Since", DateUtils.formatDate(refTime)); } } protected void logError(String what, String url, long start) { long now = SystemClock.elapsedRealtime(); VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url); } /** * Reads the contents of HttpEntity into a byte[].如果Entity太大,会不会导致OOM? byte[]是OOM的罪魁祸首 * 这才是获取数据真正的地方!!!经过测试,在这里才获取数据. * 但是,有一个很大的问题,它在下载的时候是在内存缓存区开辟空间接收数据的,内存一下子就占了和下载文件的大小. * 所以这种做法很不适合下载大文件.只适合小文件而已.注意内存的占用.看来还是得参照xutils另外写 */ private byte[] entityToBytes(HttpEntity entity, Response.ProgressListener progressListener) throws IOException, ServerError { PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength()); // 以下是控制进度的. boolean updateProgress = (progressListener != null); long current = 0; long lastUpdateTime = 0; long currentUpdateTime = 0; long total = 0; long UPDATE_RATE = 1000; //public init if (updateProgress) { total = entity.getContentLength(); progressListener.onProgressing(current, total); lastUpdateTime = System.currentTimeMillis(); } byte[] buffer = null; try { InputStream in = entity.getContent(); if (in == null) { throw new ServerError(); } buffer = mPool.getBuf(1024); int count; while ((count = in.read(buffer)) != -1) { bytes.write(buffer, 0, count); if (updateProgress) { current += count; currentUpdateTime = System.currentTimeMillis(); if (currentUpdateTime - lastUpdateTime > UPDATE_RATE) { progressListener.onProgressing(current, total); lastUpdateTime = currentUpdateTime; } } } return bytes.toByteArray(); } finally { try { // Close the InputStream and release the resources by "consuming the content". entity.consumeContent(); } catch (IOException e) { // This can happen if there was an exception above that left the entity in // an invalid state. VolleyLog.v("Error occured when calling consumingContent"); } mPool.returnBuf(buffer); bytes.close(); } } /** * Converts Headers[] to Map<String, String>. */ private static Map<String, String> convertHeaders(Header[] headers) { Map<String, String> result = new HashMap<>(); for (int i = 0; i < headers.length; i++) { result.put(headers[i].getName(), headers[i].getValue()); } return result; } }