当前位置: 首页 > news >正文

Android studio进阶开发(四)--okhttp的网络通信的使用

我们之前学过了socket服务器,这次我们继续来学习网络热门编程http/https的使用与交互

1)什么是Http协议?
答:hypertext transfer protocol(超文本传输协议),TCP/IP协议的一个应用层协议,用于 定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器 中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式。

2)Http 1.0 与 Http 1.1的区别
答:1.0协议,客户端与web服务器建立连接后,只能获得一个web资源! 而1.1协议,允许客户端与web服务器建立连接后,在一个连接上获取多个web资源!

3)Http协议的底层工作流程:
答:我们先要知道两个名词:

SYN(synchronous):TCP/IP建立连接时使用的握手信号
ACK(Acknowledgement):确认字符,确认发来的数据已经接受无误
接着就到TCP/IP三次握手的概念:

客户端发送syn包(syn = j)到服务器,进入SYN_SEND状态,然后等待服务器确认
服务器收到syn包,确认客户的syn(ack = j + 1),同时在自己也发送一个SYN包(syn=k), 即SYN + ACK包,服务器进入SYN_RECV状态
客户端收到SYN + ACK包,向服务器发送确认包ACK(ack = k +1),发送完毕后,客户端与服务端 进入ESTABLISHED状态,完成三次握手,然后两者开始传送数据。

实际开发中我们用得较多的方式是Get和Post,但是实际开发可能还会用到其他请求方式,比如PUT, 小猪的实际项目中就用到了,下面为了方便大家,就把所有的请求方式列出来吧:

Get:请求获取Request-URI所标识的资源
POST:在Request-URI所标识的资源后附加新的数据
HEAD 请求获取由Request-URI所标识的资源的响应信息报头
PUT:请求服务器存储一个资源,并用Request-URI作为其标识
DELETE:请求服务器删除Request-URI所标识的资源
TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT:保留将来使用
OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项

GET:在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔, 但数据容量通常不能超过2K,比如:http://xxx?username=…&pawd=…这种就是GET
POST: 这个则可以在请求的实体内容中向服务器发送数据,传输没有数量限制
另外要说一点,这两个玩意都是发送数据的,只是发送机制不一样,不要相信网上说的 “GET获得服务器数据,POST向服务器发送数据”!!另外GET安全性非常低,Post安全性较高, 但是执行效率却比Post方法好,一般查询的时候我们用GET,数据增删改的时候用POST!!

代码解析

1. 创建 OkHttpClient 对象​

OkHttpClient client = new OkHttpClient();

作用​​:初始化一个 OkHttp 客户端实例,用于发起 HTTP 请求。
​​说明​​:OkHttp 会自动管理连接池、线程池和缓存,确保网络请求高效。

2. 构建 HTTP 请求结构(Request)​

Request request = new Request.Builder().header("Accept-Language", "zh-CN")  // 设置请求头:语言为中文.header("Referer", "https://finance.sina.com.cn")  // 设置来源页.url(URL_STOCK)  // 指定请求的 URL(如新浪股票接口).build();

关键参数​​:
url():目标接口地址(例如 https://hq.sinajs.cn/list=s_sh000001)。
header():添加 HTTP 请求头(如语言、来源页),某些接口依赖这些头信息验证请求合法性

3. 发起异步请求​

Call call = client.newCall(request);
call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {// 处理失败(如网络不可用、URL 错误)runOnUiThread(() -> tv_result.setText("调用股指接口报错:" + e.getMessage()));}@Overridepublic void onResponse(Call call, Response response) throws IOException {String resp = response.body().string();// 处理成功响应runOnUiThread(() -> tv_result.setText("调用股指接口返回:\n" + resp));}
});

关键步骤​​:
​​client.newCall(request)​​:将请求封装为一个 Call 对象。
​​call.enqueue()​​:将请求加入队列,异步执行(后台线程发起请求,不阻塞主线程)。
​​onResponse()​​:当服务器返回响应时,通过 response.body().string() 读取响应体内容。
​​runOnUiThread()​​:将结果显示到 UI 线程(Android 禁止在非 UI 线程更新界面)。

4. 处理响应数据​

