我是靠谱客的博主 安静草莓,最近开发中收集的这篇文章主要介绍浅谈Android中Http请求与缓存(上)1. HttpUrlConnection2.封装,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

二月春风似剪刀,在这把剪刀的裁剪下,三月里春天正式拉开了她得帷幕,看到武大学妹发的樱花照片,正是美得无法收拾,令人陶醉,先给大家上两张,共同欣赏
这里写图片描述
来张近的:
这里写图片描述
再来张特写
这里写图片描述
好一只张可爱的乌龟:
这里写图片描述

连池塘里面的乌龟,都被这美景诱惑,爬上了水面,哈哈。。

时光荏苒,一转眼离开武大已经5年了,但是当我看到这些图的时候,我的思绪一下就回到了5年前,樱顶、梅园、情人坡、奥场… 种种场景历历在目,好怀念那所大学,下辈子,我愿意生在武大,长在武大,学在武大,老在武大…

好像越写越偏离主题了是不是,今天不是来跟大家分享技术的吗,哈哈。 好吧,还是先整理下思绪,开始正题吧,其实说不上分享,共同学习吧,我们知道樱花开了不是吗,但是想我这种人在杭州却想观赏武大樱花的朋友,尤其是像我这种毕业多年的校友们,肯定想一睹樱花的美丽,不要急,今天百度推出了樱花直播,朋友们,只需要在百度搜索里面搜索武大樱花节,就可以直接观看到武大樱花,从多种角度进行观赏,如果运气好的话,还可以看到很多漂亮的MM哦,哈哈,互联网给大家带来的实惠实在是明显了,我们今天要聊的话题其实跟樱花直播也有关系哦,我们知道在百度里面搜索的时候首先是不是要发一个HTTP请求,是不是,如果没有这个请求,我们怎么能拿到数据,或的直播的接口呢?哈哈关系是有点儿牵强,废话说太多了,让我们切入正题吧。

转载请注明出处:http://blog.csdn.net/qinjunni2014/article/details/44570473

互联网对世界的影响越来越大,如今任何事物都可以跟互联网扯上关系了,连克强总理都在两会上提出了“互联网+”的概念,相信如今已经全面进入了互联网改造社会的时代,互联网是基于网络请求的,而Http请求就是网络请求中用的最多的一个请求,今天我们就来浅谈一下HTTP请求如何在Android中合理的使用。

1. HttpUrlConnection

Android中发送http请求有两种方法,一种是使用HttpClient,另外一种就是HttpUrlConnection,但是如今使用最多的是HttpUrlConnection,因为它更轻量,而且对缓存和安全的支持比较好。我们先来看一个基于HttpUrlConnection建立http请求最简单的例子,

String geturl = "http://www.baidu.com;
try {
    URL getUrl = new URL(geturl);
    HttpURLConnection connection = (HttpURLConnection) getUrl.openConnection();
    // 进行连接,但是实际上get request要在下一句的connection.getInputStream()函数中才会真正发到
    // 服务器
    connection.connect();
    // 取得输入流,并使用Reader读取
    BufferedReader reader = new BufferedReader(new InputStreamReader(
            connection.getInputStream()));
    System.out.println("=============================");
    System.out.println("Contents of get request");
    System.out.println("=============================");
    String line;
    while ((line = reader.readLine()) != null) {
        Log.e("Junli", line);
    }
    reader.close();
    // 断开连接
    connection.disconnect();
    System.out.println("=============================");
    System.out.println("Contents of get request ends");
    System.out.println("=============================");
} catch (IOException e) {
    e.printStackTrace();
}

我们使用Get请求,去获得百度的主页,只需要简单地五步:

  1. new URL(String url)创建一个URL对象
  2. openConnection URL对象打开连接,获得一个HttpUrlConnection
  3. connect, 建立连接,我们知道http是基于Tcp的,这一步只是建立起Tcp连接,而并没有真正发http请求
  4. connection.getInputStream获取输入流,这一步才会真正去发request,拿到相应。

这个例子确实很简单,我们来看一个稍微复杂点的:

public void doPostRequest(String _url, List<NameValuePair> params) {
   URL url = null;
    try {
        url = new URL(_url);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000);
        conn.setConnectTimeout(15000);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded; charset= utf-8");

        conn.setDoInput(true);
        conn.setDoOutput(true);

        OutputStream os = conn.getOutputStream();
        BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(os, "UTF-8"));
        writer.write(getQuery(params));
        writer.flush();
        writer.close();

        os.close();
        conn.connect();
        conn.getResponseCode();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                conn.getInputStream()));

        // do something on reader

        reader.close();
        // 断开连接
        conn.disconnect();

    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

private String getQuery(List<NameValuePair> params) throws UnsupportedEncodingException {
    StringBuilder result = new StringBuilder();
    boolean first = true;

    for (NameValuePair pair : params) {
        if (first)
            first = false;
        else
            result.append("&");

        result.append(URLEncoder.encode(pair.getName(), "UTF-8"));
        result.append("=");
        result.append(URLEncoder.encode(pair.getValue(), "UTF-8"));
    }

    return result.toString();
}

doPostRequst 通过调用setRequestMethod(“POST”) 尝试发送一个post请求,我们post请求是可以包含body的,我们可以讲我们需要传递给服务器的参数放在body部分。
1. setConnectTimeOut指定建立连接的超时时间,如果超过这个时间,就会抛出SocketTimeOutException,如果我们没有指定,那么默认值是0,但是不代表永远不超时,在数分钟之后仍然会抛出TCP超时
2. setReadTimeOut是指定数据读取的超时时间
3. setRequestProperty,设置请求的属性,在这个函数里面设置的属性,就是配置了Http请求头中的属性, 比如上面例子中,我们配置了Content-type属性
4. 通过connection.getOutPutStream拿到输出流,向这个流中写入我们想传得参数,当然在这之前我们必须调用setDoOutput(true)允许connection写入数据,
5. setDoInput 指定connection是否允许接受数据,默认情况下位true,通常不需要设置

如果我们每次,要发送http请求都需要自己构造HttpUrlConnection,确实很繁琐,多余,因为需要自己去设置header和body,部分。如果涉及到请求的取消等操作,那就更繁琐,如果能把这些东西继承起来,包装成一个更易容的接口岂不是更好,没错Volley就是这样一个库,类似的库有很多,比如ion,OkHttp等,我相信大部分人都熟练能够熟练使用volley了,如果还不会,请参考郭霖的文章,他详细讲解了volley的基本用法。今天我要讲的是volley是如何封装httpUrlConnection的。

2.封装

我们先从RequestQueue的创建看起

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

我们注意到在Volley.newRequestQueue时,我们会判断当前系统api版本,如果大于9,则会生成一个HurlStack,否则会生成一个HttpClientStack,其中HurlStack是使用HttpUrlConnection进行http请求的发送,而后者则是使用HttpClient。

我们直接进入到HurlStack,找到performRequest函数,

public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
    String url = request.getUrl();
    HashMap<String, String> map = new HashMap<String, String>();
    map.putAll(request.getHeaders());
    map.putAll(additionalHeaders);
    if (mUrlRewriter != null) {
        String rewritten = mUrlRewriter.rewriteUrl(url);
        if (rewritten == null) {
            throw new IOException("URL blocked by rewriter: " + url);
        }
        url = rewritten;
    }
    URL parsedUrl = new URL(url);
    HttpURLConnection connection = openConnection(parsedUrl, request);
    for (String headerName : map.keySet()) {
        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) {
        // -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);
    response.setEntity(entityFromConnection(connection));
    for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
        if (header.getKey() != null) {
            Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
            response.addHeader(h);
        }
    }
    return response;
}

Somehow, 没有缓存的request会走到这一步,大家肯定能一眼就看到HttpUrlConnection, 没错,这里volley对其进行了封装,我们可以看到熟悉的创建URL,openConnection还有addRequestProperty,可并没有setDoInOutput, setRequestMethod等,没关系,都在 setConnectionParametersForRequest这个函数里面,

