前兩篇已經對網絡請求流程已經梳理了個大概,這次我們着重看一下HttpStack和它的其實現類。我們之前在Network篇講過它僅有一個實現類,而今天我們講的HttpStack有兩個實現類。

其中HttpCliantStack是在2.3以下使用,Hurl是在2.3以上使用,這樣分開的原因谷歌給了注釋。
// Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
2.3以下HttpUrlConnection 是不能用的,而2.3以上就是采用HttpUrlConnection 進行連接的,以下就是直接用的HttpClient。
HttpStack
先來看一下HttpStack接口
public interface HttpStack { /** * Performs an HTTP request with the given parameters. *通過給定的參數執行一個http請求 * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, * and the Content-Type header is set to request.getPostBodyContentType().</p> * * @param request the request to perform//要執行的請求 * @param additionalHeaders additional headers to be sent together with * {@link Request#getHeaders()} * @return the HTTP response//執行一個請求返回一個結果 */ public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; }
HttpCliantStack
這里區分Get和Post請求,我們先看下HttpCliantStack,注釋已經寫的非常清楚了,如果注釋有誤望大家指出。
/** * An HttpStack that performs request over an {@link HttpClient}. * HttpStack:通過HttpClient執行請求 * {@link Volley#newRequestQueue(android.content.Context, HttpStack)} */ public class HttpClientStack implements HttpStack { protected final HttpClient mClient;//默認HttpClient /** The Constant HEADER_CONTENT_TYPE. */ private final static String HEADER_CONTENT_TYPE = "Content-Type"; /** * Instantiates a new http client stack. * Volley中HttpClient可是AndroidHttpClient.newInstance(userAgent)產生的 * @param client the client */ public HttpClientStack(HttpClient client) { mClient = client; } /** * Adds the headers. * * @param httpRequest the http request * @param headers the headers */ private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) { for (String key : headers.keySet()) { httpRequest.setHeader(key, headers.get(key)); } } /** * Gets the post parameter pairs. * * @param postParams the post params * @return the post parameter pairs */ @SuppressWarnings("unused") private static List<NameValuePair> getPostParameterPairs(Map<String, String> postParams) { List<NameValuePair> result = new ArrayList<NameValuePair>(postParams.size()); for (String key : postParams.keySet()) { result.add(new BasicNameValuePair(key, postParams.get(key))); } return result; } /* (non-Javadoc) * @see com.android.volley.toolbox.HttpStack#performRequest(com.android.volley.Request, java.util.Map) */ @Override//中心方法 public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);//設置請求方法 addHeaders(httpRequest, additionalHeaders);//添加自定義頭部 addHeaders(httpRequest, request.getHeaders());//添加請求自帶頭部 onPrepareRequest(httpRequest);//相當於onStart,子類擴展 HttpParams httpParams = httpRequest.getParams();//獲取配置類 int timeoutMs = request.getTimeoutMs(); // TODO: Reevaluate this connection timeout based on more wide-scale // data collection and possibly different for wifi vs. 3G. /** 如果有更大規模的數據在Wifi和3G網絡下重新評估連接超時*/ HttpConnectionParams.setConnectionTimeout(httpParams, 5000);//設置超時 HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);//設置超時 return mClient.execute(httpRequest); } /** * Creates the appropriate subclass of HttpUriRequest for passed in request. * 請求工廠類{GET,DELET,PUT,POST} * @param request the request * @param additionalHeaders the additional headers * @return the http uri request * @throws AuthFailureError the auth failure error */ @SuppressWarnings("deprecation") /* protected */ static HttpUriRequest createHttpRequest(Request<?> request, Map<String, String> additionalHeaders) throws AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: { // This is the deprecated way that needs to be handled for backwards compatibility. // If the request's post body is null, then the assumption is that the request is // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); if (postBody != null) { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); HttpEntity entity; entity = new ByteArrayEntity(postBody); postRequest.setEntity(entity); return postRequest; } else { return new HttpGet(request.getUrl()); } } case Method.GET: return new HttpGet(request.getUrl()); case Method.DELETE: return new HttpDelete(request.getUrl()); case Method.POST: { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(postRequest, request); return postRequest; } case Method.PUT: { HttpPut putRequest = new HttpPut(request.getUrl()); putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(putRequest, request); return putRequest; } default: throw new IllegalStateException("Unknown request method."); } } /** * Sets the entity if non empty body. * 非空體Entity * @param httpRequest the http request * @param request the request * @throws AuthFailureError the auth failure error */ private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest, Request<?> request) throws AuthFailureError { byte[] body = request.getBody(); if (body != null) { HttpEntity entity = new ByteArrayEntity(body); httpRequest.setEntity(entity); } } /** * Called before the request is executed using the underlying HttpClient. * 在請求之前調用 * <p>Overwrite in subclasses to augment the request.</p> * 由子類覆寫擴展 * @param request the request * @throws IOException Signals that an I/O exception has occurred. */ protected void onPrepareRequest(HttpUriRequest request) throws IOException { // Nothing. } }
它的構造參數是一個HttpClient,Velloy是用AndroidHttpClient.newInstance(userAgent)建立一個HttpClient實例。
再看這個類的最重要方法也就是對HttpStack接口的performRequest()方法進行具體實現。
第一步:通過這個靜態方法createHttpRequest()來獲取一個HttpUriRequest請求實例,其中HttpGet,HttpPost,HttpPut,HttpDelete都實現了HttpUriRequest這個接口。
這步就確定了請求類型和創立了Http實際請求。
第二步:通過這兩個方法添加Http頭部,
addHeaders(httpRequest, additionalHeaders);//添加自定義頭部
addHeaders(httpRequest, request.getHeaders());//添加請求自帶頭部
第三步:這個方法
onPrepareRequest()
挺人性化的,相當於AsyncTask的onPreExecute(),不過要實現這個方法需要自行擴展此類,谷歌沒有把她提出來。
一開始我還以為沒有類似的方法,像Afinal的方法名字取得很吸引人,叫onStart(),onSuccess(),onFailure()。其實在Volley與之相對應的都有,onResponse就相當於onSuccess(),onErrorResponse就相當於onFailure(),而onPrepareRequest()就對應onStart()。
第四步:設置超時,這個超時設置值是在Request里封裝進去了,Request封裝了很多東西,比如請求的URL等。
在此谷歌在超時這里做了很溫馨的提示,設置連接超時考慮WIFI和3G網絡設置不一樣,這里應該可以留出一個缺口,根據實際網絡設置不一樣的時常。貌似現在很多應用會根據網絡來進行內容排版,例如什么無圖模式啊,僅在wifi下上傳等。
HurlStack
先看下構造,看來大框架構造都是按這格式的。
/** * 默認的構造器 * {@link Volley#newRequestQueue(android.content.Context, HttpStack)} */ public HurlStack() { this(null); } /** * @param urlRewriter Rewriter to use for request URLs */ public HurlStack(UrlRewriter urlRewriter) { this(urlRewriter, null); } /** * 兩個主要參數 * @param urlRewriter Rewriter to use for request URLs//Url轉換器 * @param sslSocketFactory SSL factory to use for HTTPS connections//安全連接 */ public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) { mUrlRewriter = urlRewriter; mSslSocketFactory = sslSocketFactory; }
由此可見主要字段就是mUrlRewriter 和mSslSocketFactory 。前面的是URL轉換接口,后面是安全連接。
這個URL轉換接口還是挺人性化的,可以過濾些非法字符和省略Http或者www.
public interface UrlRewriter { /** * Returns a URL to use instead of the provided one, or null to indicate * this URL should not be used at all. */ public String rewriteUrl(String originalUrl); }
然后我們還是看HttpStack接口的performRequest()方法實現。
@Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl();//獲取這個請求的url HashMap<String, String> map = new HashMap<String, String>(); map.putAll(request.getHeaders()); map.putAll(additionalHeaders); if (mUrlRewriter != null) {//默認的不會對url轉換 String rewritten = mUrlRewriter.rewriteUrl(url);//實現UrlRewriter#rewriteUrl方法 if (rewritten == null) { throw new IOException("URL blocked by rewriter: " + url); } url = rewritten; } URL parsedUrl = new URL(url);//解析后Url HttpURLConnection connection = openConnection(parsedUrl, request); for (String headerName : map.keySet()) {//為connection添加屬性 connection.addRequestProperty(headerName, map.get(headerName)); } setConnectionParametersForRequest(connection, request);//設置請求方法 // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); int responseCode = connection.getResponseCode(); if (responseCode == -1) {//不能取回ResponseCode // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus);//通過responseStatus獲得一個BasicHttpResponse response.setEntity(entityFromConnection(connection));//設置Entity for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {//header if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response; }
第一步:獲取請求地址url,如果寫了UrlRewriter就會按照這個接口的規則更改URL。
第二步:建立URL,通過createConnection()方法獲得一個HttpURLConnection,通過openConnection()方法設置一些超時類的Http配置,然后添加頭部,並且通過setConnectionParametersForRequest()方法設置HttpURLConnection的請求方法(PUT.GET,POST,DELETE...)。
第三步:然后通過HttpURLConnection獲取ResponseCode,ResponseMessage,ResponseStatus,BasicHttpResponse響應結果,並進行響應處理。
第四步:通過entityFromConnection(),把HttpURLConnection獲得的流轉化為HttpEntity,並返回帶HttpEntity的HttpResponse。
/** * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}. * 從HttpURLConnection獲取一個HttpEntity * @param connection * @return an HttpEntity populated with data from <code>connection</code>. */ private static HttpEntity entityFromConnection(HttpURLConnection connection) { BasicHttpEntity entity = new BasicHttpEntity(); InputStream inputStream;//首先從HttpURLConnection獲取一個輸入流 try { inputStream = connection.getInputStream(); } catch (IOException ioe) { inputStream = connection.getErrorStream(); } entity.setContent(inputStream);//把流設置為HttpEntity的Content entity.setContentLength(connection.getContentLength());//設置內容長度 entity.setContentEncoding(connection.getContentEncoding());//設置編碼格式 entity.setContentType(connection.getContentType());//設置內容類型 return entity; }
為什么HurlStack沒有onPrepareRequest()方法,如果要的話那就只有知己加了。
來看擴展HttpClientStack的一個類:
public class IpadHttpStack extends HttpClientStack{ interface OnStartListener{ void onStart(HttpUriRequest request); } OnStartListener mOnStartListener; static String ua = "ipad"; public IpadHttpStack() { super(AndroidHttpClient.newInstance(ua)); // TODO Auto-generated constructor stub } @Override protected void onPrepareRequest(HttpUriRequest request) throws IOException { // TODO Auto-generated method stub super.onPrepareRequest(request); if(mOnStartListener!=null) mOnStartListener.onStart(request); } public void setOnStartListener(OnStartListener listener) { this.mOnStartListener = listener; } }
這是測試類,訪問路由器網關。
public class MainActivity extends Activity { RequestQueue mQueue; IpadHttpStack bvinHttp; private static HashSet<Class<?>> exeptionList = new HashSet<Class<?>>(); static{ exeptionList.add(AuthFailureError.class); exeptionList.add(NetworkError.class); exeptionList.add(NoConnectionError.class); exeptionList.add(ParseError.class); exeptionList.add(ServerError.class); exeptionList.add(TimeoutError.class); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.e("onResponse","sdgdsg"); bvinHttp = new IpadHttpStack(); mQueue = Volley.newRequestQueue(getApplicationContext(),bvinHttp); //StringRequest四個構造參數分別是Request類型,url,網絡請求響應監聽器,錯誤監聽器 bvinHttp.setOnStartListener(new IpadHttpStack.OnStartListener() { @Override public void onStart(HttpUriRequest request) { // TODO Auto-generated method stub } }); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { // TODO Auto-generated method stub return new PasswordAuthentication("admin", "admin".toCharArray()); } }); mQueue.add(new StringRequest(Method.GET, "http://192.168.1.1", new Listener<String>(){ @Override public void onResponse(String arg0) { // TODO Auto-generated method stub Log.e("onResponse", arg0); } }, new ErrorListener(){ @Override public void onErrorResponse(VolleyError arg0) { // TODO Auto-generated method stub if (arg0 instanceof TimeoutError) { Log.e("onErrorResponse", "超時"); }else if(arg0 instanceof AuthFailureError){ Log.e("AuthFailureError", arg0.toString()); } Log.e("AuthFailureError", arg0.toString()); //exeptionList.contains(arg0) } })); mQueue.start(); } @Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); mQueue.stop(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub menu.add("取消"); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub mQueue.cancelAll(new RequestFilter(){ @Override public boolean apply(Request<?> arg0) { // TODO Auto-generated method stub arg0.cancel(); return false; }}); return super.onOptionsItemSelected(item); } }