​​响应内容​​:假设新浪股票接口返回文本数据(如 var hq_str_s_sh000001=“上证指数,3094.67,…”;)。
​​解析数据​​:实际开发中需解析文本(如拆分字符串或使用正则表达式提取关键数值)

String data = resp.split("\"")[1];  // 示例:提取引号内的数据
String[] fields = data.split(",");   // 按逗号分割字段
String indexName = fields[0];        // 指数名称
String currentPrice = fields[1];     // 当前价格

全部代码如下:

Netconst 类

package com.example.tttplean;public class Netconst {// HTTP地址的前缀// HTTP地址的前缀public final static String HTTP_PREFIX = "https://192.168.1.7:8080/HttpServer/";// WebSocket服务的前缀public final static String WEBSOCKET_PREFIX = "ws://192.168.1.7:8080/HttpServer/";//public final static String BASE_IP = "192.168.1.7"; // 基础Socket服务的ippublic final static String BASE_IP = "192.168.43.9"; // 基础Socket服务的ip(这里要改为前面获取的IPv4的地址)public final static int BASE_PORT = 9010; // 基础Socket服务的端口public final static String CHAT_IP = "192.168.1.7"; // 聊天Socket服务的ippublic final static int CHAT_PORT = 9011;
}

Okhttp.activity.java:

package com.example.tttplean;import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import com.example.tttplean.Netconst;import org.json.JSONObject;import java.io.IOException;import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;public class Okhttp extends AppCompatActivity {private final static String TAG = "OkhttpCallActivity";private final static String URL_STOCK = "https://hq.sinajs.cn/list=s_sh000001";private final static String URL_LOGIN = Netconst.HTTP_PREFIX + "login";private LinearLayout ll_login; // 声明一个线性布局对象private EditText et_username; // 声明一个编辑框对象private EditText et_password; // 声明一个编辑框对象private TextView tv_result; // 声明一个文本视图对象private int mCheckedId = R.id.rb_get; // 当前选中的单选按钮资源编号@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_okhttp);ll_login = findViewById(R.id.ll_login);et_username = findViewById(R.id.et_username);et_password = findViewById(R.id.et_password);tv_result = findViewById(R.id.tv_result);RadioGroup rg_method = findViewById(R.id.rg_method);rg_method.setOnCheckedChangeListener((group, checkedId) -> {mCheckedId = checkedId;int visibility = mCheckedId == R.id.rb_get ? View.GONE : View.VISIBLE;ll_login.setVisibility(visibility);});findViewById(R.id.btn_send).setOnClickListener(v -> {if (mCheckedId == R.id.rb_get) {doGet(); // 发起GET方式的HTTP请求} else if (mCheckedId == R.id.rb_post_form) {postForm(); // 发起POST方式的HTTP请求(报文为表单格式)} else if (mCheckedId == R.id.rb_post_json) {postJson(); // 发起POST方式的HTTP请求(报文为JSON格式)}});}// 发起GET方式的HTTP请求private void doGet() {OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象// 创建一个GET方式的请求结构Request request = new Request.Builder()//.get() // 因为OkHttp默认采用get方式,所以这里可以不调get方法.header("Accept-Language", "zh-CN") // 给http请求添加头部信息.header("Referer", "https://finance.sina.com.cn") // 给http请求添加头部信息.url(URL_STOCK) // 指定http请求的调用地址.build();Call call = client.newCall(request); // 根据请求结构创建调用对象// 加入HTTP请求队列。异步调用,并设置接口应答的回调方法call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) { // 请求失败// 回到主线程操纵界面runOnUiThread(() -> tv_result.setText("调用股指接口报错:"+e.getMessage()));}@Overridepublic void onResponse(Call call, final Response response) throws IOException { // 请求成功String resp = response.body().string();// 回到主线程操纵界面runOnUiThread(() -> tv_result.setText("调用股指接口返回:\n"+resp));}});}// 发起POST方式的HTTP请求(报文为表单格式)private void postForm() {String username = et_username.getText().toString();String password = et_password.getText().toString();// 创建一个表单对象FormBody body = new FormBody.Builder().add("username", username).add("password", password).build();OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象// 创建一个POST方式的请求结构Request request = new Request.Builder().post(body).url(URL_LOGIN).build();Call call = client.newCall(request); // 根据请求结构创建调用对象// 加入HTTP请求队列。异步调用,并设置接口应答的回调方法call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) { // 请求失败// 回到主线程操纵界面runOnUiThread(() -> tv_result.setText("调用登录接口报错:"+e.getMessage()));}@Overridepublic void onResponse(Call call, final Response response) throws IOException { // 请求成功String resp = response.body().string();// 回到主线程操纵界面runOnUiThread(() -> tv_result.setText("调用登录接口返回:\n"+resp));}});}// 发起POST方式的HTTP请求(报文为JSON格式)private void postJson() {String username = et_username.getText().toString();String password = et_password.getText().toString();String jsonString = "";try {JSONObject jsonObject = new JSONObject();jsonObject.put("username", username);jsonObject.put("password", password);jsonString = jsonObject.toString();} catch (Exception e) {e.printStackTrace();}// 创建一个POST方式的请求结构RequestBody body = RequestBody.create(jsonString, MediaType.parse("text/plain;charset=utf-8"));OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象Request request = new Request.Builder().post(body).url(URL_LOGIN).build();Call call = client.newCall(request); // 根据请求结构创建调用对象// 加入HTTP请求队列。异步调用,并设置接口应答的回调方法call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) { // 请求失败// 回到主线程操纵界面runOnUiThread(() -> tv_result.setText("调用登录接口报错:"+e.getMessage()));}@Overridepublic void onResponse(Call call, final Response response) throws IOException { // 请求成功String resp = response.body().string();// 回到主线程操纵界面runOnUiThread(() -> tv_result.setText("调用登录接口返回:\n"+resp));}});}
}

xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><RadioGroupandroid:id="@+id/rg_method"android:layout_width="match_parent"android:layout_height="30dp"android:orientation="horizontal"><RadioButtonandroid:id="@+id/rb_get"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:checked="true"android:gravity="left|center"android:text="GET方式"android:textColor="@color/black"android:textSize="16sp" /><RadioButtonandroid:id="@+id/rb_post_form"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:checked="false"android:gravity="left|center"android:text="表单POST"android:textColor="@color/black"android:textSize="16sp" /><RadioButtonandroid:id="@+id/rb_post_json"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:checked="false"android:gravity="left|center"android:text="JSON POST"android:textColor="@color/black"android:textSize="16sp" /></RadioGroup><LinearLayoutandroid:id="@+id/ll_login"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="5dp"android:paddingRight="5dp"android:orientation="vertical"android:visibility="gone"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="用户名:"android:textColor="@color/black"android:textSize="17sp" /><EditTextandroid:id="@+id/et_username"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入用户名"android:maxLength="11"android:textColor="@color/black"android:textSize="17sp" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:layout_marginTop="10dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="密 码:"android:textColor="@color/black"android:textSize="17sp" /><EditTextandroid:id="@+id/et_password"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:background="@drawable/editext_selector"android:gravity="left|center"android:hint="请输入密码"android:inputType="numberPassword"android:maxLength="6"android:textColor="@color/black"android:textSize="17sp" /></LinearLayout></LinearLayout><Buttonandroid:id="@+id/btn_send"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="发起接口调用"android:textColor="@color/black"android:textSize="17sp" /><TextViewandroid:id="@+id/tv_result"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="5dp"android:textColor="@color/black"android:textSize="17sp" />
</LinearLayout>

效果图:

请添加图片描述

注意项:

  1. Referer 头部的作用​​
    ​​定义​​:Referer(注意拼写是 Referer,而非正确的英文单词 “Referrer”)表示当前请求的来源页面 URL。
    ​​用途​​:
    ​​反爬虫​​:服务器可能检查 Referer 是否来自合法页面,否则拒绝响应(如新浪股票接口)。
    ​​防盗链​​:防止其他网站直接引用资源(如图片、API)。
    ​​流量统计​​:分析用户从哪个页面跳转而来。
    ​​2. 代码中 Referer 为何是 https://finance.sina.com.cn​​
    ​​接口要求​​:新浪股票接口(hq.sinajs.cn)的服务端会校验 Referer,​​必须来自新浪财经域名​​(如 finance.sina.com.cn),否则返回 403 Forbidden。
    ​​代码示例​​:
    java
.header("Referer", "https://finance.sina.com.cn") // 模拟浏览器从新浪财经页面发起的请求