static void setConnectionParametersForRequest(HttpURLConnection connection,
            Request<?> request) throws IOException, AuthFailureError {
switch (request.getMethod()) {
     case Method.GET:
         // Not necessary to set the request method because connection defaults to GET but
         // being explicit here.
         connection.setRequestMethod("GET");
         break;
     case Method.POST:
         connection.setRequestMethod("POST");
         addBodyIfExists(connection, request);
         break;
     //...other methods
}

这个函数针对不同的http请求方法对connection做了不同的设置,我们主要看Get和Post部分,Get方法,因为参数都在请求url里面,因此不需要添加任何的body,但是post的方法就需要了。我们接着看addBodyIfExists

private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
            throws IOException, AuthFailureError {
   byte[] body = request.getBody();
    if (body != null) {
        connection.setDoOutput(true);
        connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
        DataOutputStream out = new DataOutputStream(connection.getOutputStream());
        out.write(body);
        out.close();
    }
}

这个函数里面我们要读取request的两个属性, 第一个是getBodyContentType,并调用connection的setRequestProperty将这个属性写到请求头里面,第二个是request.getBody,我们会将读取到的body byte数组,写到connection.getOutputStream,跟我们上一章节讲的一样,而且需要首先设置setDoOutput(true)。

但是很多朋友肯定会问,我们在用volley时,写post参数,通常是用一个map

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

/**
 * Converts <code>params</code> into an application/x-www-form-urlencoded encoded string.
 */
private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
    StringBuilder encodedParams = new StringBuilder();
    try {
        for (Map.Entry<String, String> entry : params.entrySet()) {
            encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
            encodedParams.append('=');
            encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
            encodedParams.append('&');
        }
        return encodedParams.toString().getBytes(paramsEncoding);
    } catch (UnsupportedEncodingException uee) {
        throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
    }
}

相信大家看到上述代码,肯定豁然开朗,原来参数原先也是从getParams中拿,但是,拿到之后做了进一步的编码,我们看ecodeParameters函数,会发现,从getparams中拿到的每一对参数都是先用getParamsEncoding拿到的编码方式,去进行编码,然后用“=”符号连接起来的。大家肯定有有疑问了,http body的编码方式不应该是从header部分content-type拿到的吗?对,我们来看代码:

protected String getParamsEncoding() {
  return DEFAULT_PARAMS_ENCODING;
 }

 /**
  * Returns the content type of the POST or PUT body.
  */
 public String getBodyContentType() {
     return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
 }

Request的getBodyContentType实际上调用了getparamsEncoding,默认的编码方式是utf-8,这两个函数我们在定义自己的Request的时候都是可以去重写的,这样就能定义自己http请求的编码方式和content-type。

Body部分的添加就是如此简单,可能对http更有经验的朋友们会提出疑问了

2.1 发送Json

  1. 如果我的body部分不是键值对的形式了,我如果是想传一段json或者其他格式的文件过去呢? 或者我是想向服务器上传一个文件过去呢?
    如果是第一种情况的话,我们只需要重写getBody,并返回一段代表指定格式数据的byte数组即可,我们以json格式为例:我们只需要将一个JSONObject转为String,并调用String的getBytes即可,其实我说的这个volley已经帮我们做了,请看JsonRequest中的getBody方法
 @Override
 public byte[] getBody() {
    try {
         return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
     } catch (UnsupportedEncodingException uee) {
         VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
                 mRequestBody, PROTOCOL_CHARSET);
         return null;
     }
 }

这个mRequestBody就是代表json的字符串。需要特别提醒大家注意的是,如果你想继承JsonRequst,就不要再在getParams里面设置post参数了,很明显getBody已经不会再去调用getParams拿参数了,加了也是白加

2.2 上传文件

  1. 如果你想上传文件怎么办呢?
    对前端很熟悉的朋友们一定知道在用表单上传文件到服务器时,我们需要制定表单的ENCTYPE=”multipart/form-data”,同样,同样我们猜想可以讲content-type设为multipart/form-data,然后将文件流转为为byte数组发送出去,大致是不错的,可是在body部分的处理上还是有很多区别的,比如我们想同时传一个文件和一个字符串过去,就像html表单一样,或者我们想同时上传两个文件,该如何呢?这个时候我们是不是该在body部分有个分隔符把这三者给分开开来,对,我们需要定义一个边界,话说HTTP协议就是这么规定的,感兴趣的朋友们可以详细研究RFC1867,我们只需要知道大概的表现形式,学会如何去用就可以了,我从RFC中copy了一个例子
<FORM ACTION="http://server.dom/cgi/handle"
     ENCTYPE="multipart/form-data"
      METHOD=POST>
