Android 网络编程和网络框架(6.4、6.6 、7.4 未测试)

一、网络分层

参考:互联网协议入门
在这里插入图片描述

分清 TCP、UDP 和 HTTP 协议的区别。
传输层有两个传输协议:TCP(传输控制协议)和 UDP(用户数据报协议),TCP 是一个可靠的面向连接的协议,UDP 是不可靠的或者说无连接的协议。
应用层是传输层的上层,作用是对传输层的数据进行解读,应用层规定了对传输层的数据进行解读的格式。如 HTTP、FTP、Telnet、SMTP、POP3 等。

二、TCP 的三次握手与四次挥手

参考:TCP报文格式

示意图:

在这里插入图片描述

2.1、三次握手

  • 第一次握手:建立连接。客户端发送连接请求报文段,将 SYN 设置为 1、Sequence Number(seq)为 x;接下来客户端进入 SYN_SENT 状态,等待服务端的确认。
  • 第二次握手:服务器收到客户端的 SYN 报文段,对 SYN 报文段进行确认,设置 Acknowledgement Number(ACK)为 x+1(seq+1);同时自己还有发送 SYN 请求信息,将 SYN 设置为 1,seq 为 y。服务端将上述所有信息放到 SYN+ACK 报文段中,一并发送给客户端,此时服务端进入 SYN_REVD 状态。
  • 第三次握手:客户端收到服务端的 SYN+ACK 报文段;然后将 ACK 设置为 y+1,向服务端发送 ACK 报文段,这个报文段发送完毕后,客户端和服务端都进入 ESTABLISHED(TCP 连接成功)状态,完成 TCP 的三次握手。

2.2、四次挥手

  • 第一次挥手:客户端设置 seq 和 ACK,向服务端发送一个 FIN 报文段。此时,客户端进入 FIN_WAIT_1 状态,表示客户端没有数据要发送给服务端了。
  • 第二次挥手:服务端收到了客户端发送的 FIN 报文段,向客户端回了一个 ACK 报文段。
  • 第三次挥手:服务端向客户端发送 FIN 报文段,请求关闭连接,同时服务端进入 LAST_ACK 状态。
  • 第四次挥手:客户端收到服务端发送的 FIN 报文段,向服务端发送 ACK 报文段,然后客户端进入 TIME_WAIT 状态。服务端收到客户端的 ACK 报文段以后,就关闭连接。此时,客户端等待 2MSL(最大报文段生存时间)后依然没有收到回复,则说明服务端已正常关闭,这样客户端也可以关闭连接了。

三、HTTP 协议

3.1、HTTP 简介

3.1.1、HTTP 历史版本

  • HTTP 0.9:1991 年发布的第一个版本,只有一个命令 GET,服务器只能回应 HTML 格式的字符串。
  • HTTP 1.0:1996 年发布的版本,内容量大大增加。除了 GET 命令外,还引入了 POST 命令和 HEAD 命令。HTTP 请求和回应的格式除了数据部分,每次通信都必须包括头信息,用来描述一些元数据。
  • HTTP 1.1:1997 年发布的版本,进一步完善了 HTTP 协议,直到现在还是最流行的版本。
  • SPDY 协议:2009 年谷歌为了解决 HTTP 1.1 效率不高的问题而自行研发的协议。
  • HTTP 2:2015 年新发布的版本,SPDY 协议的主要特性也在此版本中。

3.1.2、HTTP 协议的主要特点

  • 支持 C/S(客户端/服务器)模式。
  • 简单快速:客户端向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有 GET、HEAD、POST,每种方法规定了客户端与服务器联系的类型不同。由于 HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP 允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户端的请求,并受到客户端的应答后,即断开连接。采用这种方式可以节省传输时间。
  • 无状态:HTTP 协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大;而另一方面,在服务器不需要先前信息时它的应答速度就较快。

3.1.3、HTTP URL格式

http://host[":"port][abs_path]

http:表示要通过 HTTP 协议来定位网络资源。
host:表示合法的 Internet 主机域名或者 IP 地址。
port:指定一个端口号,为空使用默认端口 80。
abs_path:指定请求资源的 URI(Web 上任意的可用资源)。

3.2、HTTP 请求报文

HTTP 报文是面向文本的,报文中的每一个字段都是一些 ASCII 码串,各个字段的长度是不确定的。一般一个 HTTP 请求报文由请求行、请求报头、空行和请求数据 4 个部分组成。

在这里插入图片描述

在这里插入图片描述

1、请求行

请求行由请求方法,URL 和协议版本组成。格式如下:

Method Request-URL HTTP-Version CRLF

  • Method:请求方法。
  • Request-URL:统一资源标识符。
  • HTTP-Version:请求的 HTTP 协议版本。
  • CRLF:回车和换行(除了作为结尾的 CRLF 外,不允许出现单独的 CR 或 LF 字符)。

其中 HTTP 的请求方法有如下 8 种。

  • GET:请求获取 Request-URL 所标识的资源。
  • POST:在 Request-URL 所标识的资源后附加新的数据。
  • HEAD:请求获取由 Request-URL 所标识的消息响应报头。
  • PUT:请求服务器存储一个资源,并用 Request-URL 作为其标识。
  • DELETE:请求服务器删除 Request-URL 所标识的资源。
  • TRACE:请求服务器回送收到的请求消息,主要用于测试或诊断。
  • CONNECT:HTTP 1.1 协议中预留给能够将连接改为管道方式的代理服务器。
  • OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求。

