基于Http协议的Android网络编程
发布日期:2021-05-06 20:21:05 浏览次数:22 分类:技术文章

本文共 20191 字,大约阅读时间需要 67 分钟。

之前写过一篇Android网络编程,随着了解了更多Android知识,我意识到HttpClient已经不推荐使用了,更是在Android 6.0中被废弃了,原因之一就是比起HttpURLConnection,HttpClient的封装性更好,导致其可扩展性较差,当然现在网络编程更多的是使用框架了,HttpURLConnection被封装了,socket也被封装了,而像OKHttp和Volley这种框架,使用起来极为方便,开发者甚至不需要知道http、TCP/IP协议的相关知识就能高效地进行网络通信,但在有些时候,当开发者遇到bug或是修改些涉及到基于原理的功能时,可能会因为没能理解这些框架的原理而无法实现,所以本文就通过案例,着重分析基于Http协议的Android网络的原理实现,而不使用框架实现,当然更多的还是基础知识。


基于Http协议的Android编程知识点概要


Android平台网络相关API接口

  • java.net.*(标准Java接口) 
    java.net.*提供与联网有关的类,包括流、数据包套接字(socket)、Internet协议、常见Http处理等。比如:创建URL,以及URLConnection/HttpURLConnection对象、设置链接参数、链接到服务器、向服务器写数据、从服务器读取数据等通信。这些在Java网络编程中均有涉及。
  • Org.apache接口 
    对于大部分应用程序而言JDK本身提供的网络功能已远远不够,这时就需要Android提供的Apache HttpClient了。它是一个开源项目,功能更加完善,为客户端的Http编程提供高效、最新、功能丰富的工具包支持。
  • Android.net.*(Android网络接口) 
    常常使用此包下的类进行Android特有的网络编程,如:访问WiFi,访问Android联网信息,邮件等功能。

网络架构主要有两种模式B/S,C/S

  • B/S:浏览器/服务器端模式,通过应用层的HTTP协议通信,不需要特定客户端软件,而是需要统一规范的客户端,简而言之就是Android网络浏览器(如chrome,UcWeb,QQ浏览器等等)访问web服务器端的方式。
  • C/S:客户端/服务器端模式,通过任意的网络协议通信,需要特定的客户端软件。

服务器端返回客户端的内容有三种方式

  • 以HTML代码的形式返回;
  • 以XML字符串的形式返回。通过XML解析(SAX、DOM,Pull 等)
  • 以json对象的方式返回

Http协议简介


Http是Internet中广泛使用的协议,几乎所有的计算机语言和SDK都会不同程度地支持HTTP,而以网络著称的Google公司自然也会使Android SDK拥有强大的HTTP访问能力。在Android SDK中可以采用多种方式使用HTTP,例如HttpURLConnection、HttpClient ( HttpGet、HttpPost )等。 
Http是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。 
Http协议的主要特点如下:

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

HttpURLConnection类


HttpURLConnection类是jdk中的标准网络接口,该类的全限定名是java.net.HttpURLConnection,下面将介绍利用HttpURLConnection实现get请求和post请求。


HttpURLConnection实现GET请求


为了实现GET请求,需在后端搭建服务器,该服务器已搭好,访问地址为:。使用浏览器访问该地址,将返回如下JSON格式的数据:


这里写图片描述


HttpURLConnection实现get请求步骤如下:

  1. 获得需要访问的server地址、方法、及参数键值;
  2. 创建URL对象,将上述访问地址传入;
  3. 调用URL.openConnection()方法返回HttpURLConnection对象;
  4. 调用HttpURLConnection.connect()方法连接server;
  5. 调用HttpURLConnection.respondCode()方法,根据返回码判断server返回是否正确;
  6. 调用HttpURLConnection.getInputStream()方法读取server返回的内容;
  7. 关闭流、调用HttpURLConnection.disconnect()方法断开连接。

示例如下: 
首先在界面中添加一个按钮,点击该按钮,程序将尝试请求上述server的相应数据并返回给程序,界面如下: 
这里写图片描述


代码示例:

public class MainActivity extends AppCompatActivity {       private Button mButtonHttpUrlConnctionGet;    private String mUrl = "http://cloud.bmob.cn/0906a62b462a3082/";    private String mMethod = "getMemberBySex";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mButtonHttpUrlConnctionGet = (Button) findViewById(R.id.button_httpurlconnection_get);        mButtonHttpUrlConnctionGet.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                doGet("boy");            }        });    }    //doGet请求    private void doGet(String s) {    //1、获得需要访问的server地址、方法、及参数键值        final String serverAddress = mUrl + mMethod + "?" + "sex=" + s;        //访问网络,开启一个线程        new Thread(new Runnable() {            @Override            public void run() {                try {                //2、创建URL对象,将上述访问地址传入;                    URL url = new URL(serverAddress);                    //3、调用URL.openConnection()方法返回HttpURLConnection对象                    HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();                    //4、调用HttpURLConnection.connect()方法连接server                    httpUrlConnection.connect();                    //5、调用HttpURLConnection.respondCode()方法,根据返回码判断server返回是否正确                    if (httpUrlConnection.getResponseCode() == 200) {                        //6、调用HttpURLConnection.getInputStream()方法读取server返回的内容                        InputStream is = httpUrlConnection.getInputStream();                        //包装流:字节流->转换字符流(处理流)->缓冲字符流(处理流)                        BufferedReader br = new BufferedReader(new InputStreamReader(is));                        StringBuffer sb = new StringBuffer();                        String readLine = "";                        while ((readLine = br.readLine()) != null) {                            sb.append(readLine);                        }                        //7、关闭流、调用HttpURLConnection.disconnect()方法断开连接                        is.close();                        br.close();                        httpUrlConnection.disconnect();                        //Log日志中显示返回结果                        Log.i("TAG", sb.toString());                    } else {                        Log.e("TAG", "failed to connect server!");                    }                } catch (MalformedURLException e) {                    e.printStackTrace();                } catch (IOException e) {                    e.printStackTrace();                }            }        }).start();    }}    1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45   46   47   48   49   50   51   52   53   54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69   70   71   72   73   74   75   76

LogCat显示结果如下所示: 
这里写图片描述


使用HttpURLConnection的get请求需要注意的地方:

  • 需要在AndroidManifest中添加访问网络的权限
1
  • 需要开启子线程访问网络