What is your name? <INPUT TYPE=TEXT NAME=submitter>
What files are you sending? <INPUT TYPE=FILE NAME=pics>
</FORM>

这是一段html,话说我怎么开始讲html了,但是事实上互联网时出现的,移动互联网是小弟,说以我们想移动端该如何做,可以先模仿web上的机制,话说回来,刚才那个表单提交时会发出一个post请求,大家可以用fiddler设置代理截取这短内容,请求的格式如下:

 Content-type: multipart/form-data, boundary=AaB03x

 --AaB03x
 content-disposition: form-data; name="field1"

 Joe Blow
 --AaB03x
 content-disposition: form-data; name="pics"; filename="file1.txt"
 Content-Type: text/plain

  ... contents of file1.txt ...
 --AaB03x--

其中header部分包括content-type属性为multipart/form-data, boundary=AaB03x,指定了一个boundary,这个boundary就是用来分割body部分的数据的,对于body部分的数据,我们会对每一块数据都指定一个头header,这样服务器就可以从header知道如何去解码和存储这些数据,对于string类型,我们只需要content-disposition 和name属性即可,对于文件上传,我们还需要指定filename和content-type属性。 header后面是一个空行,请注意这个空行很重要,指示了header部分结束,下面就是真正的data部分,data结束了之后也是一个空行,最后就是分割结束。这样就完整的构造了一个上传文件的post请求,我写了一个用HttpUrlConnection来实现这部分的代码,朋友们可以参考