2、 请求报头

在请求行之后会有 0 个或者多个请求报头,每个请求报头都包含一个名字和一个值,它们之间用英文冒号 “:” 分割。

3、 请求数据

请求数据不在 GET 方法中使用,而在 POST 方法中使用。POST 方法适用于需要客户端填写表单的场合,与请求数据相关的最常用的请求报头是 Content-Type 和 Content-Length。

3.3、HTTP 响应报文

HTTP 的响应报文由状态行、响应报头、空行、响应正文组成。
在这里插入图片描述

在这里插入图片描述

1、状态行

状态行格式如下。

HTTP-Version Status-Code Reason-Phrase CRLF

  • HTTP-Version:服务器的 HTTP 协议版本。

  • Status-Code :服务器发回的响应状态码,状态码由 3 位数字组成,第一个数字定义了响应的类别。有 5 种可能取值。

    (1)100~199:指示信息,收到请求,需要请求继续执行操作。
    (2)200~299:请求成功,请求已被成功接收并处理。
    (3)300~399:重定向,要完成请求必须进行更进一步的操作。
    (4)400~499:客户端错误,请求有语法错误或请求无法实现。
    (5)500~599:服务器错误,服务器不能实现合法的请求。

    常见的状态码如下。

    (1)200 OK:客户端请求成功。
    (2)400 Bad Request:客户端请求有语法错误,服务器无法理解。
    (3)401 Unauthorized:请求未经授权,这个状态码必须和 WWW-Authenticate 报头域一起使用。
    (4)403 Forbidden:服务器收到请求,但是拒绝提供服务。
    (5)500 Internal Server Error:服务器内部错误,无法完成请求。
    (6)503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。

  • Reason-Phrase:原因短语(状态码描述)。

  • CRLF:回车和换行。

2、响应报头

在状态行之后会有 0 个或者多个请求报头,每个响应报头都包含一个名字和一个值,它们之间用英文冒号 “:” 分割。

3、响应正文

服务器返回的内容,如 JSON 数据。

3.4、HTTP 的消息报头

消息报头分为通用报头、请求报头、响应报头、实体报头等。消息报头由键值对组成,每行一对,关键字和值用英文冒号 “:” 分隔。

3.4.1、通用报头

它既可以出现在请求报头,也可以出现在响应报头中。

  • Date:表示消息产生的日期和时间。
  • Connection:允许发送指定连接的选项。例如指定连接是连续的;或者指定 “close” 选项,通知服务器,在响应完成后,关闭连接。
  • Cache-Control:用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制)。

3.4.2、请求报头

请求报头通知服务器关于客户端请求的信息。典型的请求报头如下。

  • Host:请求的主机名,允许多个域名同处一个 IP 地址,即虚拟主机。
  • User-Agent:发送请求的浏览器类型、操作系统等信息。
  • Accept:客户端可识别的内容类型列表,用于指定客户端接收哪些类型的信息。
  • Accept-Encoding:客户端可识别的数据编码。
  • Accept-Language:表示浏览器所支持的语言类型。
  • Connection:允许客户端和服务器指定与请求/响应连接有关的选项。例如,这时为 Keep-Alive 则表示保持连接。
  • Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。

3.4.3、响应报头

用于服务器传递自身信息的响应。

  • Location:用于重定向接收者到一个新的位置,常用在更换域名的时候。
  • Server:包含服务器用来处理请求的系统信息,与 User-Agent 请求报头是相对应的。

3.4.4、实体报头

实体报头用来定义被传送资源的信息,其既可用于请求也可用于响应。请求和响应消息都可以传送一个实体。

  • Content-Type:发送给接收者的实体正文的媒体类型。
  • Content-Lenght:实体正文的长度。
  • Content-Language:描述资源所用的自然语言。
  • Content-Encoding:实体报头被用作媒体类型的修饰符。它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得 Content-Type 报头域中所引用的媒体类型,必须采用相应的解码机制。
  • Last-Modified:实体报头用于指示资源的最后修改日期和时间。
  • Expires:实体报头给出响应过期的日期和时间。

3.5、使用 Fiddler 抓包

请求报文
响应报文
后续更新。。。

四、HttpClient 与 HttpURLConnection

无论是自己封装的网络请求类还是第三方的网络请求框架,都离不开这两个库。

在这里我们使用淘宝的 IP 地址库,它同时支持 GET 和 POST 请求。首先查询一个 IP 归属地
在这里插入图片描述
再看淘宝 IP 地址库的接口说明
在这里插入图片描述

4.1、HttpClient

Android SDK 中包含了 HttpClient。Android 6.0 版本中直接删除了 HttpClient 类库。如果仍想使用它,解决办法就是在相应 module 下的 build.gradle 中加入如下代码。

android {
    ...
    useLibrary 'org.apache.http.legacy'
}

4.1.1、HttpClient 的 GET 请求