new Thread(new Runnable    {        //access the Internet    }).start();    1   2   3   4

HttpURLConnection实现POST请求


当需要向server写数据时,就需要使用post请求。Http请求由三部分组成,分别是:请求头、消息报头(可选)、请求正文。 
Http响应也是由三个部分组成,分别是:响应头、消息报头(可选)、响应正文


其中请求头允许客户端传递关于自身的信息和希望的响应形式,它负责通知服务器有关客户端请求的信息,下表列出了http的请求头,其中用黄色标记的请求头较为常用: 
这里写图片描述 
这里写图片描述


与请求头对应,响应头域允许服务器传递不能放在状态行的附加信息,下表列出了http的响应头,其中用黄色标记的响应头较为常用: 
这里写图片描述 
这里写图片描述


HttpURLConnection实现post请求步骤如下:

  1. 设置无参数的server访问地址、方法;
  2. 创建URL对象,将上述访问地址传入;
  3. 调用URL.openConnection()方法返回HttpURLConnection对象;
  4. 调用HttpURLConnection.connect()方法连接server;
  5. 调用HttpURLConnection类中的setDoInput() [设置输入流]、setDoOutput() [设置输出流]、setRequestMethod() [设置请求类型]、setUseCaches() [设置是否使用缓存]、setRequestProperty() [设置响应头];
  6. 调用HttpURLConnection.getOutputStream()方法向server发送信息;
  7. 调用HttpURLConnection.connect()方法连接server;
  8. 调用HttpURLConnection.getInputStream()方法读取server返回的内容;
  9. 关闭流、调用HttpURLConnection.disconnect()方法断开连接。

由此看出,相比于get方式,post方式不允许将参数值出入URL对象,而是需要调用HttpURLConnection.getOutputStream()方法专门向server发送参数信息;另外,需要调用HttpURLConnection的方法设置输入输出流、响应头等操作。


添加一个按钮用于测试post请求: 
这里写图片描述


下面是post请求的代码示例:

//doPost请求    private void doPost(final String s) {        //post请求的URL没有请求参数        final String postAddress = mUrl + mMethod;        new Thread(new Runnable() {            @Override            public void run() {                try {                    URL url = new URL(postAddress);                    HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();                    //打开HttpURLConnection的输入输出                    httpUrlConnection.setDoInput(true);                    httpUrlConnection.setDoOutput(true);                    //设置请求为post请求                    httpUrlConnection.setRequestMethod("POST");                    //不使用缓存                    httpUrlConnection.setUseCaches(false);                    //设置请求头                    //设置编码集                    httpUrlConnection.setRequestProperty("Accept-Charset", "UTF-8");                    //设置content-type                    httpUrlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");                    //连接server                    httpUrlConnection.connect();                    //将需要发送的参数信息写给server                    OutputStream os = httpUrlConnection.getOutputStream();                    //包装流:数据输出流(字符处理流)                    DataOutputStream dos = new DataOutputStream(os);                    String content = "sex" + s;                    dos.writeBytes(content);                    os.flush();                    os.close();                    dos.flush();                    dos.close();                    //获得server返回数据                    if (httpUrlConnection.getResponseCode() == 200) {                        InputStream is = httpUrlConnection.getInputStream();                        BufferedReader br = new BufferedReader(new InputStreamReader(is));                        StringBuffer sb = new StringBuffer();                        String readLine = "";                        while ((readLine = br.readLine()) != null) {                            sb.append(readLine);                        }                        is.close();                        br.close();                        httpUrlConnection.disconnect();                        Log.i("POST_TAG", sb.toString());                    } else {                        Log.i("POST_TAG", "failed!");                    }                } catch (MalformedURLException e) {                    e.printStackTrace();                } catch (IOException e) {                    e.printStackTrace();                }            }        }).start();    }    1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45   46   47   48   49   50   51   52   53   54   55   56   57   58   59   60   61   62   63   64   65   66   67   68

点击在LogCat中的显示如下: 
这里写图片描述


Get请求和Post请求的区别


上面介绍了使用HttpURLConnection类实现Get请求和Post请求,下面简要介绍它们的区别:

  1. get是从服务器上获取数据,post是向服务器传送数据。
  2. get是把参数数据队列tian添加到指定的URL中,参数值和参数名各个字段一一对应,并且在URL中可以看到。post是通过HTTPpost机制,将请求的参数名和参数值放置在HTML HEADER内一起传送到指定的URL地址。用户看不到这个过程。
  3. 对于get方式,服务器端用 Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
  4. get 传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。
  5. get安全性非常低,post安全性较高。

废弃HttpClient类


在较低的Android API版本中,通过Org.apache.HttpClient,Android也能访问网络,但在4.x及后续版本已不被推荐使用,更是在Android 6.0 (API 23)中被废弃,很大一部分原因就是HttpClient类的封装性很高,导致其扩展性不如HttpURLConnection。当然,如今HttpURLConnection类也被封装的很好,OkHttp就是将其封装得很好且扩展性更高的一个框架,下面将对OkHttp做简单介绍。


使用OkHttp访问网络


HttpURLConnection和HttpClient,虽然两者都能满足HTTPS流的上传和下载,配置超时,IPv6和连接池等各种HTTP请求的需求,但更高效的使用HTTP可以让我们的应用运行更快、更节省流量。而OkHttp库就是为此而生。 
OkHttp是一个高效的HTTP库: 
1、支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求,如果SPDY不可用,则通过连接池来减少请求延时。 
2、无缝的支持GZIP来减少数据流量 
3、缓存响应数据来减少重复的网络请求:会从很多常用的连接问题中自动恢复。如果服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。 
4、使用 OkHttp 无需重写程序中的网络代码。OkHttp实现了几乎和HttpURLConnection一样的API。如果用了HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。 
5、OkHttp是一个相对成熟的解决方案,Android4.4的源码中已经将HttpURLConnection替换成了OkHttp。


使用OkHttp发送GET请求

为了在程序中使用OkHttp框架,需要在gradle中添加依赖:

compile 'com.squareup.okhttp3:okhttp:3.2.0'    1

在布局中添加两个按钮,用于发送Get请求和Post请求。 
这里写图片描述 
以下是Get请求的代码示例:

private void doGet(String s) {//拼接访问server地址        final String address = new StringBuilder().append(mUrl).append(mMethod).append("?sex=").append(s).toString();        new Thread(new Runnable() {            @Override            public void run() {            //创建okhttp3.Request对象,传入访问地址                Request request = new Request.Builder().url(address).build();                try {                //使用同步方式获得返回对象okhttp3.Response                    Response response = mOkHttpCLient.newCall(request).execute();                    //访问成功                    if (response.isSuccessful()) {                        //调用Response.body().string()方法获得server返回的结果                        Log.i("TAG", response.body().string());                    } else {                        Log.i("TAG", "error!");                    }                } catch (IOException e) {                    e.printStackTrace();                }            }        }).start();    }    1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29

使用OkHttp发送POST请求


/**     * @param s post的请求参数     */    private void doPost(String s) {    //创建FormBody对象,用于封装post的请求参数        FormBody formBody = new FormBody.Builder().add("sex", s).build();        //创建Request对象,用于封装URL请求地址及请求参数        Request request = new Request.Builder().url(new StringBuilder().append(mUrl).append(mMethod).toString()).post(formBody).build();        //采用异步方式回调server返回的结果        mOkHttpCLient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {                Log.i("TAG", "failed");            }            //该回调方法的返回参数Response就是server的返回结果            @Override            public void onResponse(Call call, Response response) throws IOException {                //输出结果                Log.i("TAG", response.body().string());            }        });    }    1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27

这里写图片描述

有关OkHttp的更多学习资料,请您访问:

  1. OkHttp的;
  2. 张鸿洋前辈的博文:。

解析JSON格式数据

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C++、C#、Java、JavaScript、Perl、Python等)。这些特性使JSON成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成(一般用于提升网络传输速率)。


JSON数据的语法规则

JSON 语法规则: 
1、数据在键值对中 
2、数据由逗号分隔 
3、花括号保存对象 
4、方括号保存数组

JSON 值可以是: 
1、数字(整数或浮点数) 
2、字符串(在双引号中) 
3、逻辑值(true 或 false) 
4、数组(在方括号中) 
5、对象(在花括号中) 
6、null


JSON格式数据的优缺点

优点: 
1、数据格式比较简单,易于读写,格式都是压缩的,占用带宽小; 
2、易于解析,客户端JavaScript可以简单的通过eval()进行JSON数据的读取; 
3、支持多种语言,包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等服务器端语言,便于服务器端的解析; 
4、在PHP世界,已经有PHP-JSON和JSON-PHP出现了,偏于PHP序列化后的程序直接调用,PHP服务器端的对象、数组等能直接生成JSON格式,便于客户端的访问提取; 
5、因为JSON格式能直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,且完成任务不变,并且易于维护。

缺点: 
1、没有XML格式这么推广的深入人心和喜用广泛,没有XML那么通用性; 
2、JSON格式目前在Web Service中推广还属于初级阶段。


解析JSON数据


下面提供了一段JSON格式的数据: 
这里写图片描述


下面代码将解析上述JSON数据:

/**     * @param json     */    private void parseData(String json) {        JSONObject objectOutside = null;        try {            objectOutside = new JSONObject(json);        } catch (JSONException e) {            e.printStackTrace();        }        try {            String country = objectOutside.getString("name");            Log.i("TAG", "Country: " + country);            JSONArray provinces = objectOutside.getJSONArray("provinces");            for (int i = 0; i < provinces.length(); ++i) {                JSONObject objectInside = provinces.getJSONObject(i);                String province = objectInside.getString("name");                Log.i("TAG", "Province " + (i + 1) + ": " + province);                JSONObject objectCities = objectInside.getJSONObject("citys");                JSONArray arrayCity = objectCities.getJSONArray("city");                StringBuffer sb = new StringBuffer();                for (int j = 0; j < arrayCity.length(); ++j) {                    String city = arrayCity.getString(j);                    sb.append("City " + (j + 1) + ": " + city + "     ");                }                Log.i("TAG", sb.toString());            }        } catch (JSONException e) {            e.printStackTrace();        }    }    1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   33   34   35   36   37   38   39   40   41   42   43   44   45   46

解析结果如下所示: 
这里写图片描述


解析XML格式数据


XML是一种可扩展标记语言: 
1、可扩展标记语言是一种很像超文本标记语言的标记语言。 
2、它的设计宗旨是传输数据,而不是显示数据。 
3、它的标签没有被预定义。您需要自行定义标签。 
4、它被设计为具有自我描述性。 
5、它是W3C的推荐标准。

可扩展标记语言(XML)和超文本标记语言(HTML)之间的差异: 
1、它不是超文本标记语言的替代。 
2、它是对超文本标记语言的补充。 
3、它和超文本标记语言为不同的目的而设计: 
4、它被设计用来传输和存储数据,其焦点是数据的内容。 
5、超文本标记语言被设计用来显示数据,其焦点是数据的外观。

超文本标记语言(XML)存在的问题: 
1、某些起始标签可以选择性出现结束标签或者隐含了结束标签。 
2、标签可以以任何顺序嵌套,即使结束标签不按照起始标签的逆序出现也是允许的。 
3、某些特性不要求一定有值。 
4、定义特性的两边有没有加上双引号都是可以的,所以都是允许的。

可扩展标记语言如何解决问题: 
1、任何的起始标签都必须有一个结束标签。 
2、可以采用另一种简化语法,可以在一个标签中同时表示起始和结束标签。 
3、标签必须按合适的顺序进行嵌套,所以结束标签必须按镜像顺序匹配起始标签。 
4、所有的特性都必须有值。 
5、所有的特性都必须在值的周围加上双引号。


XML格式数据的优缺点

XML的优点: 
1、格式统一,符合标准; 
2、容易与其他系统进行远程交互,数据共享比较方便。

XML的缺点: 
1、XML文件庞大,文件格式复杂,传输占带宽; 
2、服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护; 
3、客户端不同浏览器之间解析XML的方式不一致,需要重复编写很多代码; 
4、服务器端和客户端解析XML花费较多的资源和时间。


解析XML格式数据


下限是一段XML格式的数据:

刘亦菲
MingXing/LiuYiFei.htm
red
蔡依林
MingXing/CaiYiLin.htm
blue
张娜拉
MingXing/ZhangNaLa.htm
green
张韶涵
MingXiang/ZhangShaoHan.htm
grey
张靓颖
MingXing/ZhangLiangYin.htm
black
李宇春
MingXing/LiYuChun.htm
yellow
徐若瑄
MingXing/XuLuXuan.htm
pink
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

下面使用PULL解析方式对上述XML格式数据进行解析:

/**     * @param mXML2     */    private void parseXML2(String mXML2) {        List
list = null; Star star = null; //XMLPullParser.setInput()方法接收InputStream类型,故首先将String类型的XML数据转换成InputStream类型 InputStream is = new ByteArrayInputStream(mXML2.getBytes()); XmlPullParser xpp = Xml.newPullParser(); try { //XMLPullParser.setInput()方法接收InputStream类型 xpp.setInput(is, "UTF-8"); //获取标签类型 int type = xpp.getEventType(); //若未读到最后的结束标签,则不停遍历每个标签及其内容 while (type != XmlPullParser.END_DOCUMENT) { switch (type) { //若为XML文件起始标签,则实例化List对象 case XmlPullParser.START_DOCUMENT: list = new ArrayList<>(); break; //若读到每项的起始标签 case XmlPullParser.START_TAG: //起始标签为"item" if ("item".equals(xpp.getName())) { //实例化Star对象 star = new Star(); } // 起始标签为"name" else if ("name".equals(xpp.getName())) { //后移一项 xpp.next(); //此时指向"name"标签的内容,将该内容设置到创建的Star对象中 star.setName(xpp.getText()); } //起始标签为"url" else if ("url".equals(xpp.getName())) { //后移一项 xpp.next(); //此时指向"url"标签的内容,将该内容设置到创建的Star对象中 star.setUrl(xpp.getText()); } //起始标签为"color" else if ("color".equals(xpp.getName())) { //后移一项 xpp.next(); //此时指向"color"标签的内容,将该内容设置到创建的Star对象中 star.setColor(xpp.getText()); } break; //若指向结束标签 case XmlPullParser.END_TAG: //结束标签为"item"时,说明已读完一个完整的Star对象,该Star的所有字段已被赋值对象 if ("item".equals(xpp.getName())) { //将该Star对象加入List集合中 list.add(star); } break; default: break; } //后移一项 type = xpp.next(); } //在LogCat中打印结果 for (Star s : list) { Log.i("XML2", s.toString()); } } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

在LogCat中的输出结果如下所示: 
这里写图片描述

上一篇:深入解析OkHttp3
下一篇:java可重入锁(ReentrantLock)的实现原理

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2025年03月16日 12时17分58秒