public static String uploadFile(String RequestURL, File file) {
       String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
       String PREFIX = "--", LINE_END = "rn";
       String CONTENT_TYPE = "multipart/form-data"; // 内容类型
       try {
           URL url = new URL(RequestURL);
           HttpURLConnection conn = (HttpURLConnection) url.openConnection();
           conn.setReadTimeout(TIME_OUT);
           conn.setConnectTimeout(TIME_OUT);
           conn.setDoInput(true); // 允许输入流
           conn.setDoOutput(true); // 允许输出流
           conn.setUseCaches(false); // 不允许使用缓存
           conn.setRequestMethod("POST"); // 请求方式
           conn.setRequestProperty("Charset", CHARSET); // 设置编码
           conn.setRequestProperty("connection", "keep-alive");
           conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary="
                   + BOUNDARY);
           if (file != null) {
               /**
                * 当文件不为空,把文件包装并且上传
                */
               OutputStream outputSteam = conn.getOutputStream();

               DataOutputStream dos = new DataOutputStream(outputSteam);
               StringBuffer sb = new StringBuffer();
               sb.append(PREFIX);
               sb.append(BOUNDARY);
               sb.append(LINE_END);
               /**
                * 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
                * filename是文件的名字,包含后缀名的 比如:abc.png
                */

               sb.append("Content-Disposition: form-data; name="img"; filename=""
                       + file.getName() + """ + LINE_END);
               sb.append("Content-Type: application/octet-stream; charset="
                       + CHARSET + LINE_END);
               sb.append(LINE_END);
               dos.write(sb.toString().getBytes());
               InputStream is = new FileInputStream(file);
               byte[] bytes = new byte[1024];
               int len = 0;
               while ((len = is.read(bytes)) != -1) {
                   dos.write(bytes, 0, len);
               }
               is.close();
               dos.write(LINE_END.getBytes());
               byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END)
                       .getBytes();
               dos.write(end_data);
               dos.flush();
               /**
                * 获取响应码 200=成功 当响应成功,获取响应的流
                */
               int res = conn.getResponseCode();
               if (res == 200) {
                   return SUCCESS;
               }
           }
       } catch (MalformedURLException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }
       return FAILURE;
   }
}

其实说白了,就是按照Http协议规定的格式,填充好body部分的数据,设置好content-type就可以发送了。话说,如果把这个封装在volley就是一个上传文件的Request,嘿嘿。不过上面的代码有很多不严谨的地方,比如没有考虑字符串和文件同时存在的情况,也没有考虑同时上传多个文件的情况。幸好,这些代码不用自己写,我们可以利用现有的库HttpClient中的MultipartEntityBuilder帮我们达成。那废话我就不多说了,直接上一块代码:


import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyLog;

import org.apache.http.HttpEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Junli on 3/23/15.
 */
public class MultipartRequest extends Request<String> {

    private HttpEntity mEntity;
    private static final String FILE_PART_NAME = "file";
    private static final String STRING_PART_NAME = "text";

    private final Response.Listener<String> mListener;
    private List<File> mFileParts = new ArrayList<>();
    private List<String> mStringParts = new ArrayList<>();

    public MultipartRequest(String url, List<File> files, List<String> stringPart,
                            Response.ErrorListener errorListener, Response.Listener<String> listener )
    {
        super(Request.Method.POST, url, errorListener);

        mListener = listener;
        mFileParts = files;
        mStringParts = stringPart;
        buildMultipartEntity();
    }

    private void buildMultipartEntity()
    {
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();

        int i = 0;
        for(File file : mFileParts)
        {
            builder.addPart(FILE_PART_NAME + i, new FileBody(file));
            i++;
        }

        i = 0;
        for(String str : mStringParts)
        {
            try {
                builder.addPart(STRING_PART_NAME+i, new StringBody(str));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            i++;
        }



        mEntity = builder.build();
    }

    @Override
    public String getBodyContentType()
    {
        return mEntity.getContentType().getValue();
    }

    @Override
    public byte[] getBody() throws AuthFailureError
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            mEntity.writeTo(bos);
        } catch (IOException e) {
            VolleyLog.e("IOException writing to ByteArrayOutputStream");
        }
        return bos.toByteArray();
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response)
    {
        return Response.success("Uploaded", getCacheEntry());
    }

    @Override
    protected void deliverResponse(String response)
    {
        mListener.onResponse(response);
    }
}

这样,我们就自定义了一个multipart/form-data类型的Request类型,可是,上传文件这么重要的操作,为什么volley不帮我们做好呢?我们看源码Request里面直接默认的content-type类型就是application/x-www-form-urlencoded,似乎Google不建议我们用volley上传文件? 正是如此,在此我引用stackoverflow上一位朋友的陈述:

As mentioned in the presentation at the I/O (about 4:05), Volley “is terrible” for large payloads. As I understand it that means not to use Volley for receiving/sending (big) files. Looking at the code it seems that it is not even designed to handle multipart form data (e.g. Request.java has getBodyContentType() with hardcoded “application/x-www-form-urlencoded”; HttpClientStack::createHttpRequest() can handle only byte[], etc…). Probably you will be able to create implementation that can handle multipart but If I were you I will just use HttpClient directly with MultipartEntity。

他说Google不建议我们使用volley建议大文件的发送和接受,当然我们可以自己定义这样的实现,但是效率会很差。建议使用HttpClient加MultipartEntity实现大文件的发送和接受,像这样的代码:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost uploadFile = new HttpPost("...");

MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addTextBody("field1", "yes", ContentType.TEXT_PLAIN);
builder.addBinaryBody("file", new File("..."), ContentType.APPLICATION_OCTET_STREAM, "file.ext");
HttpEntity multipart = builder.build();

uploadFile.setEntity(multipart);

response = httpClient.execute(uploadFile);
responseEntity = response.getEntity();

好了,对于文件的上传,我们讲了很久了,对于这部分不敢兴趣的朋友,实在对不住了,马上换题目。

2.3 返回response

如果不出意外,我们大部分的Request都能发送出了,现在就等着返回了,从哪里返回呢?刚才我们讲HttpUrlConnection时谈到,直到调用connection.getInputStream才会发送真正的http请求,获得response,我们看HurlStack是怎么做的。

 setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
    // -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);
response.setEntity(entityFromConnection(connection));
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
    if (header.getKey() != null) {
        Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
        response.addHeader(h);
    }
}
return response;

 private static HttpEntity entityFromConnection(HttpURLConnection connection) {
   BasicHttpEntity entity = new BasicHttpEntity();
    InputStream inputStream;
    try {
        inputStream = connection.getInputStream();
    } catch (IOException ioe) {
        inputStream = connection.getErrorStream();
    }
    entity.setContent(inputStream);
    entity.setContentLength(connection.getContentLength());
    entity.setContentEncoding(connection.getContentEncoding());
    entity.setContentType(connection.getContentType());
    return entity;
}

从代码中,我们看到从调用connection.getResponseCode开始,我们陆续拿到了http相应的返回码,其他还有
1. getResponseMessage 比如 OK, NotFound等
2. getInputStream拿到body部分的数据content,
3. getContentLength拿到Content-length
4. getContentEncoding getContentType拿到content的编码方式和类型
5. getHeaderFields返回一个map

2.4 解码数据

BasicNetwork返回的response为NetworkResponse, NetworkResponse与HttpResponse类似,唯一的区别就是httpresposne中的entity为stream,而NetworkResponse,将从这个stream里面读取数据,转为Byte数组。读取到这个byte数组之后,我们需要进一步将其转为用户可以直接使用的类型,这一步需要调用

Response<?> response = request.parseNetworkResponse(networkResponse);

至于怎么转化,每个不同的类型有转码方式,基类Request肯定不知道怎么做,需要子类去实现,因此这个方法是个abstract函数,子类怎么实现,取决于它需要拿取的数据,我们来看一个简单地例子,比如StringRequest

public class StringRequest extends Request<String> {

    @Override
    protected Response<String> 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));
    }
}

StringRequest继承自Request,请注意泛型类Request中的T指的就是我们需要从response中获取的数据类型。对于StringRequest当然就是String, parse的过程也很简单,首先去header部分拿到charset,然后用这个charset去解码。拿到解码后的String之后,再用静态方法success构建出一个Response出来,Response的构造函数第二个参数是一个CacheEntry,这个是用来做缓存的。

Volley里面帮我们实现了一个类JsonRequest,这个Request 会在http请求中发送json数据,至于Response的数据类型,取决于T的类型,我们可以根据T的类型采取不同的parse方法,如果我们既需要发json也需要接受json,可以用它的子类JsonObjectRequest

public class JsonObjectRequest extends JsonRequest<JSONObject>{
@Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
            return Response.success(new JSONObject(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }
}

JsonObjectRequest会利用Response的数组先解码为string,然后用这个string直接构造出一个JSONObject。

拿到数据之后,最后一步是mDelivery.postResponse(request, response); 这个mDelivery是一个ResponseDelivery类,默认情况下volley会传一个ExecutorDelivery类,这个类使用一个Executor来执行我们

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

public ExecutorDelivery(final Handler handler) {
  // Make an Executor that just wraps the handler.
    mResponsePoster = new Executor() {
        @Override
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}

这个类,会将我们需要执行的任务以Runnable的形式post到主线程上去,那我们postResponse做了什么呢?

@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
     request.markDelivered();
     request.addMarker("post-response");
     mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
 }
private class ResponseDeliveryRunnable implements Runnable {

@SuppressWarnings("unchecked")
@Override
public void run() {
    //....
    // Deliver a normal response or error, depending.
    if (mResponse.isSuccess()) {
        mRequest.deliverResponse(mResponse.result);
    } else {
        mRequest.deliverError(mResponse.error);
    }
    //....
}

因此看这个代码,我们就知道了最后我们调的就是Request类中得deliverResponse函数,这个函数也是个abstract函数,需要我们子类自己去实现,大部分的实现都是去调用ResponseLisnter,通知用户,相应返回了。

从发送到返回,相信大家对Request的工作原理也明白了不少,我来整理下大家在自定义自己的Request的需要做的事情:
1. getBody与getParams 都是用来设置请求体的,如果是键值对,直接在getParams里设置就可以了,如果是其他数据类型,我们可以重写getBody将数据编码成byte数组返回
2. getBodyContentType 设置header中content-type属性,这个也可以在getheader中设置
3. 如果有其它属性需要设置,比如关于cache和use-agent就重写getHeader
4. 重写parseNetworkRespse,在这里将返回的byte数组转成我们需要的类型
5. 重写deliverResponse,调用ResponseListener或者对返回数据做更多的自定义操作

相信大家看到这里也就累了,不管怎么样希望能给朋友们带来收获吧,先写到这里,这只是上篇,还有下篇,朋友们不要走太远哦^_^

这里写图片描述

最后

以上就是安静草莓为你收集整理的浅谈Android中Http请求与缓存(上)1. HttpUrlConnection2.封装的全部内容,希望文章能够帮你解决浅谈Android中Http请求与缓存(上)1. HttpUrlConnection2.封装所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部