Get 请求的参数暴露在 URL 中,这有些不大妥当,而且 URL 的长度也有限制:长度在 2048 字符之内,在 HTTP 1.1 后 URL 长度才没有了限制。一般情况下 POST 可以替代 GET。

  • 创建 HttpClient。

    private HttpClient createHttpClient() {
        HttpParams params = new BasicHttpParams();
        // 设置连接超时
        HttpConnectionParams.setConnectionTimeout(params, 15000);
        // 设置请求超时
        HttpConnectionParams.setSoTimeout(params, 15000);
        HttpConnectionParams.setTcpNoDelay(params, true);
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
        // 持续握手
        HttpProtocolParams.setUseExpectContinue(params, true);
        HttpClient httpClient = new DefaultHttpClient(params);
        return httpClient;
    }
  • 创建 HttpGet,请求网络并得到 HttpResponse。
    private void useHttpClientGet(String url) {
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Connection", "Keep-Alive");
        try {
            HttpClient httpClient = createHttpClient();
            HttpResponse httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();
            int code = httpResponse.getStatusLine().getStatusCode();
            if (null != httpEntity) {
                InputStream inputStream = httpEntity.getContent();
                String response = convertStreamToString(inputStream);
                Log.d("TAG", "请求状态码:" + code + "\n请求结果:\n" + response);
                inputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  • 将请求结果转换成 String
    private String convertStreamToString(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuffer sb = new StringBuffer();
        String line = null;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        String response = sb.toString();
        return response;
    }
  • 开启线程访问 API。
        new Thread(new Runnable() {
            @Override
            public void run() {
                useHttpClientGet("http://ip.taobao.com/outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc");
            }
        }).start();

4.1.2、HttpClient 的 POST 请求

POST 请求和 GET 请求类似,就是需要配置要传递的参数。请求代码如下。

    private void useHttpClientPost(String url) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Connection", "Keep-Alive");
        try {
            HttpClient httpClient = createHttpClient();
            List<NameValuePair> postParams = new ArrayList<>();
            // 要传递的参数
            postParams.add(new BasicNameValuePair("ip", "125.84.85.202"));
            postParams.add(new BasicNameValuePair("accessKey", "alibaba-inc"));
            httpPost.setEntity(new UrlEncodedFormEntity(postParams));
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            int code = httpResponse.getStatusLine().getStatusCode();
            if (null != httpEntity) {
                InputStream inputStream = httpEntity.getContent();
                String response = convertStreamToString(inputStream);
                Log.d("TAG", "请求状态码:" + code + "\n请求结果:\n" + response);
                inputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
        new Thread(new Runnable() {
            @Override
            public void run() {
                useHttpClientPost("http://ip.taobao.com/outGetIpInfo");
            }
        }).start();

不管是使用 GET 还是 POST,都可以成功获取数据。运行效果如下。
在这里插入图片描述

4.2、HttpURLConnection

在 Android 2.2 版本及其之前的版本使用 HttpClient 是较好的选择;而在 2.3 版本及其之后,HttpURLConnection 则是最佳的选择,它的 API 简单,体积较小,因而非常适用于 Android 项目。HttpURLConnection 的压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。

创建一个 UrlConnManager 类。

public class UrlConnManager {

    public static HttpURLConnection getHttpURLConnectionGet(String url) {
        HttpURLConnection httpURLConnection = null;
        try {
            URL mUrl = new URL(url);
            httpURLConnection = (HttpURLConnection) mUrl.openConnection();
            // 设置连接超时时间
            httpURLConnection.setConnectTimeout(15000);
            // 设置读取超时时间
            httpURLConnection.setReadTimeout(15000);
            // 设置请求方法
            httpURLConnection.setRequestMethod("GET");
            // 添加 Header
            httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
            // 接收输入流
            httpURLConnection.setDoInput(true);
            // 传递参数时需要开启
            httpURLConnection.setDoOutput(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return httpURLConnection;
    }

    public static HttpURLConnection getHttpURLConnectionPost(String url) {
        HttpURLConnection httpURLConnection = null;
        try {
            URL mUrl = new URL(url);
            httpURLConnection = (HttpURLConnection) mUrl.openConnection();
            // 设置连接超时时间
            httpURLConnection.setConnectTimeout(15000);
            // 设置读取超时时间
            httpURLConnection.setReadTimeout(15000);
            // 设置请求方法
            httpURLConnection.setRequestMethod("POST");
            // 添加 Header
            httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
            // 接收输入流
            httpURLConnection.setDoInput(true);
            // 传递参数时需要开启
            httpURLConnection.setDoOutput(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return httpURLConnection;
    }

    public static void postParams(OutputStream output, List<NameValuePair> paramsList) throws IOException {
        StringBuilder builder = new StringBuilder();
        for (NameValuePair pair : paramsList) {
            if (!TextUtils.isEmpty(builder)) {
                builder.append("&");
            }
            builder.append(URLEncoder.encode(pair.getName(), "UTF-8"));
            builder.append("=");
            builder.append(URLEncoder.encode(pair.getValue(), "UTF-8"));
        }
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, "UTF-8"));
        writer.write(builder.toString());
        writer.flush();
        writer.close();
    }
}

4.2.1、HttpURLConnection 的 GET 请求

    private void useHttpUrlConnectionGet(String url) {
        InputStream inputStream = null;
        HttpURLConnection httpURLConnection = UrlConnManager.getHttpURLConnectionGet(url);
        try {
            httpURLConnection.connect();
            inputStream = httpURLConnection.getInputStream();
            int code = httpURLConnection.getResponseCode();
            String response = convertStreamToString(inputStream);
            Log.d("TAG", "请求状态码:" + code + "\n请求结果:\n" + response);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
        new Thread(new Runnable() {
            @Override
            public void run() {
                useHttpUrlConnectionGet("http://ip.taobao.com/outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc");
            }
        }).start();

4.2.2、HttpURLConnection 的 POST 请求

    private void useHttpUrlConnectionPost(String url) {
        InputStream inputStream = null;
        HttpURLConnection httpURLConnection = UrlConnManager.getHttpURLConnectionPost(url);
        try {
            List<NameValuePair> postParams = new ArrayList<>();
            postParams.add(new BasicNameValuePair("ip", "125.84.85.202"));
            postParams.add(new BasicNameValuePair("accessKey", "alibaba-inc"));
            UrlConnManager.postParams(httpURLConnection.getOutputStream(), postParams);
            httpURLConnection.connect();
            inputStream = httpURLConnection.getInputStream();
            int code = httpURLConnection.getResponseCode();
            String response = convertStreamToString(inputStream);
            Log.d("TAG", "请求状态码:" + code + "\n请求结果:\n" + response);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
        new Thread(new Runnable() {
            @Override
            public void run() {
                useHttpUrlConnectionPost("http://ip.taobao.com/outGetIpInfo");
            }
        }).start();

五、解析 Volley

Volley 在访问网络取得数据,加载图片等数据量不大但通信频繁的网络操作,在性能方面有大幅度的调整。而对于大数据量的网络操作,比如下载文件等,Volley 的表现却非常糟糕。

5.1、Volley 基本用法

5.1.1、导包

    implementation 'com.android.volley:volley:1.1.1'

5.1.2、创建队列

Volley 请求网络都是基于队列的,只需要把请求放在请求队列中,请求队列会依次进行请求。一般情况下,如果一个应用程序的网络请求不频繁,可以只有一个请求队列(对应 Application),如果网络请求较多,则可以是一个 Activity 对应一个网络请求队列。

        RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

5.1.3、请求数据类型

根据需求,Volley 提供了 StringRequest ,JsonRequest,ImageRequest 等,其中,StringRequet 返回的数据是 String 类型,JsonRequest 返回的是 Json 格式的,ImageRequest 返回的是一个 Bitmap。

  • StringRequest 提供了两个构造方法,如果不传入请求方法即使用第一个构造方法,默认是 GET 请求。
        StringRequest stringRequest = new StringRequest(Request.Method.GET,
                "http://ip.taobao.com/outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc",
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Log.d("TAG", response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d("TAG", error.getMessage(), error);
                    }
                });
        // 将请求添加到请求队列中
        queue.add(jsonObjectRequest);
D/TAG: {"data":{"area":"","country":"中国","isp_id":"100017","queryIp":"125.84.85.202","city":"重庆","ip":"125.84.85.202","isp":"电信","county":"","region_id":"500000","area_id":"","county_id":null,"region":"重庆","country_id":"CN","city_id":"500100"},"msg":"query success","code":0}
  • JsonRequest 和 StringRequest 使用方法类似,先根据 Json 数据创建一个实体类 IpModel。
public class IpModel {
    private int code;
    private DataBean data;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public DataBean getData() {
        return data;
    }

    public void setData(DataBean data) {
        this.data = data;
    }

    public static class DataBean {

        private String ip;
        private String country;
        private String area;
        private String region;
        private String city;
        private String county;
        private String isp;
        private String country_id;
        private String area_id;
        private String region_id;
        private String city_id;
        private String county_id;
        private String isp_id;

        public String getIp() {
            return ip;
        }

        public void setIp(String ip) {
            this.ip = ip;
        }

        public String getCountry() {
            return country;
        }

        public void setCountry(String country) {
            this.country = country;
        }

        public String getArea() {
            return area;
        }

        public void setArea(String area) {
            this.area = area;
        }

        public String getRegion() {
            return region;
        }

        public void setRegion(String region) {
            this.region = region;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        public String getCounty() {
            return county;
        }

        public void setCounty(String county) {
            this.county = county;
        }

        public String getIsp() {
            return isp;
        }

        public void setIsp(String isp) {
            this.isp = isp;
        }

        public String getCountry_id() {
            return country_id;
        }

        public void setCountry_id(String country_id) {
            this.country_id = country_id;
        }

        public String getArea_id() {
            return area_id;
        }

        public void setArea_id(String area_id) {
            this.area_id = area_id;
        }

        public String getRegion_id() {
            return region_id;
        }

        public void setRegion_id(String region_id) {
            this.region_id = region_id;
        }

        public String getCity_id() {
            return city_id;
        }

        public void setCity_id(String city_id) {
            this.city_id = city_id;
        }

        public String getCounty_id() {
            return county_id;
        }

        public void setCounty_id(String county_id) {
            this.county_id = county_id;
        }

        public String getIsp_id() {
            return isp_id;
        }

        public void setIsp_id(String isp_id) {
            this.isp_id = isp_id;
        }
    }
}
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET,
                "http://ip.taobao.com/outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc",
                new JSONObject(),
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        IpModel ipModel = new Gson().fromJson(response.toString(), IpModel.class);
                        if (null != ipModel && null != ipModel.getData()) {
                            String city = ipModel.getData().getCity();
                            Log.d("TAG", city);
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d("TAG", error.getMessage(), error);
                    }
                });
        // 将请求添加到请求队列中
        queue.add(jsonObjectRequest);
D/TAG: 重庆
  • ImageRequest 加载图片,方法已经过时,使用方法和 StringRequest,JsonRequest 类似,可以设置图片的最大宽度和高度,如果图片超过最大宽高,则会进行压缩。
        ivPic = findViewById(R.id.ivPic);
        ImageRequest imageRequest = new ImageRequest("https://img-blog.csdnimg.cn/20201215103529586.png",
                new Response.Listener<Bitmap>() {
                    @Override
                    public void onResponse(Bitmap response) {
                        ivPic.setImageBitmap(response);
                    }
                }, 0, 0, Bitmap.Config.RGB_565,
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        ivPic.setImageResource(R.mipmap.ic_launcher);
                    }
                });
        // 将请求添加到请求队列中
        queue.add(jsonObjectRequest);

在这里插入图片描述

  • ImageLoader 的内部使用 ImageRequest 来实现,它的构造方法可以传入一个 ImageCache 缓存形参,实现图片缓存的功能;同时还可以过滤重复连接,避免重复发送请求。与 ImageRequest 不同,ImageLoader 加载图片会先显示默认的图片,等待图片加载完成才会显示在 ImageView 上。
public class BitmapCache implements ImageLoader.ImageCache {
    private LruCache<String, Bitmap> mCache;

    public BitmapCache() {
        int maxSize = 8 * 1024 * 1024;
        mCache = new LruCache<String, Bitmap>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight();
            }
        };
    }

    @Override
    public Bitmap getBitmap(String url) {
        return mCache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        mCache.put(url, bitmap);
    }
}
            ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
            ImageLoader.ImageListener listener = ImageLoader.getImageListener(ivPic, R.mipmap.ic_default, R.mipmap.ic_default);
            // imageLoader.get("https://img-blog.csdnimg.cn/20201215103529586.png", listener);
            // imageLoader.get("https://img-blog.csdnimg.cn/20201215103529586.png", listener, 0, 0);
            // 设置最大宽高,设置 ImageView 的 ScaleType 属性
            imageLoader.get("https://img-blog.csdnimg.cn/20201215103529586.png", listener, 0, 0, ImageView.ScaleType.CENTER_CROP);
  • NetworkImageView 是一个自定义控件,继承自 ImageView,其封装了网络加载图片的功能。没有提供最大宽高的设置方法,而是根据设置的控件的宽高结合网络图片的宽高,内部会自动实现压缩。如果不想要压缩,可以设置 NetworkImageView 的宽高为 wrap_content。
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/nivPic"
        android:layout_width="200dp"
        android:layout_height="200dp" />
        nivPic = findViewById(R.id.nivPic);
        ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
        nivPic.setDefaultImageResId(R.mipmap.ic_default);
        nivPic.setErrorImageResId(R.mipmap.ic_default);
        nivPic.setImageUrl("https://img-blog.csdnimg.cn/20201215103529586.png", imageLoader);

六、解析 OkHttp

6.1、配置 gradle,GitHub 地址

implementation 'com.squareup.okhttp3:okhttp:4.9.0'

6.2、异步 GET 请求

        Request.Builder requestBuilder = new Request.Builder().url("http://ip.taobao.com/outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc");
        requestBuilder.method("GET", null);
        Request request = requestBuilder.build();
        OkHttpClient okHttpClient = new OkHttpClient();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Toast.makeText(getApplicationContext(), "请求失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                String str = response.body().string();
                Log.d("TAG", str);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });

基本步骤就是创建 OkHttpClient、Request 和 Call,最后调用 Call 的 enqueue() 方法。但是每次这么写很麻烦,是需要进行封装的。需要注意的是 onResponse 回调并非是在 UI 线程,如果想要调用同步 GET 请求,就调用 Call 的 execute 方法。

6.3、异步 POST 请求

        RequestBody formBody = new FormBody.Builder()
                .add("ip", "125.84.85.20")
                .add("accessKey", "alibaba-inc")
                .build();
        Request request = new Request.Builder()
                .url("http://ip.taobao.com/outGetIpInfo")
                .post(formBody)
                .build();
        OkHttpClient okHttpClient = new OkHttpClient();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Toast.makeText(getApplicationContext(), "请求失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                String str = response.body().string();
                Log.d("TAG", str);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });

6.4、异步上传文件(未测试)

Android 6.0 以上需要动态申请权限

上传文件本身也是一个 POST 请求,首先定义上传文件类型。

    public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown;charset=utf-8");

在 SD 卡根目录创建一个 test.txt 文件,里面的内容为 “OkHttp”。

        String filePath = "";
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            filePath = Environment.getExternalStorageDirectory().getAbsolutePath();
        } else {
            return;
        }
        File file = new File(filePath, "test.txt");
        Request request = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
                .build();
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Toast.makeText(getApplicationContext(), "文件上传失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.d("TAG", response.body().string());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), "文件上传成功", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });

如果要改为同步上传文件,只要调用 OkHttpClient.newCall(request).execute() 就可以了。最终请求网络返回的结果就是我们 txt 文件中的内容。

6.5、异步下载文件

Android 6.0 以上需要动态申请权限

        String url = "https://img-blog.csdnimg.cn/20201215103529586.png";
        Request request = new Request.Builder().url(url).build();
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Toast.makeText(getApplicationContext(), "文件下载失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                InputStream inputStream = response.body().byteStream();
                FileOutputStream fileOutputStream = null;
                String filepath = "";
                try {
                    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        filepath = Environment.getExternalStorageDirectory().getAbsolutePath();
                    } else {
                        filepath = getFilesDir().getAbsolutePath();
                    }
                    File file = new File(filepath, "test.png");
                    if (null != file) {
                        fileOutputStream = new FileOutputStream(file);
                        byte[] buffer = new byte[2048];
                        int len = 0;
                        while ((len = inputStream.read(buffer)) != -1) {
                            fileOutputStream.write(buffer, 0, len);
                        }
                        fileOutputStream.flush();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(getApplicationContext(), "文件存储成功", Toast.LENGTH_SHORT).show();
                            }
                        });
                    } else {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(getApplicationContext(), "文件存储失败", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                } catch (IOException e) {
                    Log.e("TAG", "IOException");
                    e.printStackTrace();
                }
            }
        });