​​3. Referer 网址的具体要求​​
​​(1) 合法性要求​​
​​域名匹配​​:Referer 的域名需与目标接口的域名一致或属于其信任的白名单。
✅ 合法案例:访问 hq.sinajs.cn 接口时,Referer 设置为 https://finance.sina.com.cn。
❌ 非法案例:若设置为 https://example.com,新浪服务器会拒绝请求。
​​(2) 协议一致性​​
​​HTTP/HTTPS​​:如果目标接口是 HTTPS,Referer 也应尽量使用 HTTPS(避免混合协议问题)。
​​(3) 路径要求​​
​​允许根域名或子路径​​:服务器可能接受根域名或特定子路径。
✅ 可接受:https://finance.sina.com.cn 或 https://finance.sina.com.cn/stock/
❌ 不可接受:随意编造的不相关路径(如 https://finance.sina.com.cn/fake-path)。
​​4. 常见场景及调试方法​​
​​(1) 服务器不校验 Referer​​
如果接口未校验 Referer,可不设置该头部,或设为 null:
java
// 显式移除 Referer(OkHttp 默认不发送)
.header(“Referer”, “”)
​​(2) 服务器严格校验 Referer​​
​​通过浏览器开发者工具分析​​:
在浏览器中打开目标页面(如新浪财经)。
按 ​​F12 → Network​​ 查看实际请求的 Referer 值。
在代码中复制该值。
​​接口文档​​:查阅官方文档是否明确要求 Referer。
​​(3) 动态 Referer​​
如果不同页面需设置不同 Referer,可通过变量动态设置:

String referer = "https://finance.sina.com.cn"; // 根据场景调整
Request request = new Request.Builder().header("Referer", referer).url(URL_STOCK).build();

​​5. 注意事项​​
​​拼写错误​​:确保拼写为 Referer(非 Referrer)。
​​隐私限制​​:部分浏览器或安全设置会禁用 Referer 头(需测试兼容性)。
​​反爬策略​​:频繁调用接口时,即使 Referer 合法,也可能触发 IP 封禁。
​​总结​​
​​核心要求​​:Referer 必须指向服务器信任的域名(如新浪财经域名)。
​​验证方式​​:通过浏览器开发者工具或接口文档确定合法值。
​​代码实现​​:在 OkHttp 请求中通过 .header(“Referer”, “信任的URL”) 设置。

相关文章:

  • 【云计算】云计算中IaaS、PaaS、SaaS介绍
  • Linux Awk 深度解析:10个生产级自动化与云原生场景
  • 大语言模型的“模型量化”详解 - 03:【超轻部署、极致推理】KTransformers 环境配置 实机测试
  • 函数模板 (Function Templates)
  • Kafka命令行的使用/Spark-Streaming核心编程(二)
  • MCP协议最新进展分析报告
  • 产品经理对于电商接口的梳理||电商接口文档梳理与接入
  • 【Axure教程】表格嵌套卡片
  • Axure复选框组件的深度定制:实现自定义大小、颜色与全选功能
  • NestJS 统一异常处理 + 日志追踪链路设计
  • MySQL数据库基本操作-DQL-基本查询
  • 从低星到4.5+:ASO优化如何重塑Google Play评分与用户信任
  • 【网络应用程序设计】实验四:物联网监控系统
  • Spring Cloud Gateway配置双向SSL认证(完整指南)
  • 算法题(133):二维差分
  • 银河麒麟(内核CentOS8)安装rbenv、ruby2.6.5和rails5.2.6
  • java—12 kafka
  • [特殊字符][特殊字符] HarmonyOS相关实现原理聊聊![特殊字符][特殊字符]
  • BY免费空间去掉?i=1
  • 使用eclipse将原有tomcat插件工程调整为的Dynamic Web Module工程(保姆级教程)
  • 解码人格拼图:探索心理健康的多维视角
  • 合同约定拿850万保底利润?重庆市一中院:约定无效,发回重审
  • 李家超称香港将部署为内地企业提供供应链服务,突破美国封锁
  • 深圳大学传播学院院长巢乃鹏已任深圳大学副校长
  • 证券时报:落实“非禁即入” ,让创新活力充分涌流
  • 长三角议事厅|国际产业转移对中国产业链韧性的影响与对策