6.6、异步上传 Multipart 文件(未测试)

Android 6.0 以上需要动态申请权限

有时上传文件,同时还需要传其他类型的字段。在这里并没有服务器接收这个 Multipart 文件,只是示例。

        OkHttpClient okHttpClient = new OkHttpClient();
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("title", "test") // 1
                .addFormDataPart("image", "test.png", RequestBody.create(MEDIA_TYPE_PNG, new File("/sdcard/test.png"))) // 2
                .build();
        Request request = new Request.Builder()
                .header("Authorization", "Client-ID " + "...")
                .url("https://api.imgur.com/3/image")
                .post(requestBody)
                .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.d("TAG", response.body().string());
            }
        });

首先定义上传文件类型,注释 1 处是常见的 key-value 形式的参数上传;而注释 2 处则上传表单。addFormDataPart 方法的第一个参数时 key 值,第二个参数是上传文件的名字,第三个参数是需要上传的文件。

6.7、设置超时时间和缓存

创建 OkHttpClient 对象时使用 build,代码如下。

        File file = getExternalCacheDir();
        int cacheSize = 10 * 1024 * 1024;
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .cache(new Cache(file.getAbsoluteFile(), cacheSize))
                .build();

6.8、取消请求

使用 call.cancel() 可以立即停止一个正在执行的 call。当用户离开一个应用或者跳转到其他界面时,使用 call.cancel() 可以节约网络资源;另外,不管同步还是异步的 call 都可以取消,也可以通过 tag 来同时取消多个请求。当构建一个请求时,使用 Request.Builder.tag(Object tag) 来分配一个标签,之后就可以用 OkHttpClient.cancel(Object tag) 来取消所有带有这个 tag 的 call。

6.9、关于封装

  • 避免重复代码调用。
  • 将请求结果回调改为 UI 线程。

原理就是写一个双重检查模式的单例,在开始创建的时候配置好 OkHttp,并创建 Handler,在请求网络的时候用 Handler 将请求的结果回调给 UI 线程。

6.9.1、首先写一个抽象类用于请求回调

public abstract class ResultCallback {
    public abstract void onError(Request request, Exception e);

    public abstract void onResponse(String str) throws IOException;
}

6.9.2、封装 OkHttp

public class OkHttpEngine {
    private static volatile OkHttpEngine mInstance;
    private OkHttpClient mOkHttpClient;
    private Handler mHandler;

    public static OkHttpEngine getInstance(Context context) {
        if (mInstance == null) {
            synchronized (OkHttpEngine.class) {
                if (mInstance == null) {
                    mInstance = new OkHttpEngine(context);
                }
            }
        }
        return mInstance;
    }

    private OkHttpEngine(Context context) {
        File file = context.getExternalCacheDir();
        int cacheSize = 10 * 1024 * 1024;
        mOkHttpClient = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .cache(new Cache(file.getAbsoluteFile(), cacheSize))
                .build();
        mHandler = new Handler();
    }

    /**
     * 异步 GET 请求
     *
     * @param url
     * @param callback
     */
    public void getAsyncHttp(String url, ResultCallback callback) {
        final Request request = new Request.Builder().url(url).build();
        Call call = mOkHttpClient.newCall(request);
        dealResult(call, callback);
    }

    private void dealResult(Call call, final ResultCallback callback) {
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                sendFailedCallback(call.request(), e, callback);
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                sendSuccessCallback(response.body().string(), callback);
            }
        });
    }

    private void sendSuccessCallback(String string, ResultCallback callback) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (callback != null) {
                    try {
                        callback.onResponse(string);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    private void sendFailedCallback(Request request, IOException e, ResultCallback callback) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (callback != null) {
                    callback.onError(request, e);
                }
            }
        });
    }
}

6.9.3、使用

        OkHttpEngine.getInstance(this).getAsyncHttp("http://ip.taobao.com/outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc", new ResultCallback() {
            @Override
            public void onError(Request request, Exception e) {
                Toast.makeText(getApplicationContext(), "请求失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResponse(String str) throws IOException {
                Log.d("TAG", str);
                Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
            }
        });

七、解析 Retrofit

7.1、配置 build.gradle

github-Retrofit

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

这里添加了 Gson 类型数据的依赖包。

7.2、Retrofit 的注解分类

  • HTTP 请求方法注解
    GET、POST、PUT、DELETE、HEAD、PATCH、OPTIONS 和 HTTP 8 种。前 7 种分别对应 HTTP 的请求方法,HTTP 可以替换以上 7 种。

  • 标记类注解
    FormUrlEncoded、Multipart 和 Streaming 3 种, FormUrlEncoded 和 Multipart 后面会讲到,Streaming 代表响应的数据以流的形式返回,如果不适用它,则默认会把全部数据加载到内存,所以下载大文件时需要加上这个注解。

  • 参数类注解
    Header、Headers、Body、Path、Field、FieldMap、Part、PartMap、Query 和 QueryMap 等。

7.3、GET 请求访问网络

public interface IpService {
    @GET("outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc")
    Call<IpModel> getIpMsg();
}
        String url = "http://ip.taobao.com/";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IpService ipService = retrofit.create(IpService.class); // 这里使用了动态代理模式
        Call<IpModel> call = ipService.getIpMsg();
        call.enqueue(new Callback<IpModel>() {
            @Override
            public void onResponse(Call<IpModel> call, Response<IpModel> response) {
                String country = response.body().getData().getCountry();
                Log.d("TAG", country);
                Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<IpModel> call, Throwable t) {

            }
        });

这里是异步请求网络,回调的 Callback 是运行在 UI 线程的。如果想同步请求网络,使用 call.execute(),想中断网络请求,使用 call.cancel()。

动态配置 URL 地址:@Path

public interface IpService {
    @GET("{path}?ip=125.84.85.202&accessKey=alibaba-inc")
    Call<IpModel> getIpMsg(@Path("path") String path);
}

在 GET 注解中包含了 {path},它对应着 @Path 注解中的 “path” 的值。

        String url = "http://ip.taobao.com/";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IpService ipService = retrofit.create(IpService.class);
        Call<IpModel> call = ipService.getIpMsg("outGetIpInfo");
        call.enqueue(new Callback<IpModel>() {
            @Override
            public void onResponse(Call<IpModel> call, Response<IpModel> response) {
                String country = response.body().getData().getCountry();
                Log.d("TAG", country);
                Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<IpModel> call, Throwable t) {

            }
        });

动态指定查询条件:@Query

public interface IpService {
    @GET("outGetIpInfo")
    Call<IpModel> getIpMsg(@Query("ip") String ip,
                           @Query("accessKey") String accessKey);
}
        String url = "http://ip.taobao.com/";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IpService ipService = retrofit.create(IpService.class);
        Call<IpModel> call = ipService.getIpMsg("125.84.85.202", "alibaba-inc");
        call.enqueue(new Callback<IpModel>() {
            @Override
            public void onResponse(Call<IpModel> call, Response<IpModel> response) {
                String country = response.body().getData().getCountry();
                Log.d("TAG", country);
                Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<IpModel> call, Throwable t) {

            }
        });

动态指定查询条件组:@QueryMap

public interface IpService {
    @GET("outGetIpInfo")
    Call<IpModel> getIpMsg(@QueryMap Map<String, String> options);
}
        String url = "http://ip.taobao.com/";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IpService ipService = retrofit.create(IpService.class);
        Map<String, String> params = new HashMap<>();
        params.put("ip", "125.84.85.202");
        params.put("accessKey", "alibaba-inc");
        Call<IpModel> call = ipService.getIpMsg(params);
        call.enqueue(new Callback<IpModel>() {
            @Override
            public void onResponse(Call<IpModel> call, Response<IpModel> response) {
                String country = response.body().getData().getCountry();
                Log.d("TAG", country);
                Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<IpModel> call, Throwable t) {

            }
        });

7.4、POST 请求访问网络

传输数据类型为键值对:@Field

public interface IpService {
    @FormUrlEncoded
    @POST("outGetIpInfo")
    Call<IpModel> getIpMsg(@Field("ip") String ip,
                           @Field("accessKey") String accessKey);
}
        String url = "http://ip.taobao.com/";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IpService ipService = retrofit.create(IpService.class);
        Call<IpModel> call = ipService.getIpMsg("125.84.85.202", "alibaba-inc");
        call.enqueue(new Callback<IpModel>() {
            @Override
            public void onResponse(Call<IpModel> call, Response<IpModel> response) {
                String country = response.body().getData().getCountry();
                Log.d("TAG", country);
                Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<IpModel> call, Throwable t) {

            }
        });

传输数据类型为 JSON 字符串:@Body

在此示例中,由于淘宝 IP 地址库并不支持此类型的请求数据,所以不会成功返回结果,@Body 注解标注的对象会被转换为一个 JSON 字符串作为请求数据提交给服务器。

{"ip": "125.84.85.202",	"accessKey": "alibaba - inc"}
public interface IpService {
    @POST("outGetIpInfo")
    Call<IpModel> getIpMsg(@Body RequestBean bean);
}
        String url = "http://ip.taobao.com/";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        IpService ipService = retrofit.create(IpService.class);
        Call<IpModel> call = ipService.getIpMsg(new RequestBean("125.84.85.202", "alibaba-inc"));
        call.enqueue(new Callback<IpModel>() {
            @Override
            public void onResponse(Call<IpModel> call, Response<IpModel> response) {
                String country = response.body().getData().getCountry();
                Log.d("TAG", country);
                Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<IpModel> call, Throwable t) {

            }
        });

单个文件上传:@Part (未测试)

Android 6.0 以上需要动态申请权限

public interface IpService {
    @Multipart
    @POST("user/photo")
    Call<ResponseBean> updateUser(@Part MultipartBody.Part photo, @Part("description") RequestBody description);
}

Multipart 注解表示允许多个 @Part。updateUser 方法中第一个参数是准备上传的图片文件,使用了 MultipartBody.Part 类型;第二个参数是 RequestBody 类型,用来传递简单的键值对用来描述。

        String url = "";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        File file = new File(Environment.getExternalStorageDirectory(), "test.png");
        RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
        RequestBody description = RequestBody.create(null, "文件描述");
        // photos 是参数名称,是服务端 request.getParmars()要用的,需要和服端约定好
        MultipartBody.Part photo = MultipartBody.Part.createFormData("photo", file.getName(), requestBody);
        IpService ipService = retrofit.create(IpService.class);
        Call<ResponseBean> call = ipService.updateUser(photo, description);
        call.enqueue(new Callback<ResponseBean>() {
            @Override
            public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {
                String result= response.body().getResult();
                Log.d("TAG", result);
                Toast.makeText(getApplicationContext(), result, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<ResponseBean> call, Throwable t) {

            }
        });
public class ResponseBean {
    private String result;

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }
}

多个文件上传:@PartMap (未测试)

Android 6.0 以上需要动态申请权限

参考博客

public interface IpService {
    @Multipart
    @POST("user/photo")
    Call<ResponseBean> updateUser(@PartMap Map<String, RequestBody> photos, @Part("description") RequestBody description);
}
        String url = "";
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        Map<String, RequestBody> photos = new HashMap<>();
        File file1 = new File(Environment.getExternalStorageDirectory(), "test1.png");
        File file2 = new File(Environment.getExternalStorageDirectory(), "test2.png");
        File file3 = new File(Environment.getExternalStorageDirectory(), "test3.png");
        RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png"), file1);
        RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png"), file2);
        RequestBody requestBody3 = RequestBody.create(MediaType.parse("image/png"), file3);
        RequestBody description = RequestBody.create(null, "文件描述");
        IpService ipService = retrofit.create(IpService.class);
        //注意:file 就是与服务器对应的 key,后面 filename 是服务器得到的文件名
        photos.put("file\"; filename=\"" + file1.getName(), requestBody1);
        photos.put("file\"; filename=\"" + file2.getName(), requestBody2);
        photos.put("file\"; filename=\"" + file3.getName(), requestBody3);
        Call<ResponseBean> call = ipService.updateUser(photos, description);
        call.enqueue(new Callback<ResponseBean>() {
            @Override
            public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {
                String result = response.body().getResult();
                Log.d("TAG", result);
                Toast.makeText(getApplicationContext(), result, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<ResponseBean> call, Throwable t) {

            }
        });
    }

消息报头 Header (未测试)

在 HTTP 请求中,为了防止攻击或过滤掉不安全的访问,或者添加特殊加密的访问等,以便减轻服务器的压力和保证请求的安全,通常会在消息报头中携带一些特殊的消息头处理。Retrofit 也提供了 @Header 来添加消息报头。添加消息报头有两种方式:一种是静态的,另一种是动态的。

  • 静态方式
public interface IpService {
    @Multipart
    @POST("user/photo")
    @Headers("Accept-Encoding:application/json")
    Call<ResponseBean> updateUser(@PartMap Map<String, RequestBody> photos, @Part("description") RequestBody description);
}

// 添加多个,用 {} 包含起来
public interface IpService {
    @Multipart
    @POST("user/photo")
    @Headers({"Accept-Encoding:application/json", "User-Agent:MoonRetrofit"})
    Call<ResponseBean> updateUser(@PartMap Map<String, RequestBody> photos, @Part("description") RequestBody description);
}
  • 动态方式
public interface IpService {
    @Multipart
    @POST("user/photo")
    Call<ResponseBean> updateUser(@PartMap Map<String, RequestBody> photos, @Part("description") RequestBody description, @Header("Location") String location);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值