Android端通过GET/POST实现与服务器数据交互的完整Demo实战
本文还有配套的精品资源,点击获取
简介:在Android应用开发中,使用GET和POST方法与服务器进行数据交互是基础且关键的技术。本文基于传智播客张泽华Android视频54-57的代码示例,详细解析如何在Android端利用HttpURLConnection、HttpClient和OkHttp等技术实现HTTP请求。涵盖GET请求获取数据、POST提交表单、与JavaWeb后端(如Spring MVC)交互、网络权限配置及HTTPS安全通信等内容。本Demo经过验证,帮助开发者掌握移动端网络编程的核心流程与最佳实践,适用于登录、注册等常见业务场景。
1. Android网络请求基础概述
在移动应用开发中,客户端与服务器之间的数据交互是实现功能完整性的核心环节。Android作为主流的移动操作系统之一,提供了多种方式进行HTTP通信,以支持向远程服务器发送GET和POST请求并获取响应数据。本章将系统性地介绍Android端进行网络请求的基本概念、工作原理以及技术背景。
1.1 HTTP协议在Android通信中的角色
HTTP(HyperText Transfer Protocol)是应用层的核心协议,采用请求-响应模型实现客户端与服务端的数据交换。在Android中,所有网络请求均基于TCP/IP协议栈,通过HTTP/HTTPS构建语义明确的通信通道。GET用于获取资源,具有幂等性;POST用于提交数据,适用于非幂等操作。
// 示例:一个简单的HTTP GET请求URL
String url = "https://api.example.com/weather?city=Beijing&unit=metric";
该URL中, https 为安全传输协议, /weather 为资源路径,查询参数通过 ? 拼接,遵循 key=value 格式编码。
1.2 Android网络编程核心API演进
Android早期使用 HttpURLConnection 和Apache的 HttpClient 进行网络操作。自API 23起, HttpClient 被完全移除,官方推荐使用轻量高效的 HttpURLConnection 或第三方库如OkHttp。
| API组件 | 特点说明 |
|---|---|
| HttpURLConnection | 内置支持,简单GET/POST,需手动处理流与线程 |
| HttpClient (废弃) | 易用但维护成本高,已不推荐 |
| OkHttp | 支持连接池、拦截器、异步调用,现代开发首选 |
1.3 网络请求生命周期与安全策略
一次完整的HTTP请求流程包括:
1. 创建URL对象;
2. 打开连接并设置请求方法、头信息;
3. 发送请求体(POST特有);
4. 读取响应码与响应流;
5. 解析数据并更新UI;
6. 异常捕获与资源释放。
从Android 9.0(Pie)开始,默认禁止明文HTTP请求。必须在 res/xml/network_security_config.xml 中显式允许:
api.example.com
并在 AndroidManifest.xml 中引用:
此外,生产环境应优先使用HTTPS,结合SSL Pinning增强安全性。通过本章学习,开发者可建立对Android网络通信的整体认知框架,为后续深入掌握具体实现方式打下坚实基础。
2. GET方法原理与HttpURLConnection实现
在现代移动应用开发中,客户端与服务器之间的数据交互构成了绝大多数功能的核心。其中,HTTP GET 请求作为最基础、最常见的通信方式之一,在获取资源信息方面具有不可替代的地位。Android 平台为开发者提供了多种网络请求手段,而 HttpURLConnection 是原生支持且无需引入第三方依赖的底层实现方式之一。尽管随着 OkHttp 等现代化框架的普及,其使用频率有所下降,但深入理解 HttpURLConnection 的工作机制,有助于掌握 Android 网络编程的本质逻辑,并为后续高级网络库的学习打下坚实基础。
本章将系统性地剖析 HTTP GET 方法的工作机制及其在 Android 中的具体实现路径。首先从协议层面解析 GET 请求的语义特征与设计原则,明确其在参数传递、安全性及幂等性方面的行为规范;随后聚焦于 HttpURLConnection 类的实际编码实践,详细讲解如何通过标准 Java API 构建完整的请求流程,涵盖 URL 初始化、连接建立、请求头设置、响应读取以及字符编码处理等关键环节;最后结合一个真实的天气查询接口调用案例,演示如何在子线程中执行网络操作并安全更新 UI,同时探讨常见问题如网络超时、连接失败和调试技巧,帮助开发者构建稳定可靠的网络模块。
2.1 GET请求的工作机制与应用场景
GET 请求是 HTTP/1.1 协议定义的标准请求方法之一,主要用于从服务器获取指定资源。它遵循“只读”语义,意味着客户端不应通过 GET 请求对服务器状态产生副作用。该方法广泛应用于页面加载、数据检索、图片拉取、API 数据获取等场景,尤其适用于那些需要频繁刷新或缓存的内容展示类需求。
2.1.1 HTTP GET方法语义解析
根据 RFC 7231 规范,GET 方法用于“请求目标资源的当前状态表示”。它的核心特性包括:
- 幂等性(Idempotent) :多次执行相同的 GET 请求应返回相同的结果,不会改变服务器状态。
- 可缓存性(Cacheable) :默认情况下,GET 响应可以被浏览器或代理服务器缓存,提升访问效率。
- 可书签化(Bookmarkable) :由于所有参数都包含在 URL 中,用户可以直接收藏该链接以便后续访问。
- 长度限制 :URL 长度受浏览器和服务器限制(通常约为 2048 字符),不适合传输大量数据。
下面是一个典型的 GET 请求示例:
GET /api/weather?city=Beijing&unit=metric HTTP/1.1
Host: api.example.com
User-Agent: AndroidApp/1.0
Accept: application/json
此请求向远程服务发起查询,要求返回北京地区的天气信息,单位为摄氏度。所有参数均以键值对形式附加在 URL 查询字符串中。
| 属性 | 描述 |
|---|---|
| 方法名 | GET |
| 资源路径 | /api/weather |
| 查询参数 | city=Beijing , unit=metric |
| 协议版本 | HTTP/1.1 |
| 请求头示例 | Host, User-Agent, Accept |
该结构清晰体现了 GET 请求的简洁性和透明性,使得调试与监控变得直观易行。
2.1.2 请求参数编码与URL拼接规范
当使用 GET 方法传递参数时,必须将这些参数附加到 URL 的查询部分(即 ? 后面)。然而,由于 URL 只能包含特定字符集(ASCII 字母数字及部分符号),任何非 ASCII 字符(如中文)、空格或其他特殊符号都需要进行百分号编码(Percent-Encoding)。
Java 提供了 URLEncoder.encode(String s, String enc) 工具类来完成这一任务。例如:
String city = "上海";
String encodedCity = URLEncoder.encode(city, "UTF-8"); // 结果:%E4%B8%8A%E6%B5%B7
然后构建完整 URL:
StringBuilder urlBuilder = new StringBuilder("https://api.example.com/weather");
urlBuilder.append("?city=").append(encodedCity);
urlBuilder.append("&lang=").append(URLEncoder.encode("zh", "UTF-8"));
最终生成的 URL 如下:
https://api.example.com/weather?city=%E4%B8%8A%E6%B5%B7&lang=zh
⚠️ 注意:应始终使用 UTF-8 编码进行 URL 编码,避免乱码问题。
此外,建议采用模板化方式管理动态参数注入,提升代码可维护性。以下为推荐的参数拼接工具类片段:
public class UrlBuilder {
private final Uri.Builder builder;
public UrlBuilder(String baseUrl) {
this.builder = Uri.parse(baseUrl).buildUpon();
}
public UrlBuilder addParam(String key, String value) throws UnsupportedEncodingException {
builder.appendQueryParameter(key, URLEncoder.encode(value, "UTF-8"));
return this;
}
public String build() {
return builder.build().toString();
}
}
代码逻辑逐行分析:
-
Uri.Builder利用 Android SDK 自带的 URI 构造器,提供类型安全的 URL 拼接能力; -
appendQueryParameter内部自动处理编码,开发者无需手动调用URLEncoder; - 返回链式调用对象,便于连续添加多个参数;
- 最终
build()输出标准化 URL 字符串。
该模式显著降低了出错概率,尤其适合复杂查询场景。
2.1.3 GET请求的安全性与幂等性分析
虽然 GET 请求本身不携带请求体(request body),但它并非绝对安全。主要风险集中在以下几个方面:
安全性隐患
- 敏感信息暴露 :若将密码、token 或个人身份信息放入 URL 参数中,可能被记录在服务器日志、浏览器历史、反向代理缓存中,造成信息泄露。
- CSRF 攻击风险 :GET 请求容易被恶意网页诱导触发,例如
可能在用户未察觉的情况下执行登出操作。
因此, 严禁使用 GET 请求提交登录凭证、修改订单状态或删除资源等敏感操作 。
幂等性保障
幂等性是指重复执行同一请求不会导致不同的结果。对于 GET 而言,这是天然满足的——无论请求多少次,只要资源未变,响应就一致。这使其非常适合用于轮询、重试机制或离线缓存策略的设计。
sequenceDiagram
participant Client
participant Server
Client->>Server: GET /users/123
Server-->>Client: 200 OK + User Data
Client->>Server: GET /users/123 (again)
Server-->>Client: 200 OK + Same Data
上图展示了两次相同 GET 请求的行为一致性,说明其幂等性特征。
综上所述,GET 方法应在“获取数据”的上下文中合理使用,避免越界用于写操作,确保系统的安全与稳定性。
2.2 使用HttpURLConnection发起GET请求
HttpURLConnection 是 Java 标准库的一部分,也是 Android 原生支持的网络通信类。它封装了底层 TCP/IP 连接细节,允许开发者以面向对象的方式发送 HTTP 请求并接收响应。尽管其 API 相对繁琐,但了解其实现机制有助于理解更高层网络库的工作原理。
2.2.1 初始化URL对象与打开连接通道
要发起一次 GET 请求,首先需要创建一个有效的 URL 对象,然后调用 .openConnection() 方法获取 HttpURLConnection 实例。
URL url = new URL("https://api.example.com/data?param=value");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
上述代码完成了三个关键步骤:
-
new URL(...):验证并构造统一资源定位符; -
openConnection():返回抽象的URLConnection子类实例,实际类型由协议决定(HTTP → HttpURLConnection); -
setRequestMethod("GET"):显式声明请求方法,防止误用 POST。
📌 参数说明:
-url: 必须是合法格式的字符串,否则抛出MalformedURLException;
-setRequestMethod: 必须在连接前调用,否则抛出IllegalStateException。
连接尚未真正建立,直到调用 connect() 或尝试读取输入流时才会发起实际网络请求。
2.2.2 设置请求头字段(User-Agent, Accept-Type等)
为了模拟真实浏览器行为或满足服务端认证要求,通常需要自定义请求头。 HttpURLConnection 提供了 setRequestProperty(key, value) 方法用于添加头部信息。
connection.setRequestProperty("User-Agent", "MyAndroidApp/1.0");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("Authorization", "Bearer ");
connection.setConnectTimeout(5000); // 连接超时:5秒
connection.setReadTimeout(10000); // 读取超时:10秒
| 请求头 | 用途 |
|---|---|
| User-Agent | 标识客户端类型,便于服务端统计与兼容处理 |
| Accept | 声明期望的响应格式(JSON/XML) |
| Authorization | 携带身份凭证(如 JWT Token) |
| Content-Type | 虽然 GET 不常用,但在某些场景仍需设置 |
超时设置至关重要,避免因网络异常导致主线程长时间阻塞。
2.2.3 发送请求并读取服务器响应流
一旦配置完成,即可通过 getInputStream() 获取响应正文流:
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
reader.close();
inputStream.close();
// 处理 result.toString()
} else {
// 错误处理
InputStream errorStream = connection.getErrorStream();
}
代码逻辑逐行解读:
-
getResponseCode():触发实际网络请求,返回状态码(如 200、404、500); - 若为 200 OK,则从
getInputStream()读取成功响应; - 使用
BufferedReader按行读取文本内容,防止内存溢出; - 关闭流资源,避免泄漏;
- 非 2xx 状态码时,需从
getErrorStream()获取错误详情。
✅ 最佳实践:始终检查响应码再决定处理路径。
2.2.4 响应结果的字符编码处理与JSON解析
服务器返回的数据可能存在不同编码格式(如 UTF-8、GBK),应优先依据响应头中的 Content-Type 提取 charset:
String contentType = connection.getContentType(); // e.g., "application/json; charset=UTF-8"
String charset = "UTF-8"; // 默认
if (contentType != null && contentType.contains("charset=")) {
charset = contentType.split("charset=")[1];
}
InputStreamReader isr = new InputStreamReader(connection.getInputStream(), charset);
之后可结合 org.json.JSONObject 或 Gson 库进行 JSON 解析:
String jsonResponse = result.toString();
JSONObject obj = new JSONObject(jsonResponse);
String temp = obj.getString("temperature");
或使用 Gson:
Gson gson = new Gson();
WeatherData data = gson.fromJson(jsonResponse, WeatherData.class);
表格总结常见 JSON 解析方式对比:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSONObject | 内置类,无需依赖 | 易出错,样板代码多 | 简单结构 |
| Gson | 自动映射 POJO,支持泛型 | 需引入库 | 复杂嵌套对象 |
| Jackson | 性能高,功能丰富 | 配置复杂 | 高性能需求 |
选择合适的解析方案可大幅提升开发效率与健壮性。
2.3 实践案例:构建天气查询GET接口调用模块
现在我们将整合前述知识,构建一个完整的天气信息查询模块。
2.3.1 设计请求URL模板与动态参数注入
假设使用 OpenWeatherMap 免费 API:
https://api.openweathermap.org/data/2.5/weather?q={city}&appid={key}&units=metric&lang=zh_cn
使用 UrlBuilder 模板注入城市名和 API Key:
public String buildWeatherUrl(String city, String apiKey) {
return new UrlBuilder("https://api.openweathermap.org/data/2.5/weather")
.addParam("q", city)
.addParam("appid", apiKey)
.addParam("units", "metric")
.addParam("lang", "zh_cn")
.build();
}
2.3.2 在子线程中执行网络操作避免主线程阻塞
Android 禁止在主线程执行网络请求(NetworkOnMainThreadException),必须使用异步机制:
new Thread(() -> {
try {
URL url = new URL(buildWeatherUrl("北京", "your_api_key"));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
if (conn.getResponseCode() == 200) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) sb.append(line);
reader.close();
// 发送到主线程更新 UI
runOnUiThread(() -> displayWeather(sb.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
🔒 注意:网络操作必须在子线程中进行!
2.3.3 利用Handler或AsyncTask更新UI显示数据
虽然 Thread + runOnUiThread 可行,但更推荐使用 Handler 或现代 ExecutorService 配合 Runnable 。
示例使用 Handler:
private Handler mainHandler = new Handler(Looper.getMainLooper());
// 在子线程中
mainHandler.post(() -> {
textView.setText(parsedWeatherInfo);
});
或者使用 AsyncTask(已弃用但仍可见于旧项目):
private class FetchWeatherTask extends AsyncTask {
protected String doInBackground(String... params) {
// 执行 GET 请求
return fetchData(params[0]);
}
protected void onPostExecute(String result) {
displayWeather(result);
}
}
调用:
new FetchWeatherTask().execute("北京");
⚠️ 注意:
AsyncTask在 API 30+ 被标记为废弃,建议改用Executors+Handler组合。
2.4 常见问题与调试技巧
2.4.1 网络超时设置与连接失败重试机制
合理的超时设置是健壮性的关键。除了之前提到的 setConnectTimeout 和 setReadTimeout ,还应加入重试逻辑:
int maxRetries = 3;
int retryDelayMs = 2000;
for (int i = 0; i <= maxRetries; i++) {
try {
// 执行请求
break; // 成功则退出循环
} catch (IOException e) {
if (i == maxRetries) throw e;
Thread.sleep(retryDelayMs);
}
}
也可使用指数退避算法优化重试间隔。
2.4.2 日志输出与抓包工具(如Charles)辅助排查
启用详细日志有助于定位问题:
Log.d("GET_REQUEST", "URL: " + url);
Log.d("GET_RESPONSE", "Code: " + responseCode);
配合 Charles Proxy 抓包工具,可查看:
- 实际发出的请求头与参数
- SSL/TLS 握手过程
- 响应时间与大小
- 是否命中缓存
配置步骤:
- 手机连接同一 Wi-Fi;
- 设置代理为电脑 IP 和端口 8888;
- 安装 Charles 根证书至设备;
- 开始捕获流量。
graph TD
A[Android Device] -->|HTTP Request| B(Charles Proxy)
B --> C[Remote Server]
C --> B
B --> A
通过可视化流量流向,极大提升调试效率。
综上所述,掌握 HttpURLConnection 实现 GET 请求的全过程,不仅是技术能力的体现,更是构建高质量网络模块的基础。
3. POST方法原理与HttpClient实现
在现代移动应用开发中,客户端与服务器之间的数据交互早已超越了简单的信息获取。随着用户行为的复杂化和服务端业务逻辑的深化,越来越多的应用场景需要向服务端提交结构化的数据内容,如用户登录、注册、文件上传、订单创建等。这类操作通常依赖于 HTTP 的 POST 请求方法完成。与 GET 不同,POST 方法允许将大量数据封装在请求体(Request Body)中进行传输,具有更高的安全性与灵活性。本章将深入剖析 POST 请求的核心机制,并以 Android 平台早期广泛使用的 Apache HttpClient 框架为例,系统性地讲解如何通过该技术栈实现高效的 POST 数据提交。
3.1 POST请求的数据封装机制
HTTP 协议定义了多种请求方法,其中 POST 是最常用于“向服务器发送数据”的标准方式。它与 GET 最本质的区别在于:GET 将参数附加在 URL 上,而 POST 则将数据放置在请求体内部,不暴露于地址栏中,因此更适合处理敏感或大量的数据传输任务。理解 POST 请求的数据封装形式是掌握其使用的关键前提。
3.1.1 表单提交与application/x-www-form-urlencoded格式
当用户在网页或 App 中填写表单并点击提交时,浏览器或客户端通常会采用 application/x-www-form-urlencoded 编码方式对数据进行序列化。这种格式遵循键值对的形式,每个字段名和值之间用等号连接,多个字段之间用 & 分隔,例如:
username=admin&password=123456&remember=true
同时,所有非 ASCII 字符和特殊符号必须经过 URL 编码(Percent-Encoding),如空格变为 %20 ,中文字符转换为 UTF-8 后再编码。这是 HTML 表单默认的 enctype 类型,也是许多传统 Web API 接受数据的标准格式。
该编码方式适用于文本类数据提交,尤其常见于用户认证接口。它的优势在于兼容性强、解析简单,几乎所有后端语言(Java、PHP、Python 等)都能直接从请求流中提取并自动解码成键值映射结构。但在处理二进制数据(如图片、音频)时则无法胜任,需切换至更复杂的编码类型。
以下是一个典型的 HTTP 请求头示例,表明当前请求体采用了 URL 编码格式:
Content-Type: application/x-www-form-urlencoded
Content-Length: 37
这说明请求体长度为 37 字节,且内容符合标准表单编码规则。服务端据此选择相应的解析策略。
3.1.2 使用UrlEncodedFormEntity构造请求体
在 Android 开发中,若使用 Apache HttpClient 库发送 POST 请求,可以通过 UrlEncodedFormEntity 工具类来生成符合 application/x-www-form-urlencoded 格式的请求体。该类接受一个 List 集合,自动完成字段拼接与 URL 编码工作。
List params = new ArrayList<>();
params.add(new BasicNameValuePair("username", "zhangsan"));
params.add(new BasicNameValuePair("password", "P@ssw0rd!"));
params.add(new BasicNameValuePair("device_id", "ABC123XYZ"));
HttpEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
上述代码创建了一个包含三个字段的参数列表,并指定字符集为 UTF-8,确保中文或其他多字节字符能正确编码。最终生成的实体对象可直接设置到 HttpPost 请求中。
| 参数名称 | 值 | 是否编码 |
|---|---|---|
| username | zhangsan | 否 |
| password | P@ssw0rd! | 是(特殊字符保留) |
| device_id | ABC123XYZ | 否 |
注意 :尽管
@和!属于特殊字符,在某些上下文中可能被编码,但根据 RFC 3986 规范,这些字符在查询组件中属于“子分隔符”,通常无需编码,除非目标服务器有特殊要求。
该方式极大简化了开发者手动拼接字符串的工作,避免因编码不当导致服务端解析失败的问题。
flowchart TD
A[开始构建POST请求] --> B[准备NameValuePair列表]
B --> C[添加键值对参数]
C --> D[使用UrlEncodedFormEntity封装]
D --> E[设置字符编码UTF-8]
E --> F[生成HttpEntity对象]
F --> G[绑定至HttpPost请求]
G --> H[执行请求并获取响应]
该流程图清晰展示了从参数收集到请求体生成的完整路径,体现了模块化设计的思想。
3.1.3 文件上传与multipart/form-data编码详解
当涉及到文件上传(如头像、附件、语音记录)时, application/x-www-form-urlencoded 已无法满足需求。此时应使用更为复杂的 multipart/form-data 编码方式。该格式通过边界符(boundary)将不同部分的数据分割开来,每一部分可以是普通字段或文件流,支持混合传输。
一个典型的 multipart/form-data 请求体结构如下:
--AaB03x
Content-Disposition: form-data; name="username"
zhangsan
--AaB03x
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg
(binary JPEG data)
--AaB03x--
每一段以 --boundary 开始,最后以 --boundary-- 结束。每个部分包含自己的 Content-Disposition 头部描述字段名及文件名(如有),还可附带 Content-Type 指定媒体类型。
在 Android 中使用 HttpClient 实现此类请求,需借助 MultipartEntityBuilder 构建器模式:
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.addTextBody("username", "lisi", ContentType.TEXT_PLAIN);
builder.addBinaryBody("file", new File("/sdcard/photo.jpg"),
ContentType.IMAGE_JPEG, "photo.jpg");
HttpEntity entity = builder.build();
-
setMode(HttpMultipartMode.BROWSER_COMPATIBLE)设置兼容浏览器的行为,防止 Tomcat 等容器出现解析异常。 -
addTextBody()添加普通文本字段。 -
addBinaryBody()添加二进制文件,自动设置 MIME 类型和文件名。
此方法灵活且安全,能够支持多文件并发上传,是实现富媒体交互的基础能力之一。
此外,服务端需配置对应的文件接收机制(如 Spring MVC 中的 MultipartFile ),否则即使客户端成功发送,也无法正确落地存储。
3.2 基于HttpClient的传统POST实现方式
尽管 Google 自 Android 6.0(API 23)起正式移除了对 Apache HttpClient 的内置支持,但在大量遗留项目、旧版本适配以及特定定制 ROM 环境中,仍有不少应用沿用这一经典网络框架。理解其工作原理不仅有助于维护老代码,也能帮助开发者更好地对比新旧技术差异,做出合理的技术选型决策。
3.2.1 添加Apache HttpClient库依赖(旧版本兼容)
由于 Android SDK 不再包含 Apache HttpClient 的实现类(如 DefaultHttpClient 、 ClientConnectionManager ),若要在高版本 Android 中继续使用,必须显式引入官方提供的独立包—— org.apache.http.legacy 。
在 app/build.gradle 文件中添加以下配置:
android {
compileSdkVersion 33
useLibrary 'org.apache.http.legacy'
}
或者,对于某些 Gradle 插件版本,也可通过 dependencies 显式引用 JAR 包:
dependencies {
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
}
⚠️ 注意事项:
- 使用
useLibrary方式仅适用于 Android 内置的 legacy 版本,功能受限。- 若引入外部 Maven 依赖,则需自行管理冲突与体积膨胀问题。
- 建议仅在迁移过渡期使用,长期项目应优先考虑 OkHttp 或 HttpURLConnection。
一旦配置完成,即可在代码中导入相关类:
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
虽然 API 看似简洁,但其背后隐藏着较多底层细节,包括连接池管理、重试机制、SSL 支持等,均需手动配置才能达到生产级稳定性。
3.2.2 创建HttpPost实例并设置目标URI
发起 POST 请求的第一步是构建一个 HttpPost 对象,传入目标服务器的完整 URL 地址:
String url = "https://api.example.com/login";
HttpPost httpPost = new HttpPost(url);
该对象代表一次具体的 HTTP POST 动作,后续的所有配置(如请求头、超时、实体)都将作用于此实例。
URL 必须符合标准格式,建议提前验证有效性:
try {
new URL(url).toURI(); // 验证是否合法
} catch (Exception e) {
Log.e("URL_ERROR", "Invalid URL provided", e);
return;
}
此外,若涉及 HTTPS 请求,还需确认服务器证书有效性,必要时配置自定义 SSLSocketFactory 来处理双向认证或忽略不安全证书(仅限测试环境)。
值得注意的是, HttpPost 继承自 HttpRequestBase ,实现了 HttpUriRequest 接口,具备完整的 URI 控制能力,支持动态路径拼接与参数注入。
3.2.3 添加请求实体与自定义请求头信息
请求头(Headers)是影响服务端行为的重要因素。常见的自定义头部包括:
-
User-Agent:标识客户端设备与应用版本 -
Authorization:携带 Token 进行身份认证 -
Accept-Language:通知服务端返回本地化内容 -
X-Device-ID:用于追踪设备唯一性
设置方式如下:
httpPost.setHeader("User-Agent", "MyApp/1.0 (Android 12)");
httpPost.setHeader("Authorization", "Bearer eyJhbGciOiJIUzI1NiIs...");
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
❗ 特别提醒:如果使用
UrlEncodedFormEntity或MultipartEntityBuilder,请勿手动设置Content-Type,否则可能导致边界符缺失或编码混乱。
接下来,将之前构造的 HttpEntity 设置为请求体:
HttpEntity requestEntity = new UrlEncodedFormEntity(params, "UTF-8");
httpPost.setEntity(requestEntity);
此时,整个请求已具备完整的语义结构:明确的目标地址、合理的头部元数据、有效的负载内容。
3.2.4 执行请求并处理HttpResponse对象
真正执行网络请求的操作由 HttpClient 实例完成。传统的同步调用方式如下:
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
Log.d("HTTP_RESPONSE", "Status: " + statusCode);
Log.d("HTTP_RESPONSE", "Body: " + responseBody);
关键点解析:
| 方法/属性 | 说明 |
|---|---|
execute(HttpUriRequest) | 发起阻塞式请求,返回 HttpResponse |
getStatusLine().getStatusCode() | 获取 HTTP 状态码(200、404、500 等) |
getEntity() | 获取响应体实体 |
EntityUtils.toString(...) | 将实体流读取为字符串,支持指定编码 |
⚠️ 重要警告 :以上代码必须运行在子线程中!Android 主线程禁止执行网络操作,否则会抛出 NetworkOnMainThreadException 异常。
完整的异常处理逻辑应当覆盖:
- 网络不可达(
IOException) - 解析失败(
ParseException) - 服务端错误(状态码 ≥ 400)
- 超时中断(连接/读取超时)
try {
HttpResponse response = httpClient.execute(httpPost);
int code = response.getStatusLine().getStatusCode();
if (code == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
// 成功处理
} else {
// 处理错误状态
}
} catch (ClientProtocolException e) {
Log.e("HTTP_ERROR", "协议异常", e);
} catch (IOException e) {
Log.e("HTTP_ERROR", "IO异常(网络中断)", e);
} finally {
httpClient.getConnectionManager().shutdown(); // 释放资源
}
尽管 Apache HttpClient 提供了较完善的 API 封装,但其资源管理繁琐、性能较低、缺乏现代特性支持,逐渐被 OkHttp 取代。
3.3 实战演练:用户登录表单数据提交
理论知识需结合实际场景方能内化为技能。本节将以“用户登录”功能为例,完整演示如何在 Android 应用中基于 HttpClient 实现安全可靠的 POST 表单提交。
3.3.1 构建EditText输入监听与参数收集逻辑
界面布局中包含两个 EditText 分别用于输入用户名与密码:
在 Activity 中注册按钮点击事件,并收集输入内容:
Button btnLogin = findViewById(R.id.btn_login);
btnLogin.setOnClickListener(v -> {
EditText etUser = findViewById(R.id.et_username);
EditText etPass = findViewById(R.id.et_password);
String username = etUser.getText().toString().trim();
String password = etPass.getText().toString().trim();
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
Toast.makeText(this, "请填写完整信息", Toast.LENGTH_SHORT).show();
return;
}
// 启动异步任务发送请求
new LoginTask().execute(username, password);
});
此处使用 AsyncTask 作为轻量级异步工具(虽已废弃但仍可用于教学演示)。
3.3.2 异步任务中执行POST请求并反馈登录状态
定义内部类 LoginTask 继承 AsyncTask :
private class LoginTask extends AsyncTask {
@Override
protected String doInBackground(String... params) {
String username = params[0];
String password = params[1];
try {
List postParams = new ArrayList<>();
postParams.add(new BasicNameValuePair("username", username));
postParams.add(new BasicNameValuePair("password", password));
HttpEntity entity = new UrlEncodedFormEntity(postParams, "UTF-8");
HttpPost httpPost = new HttpPost("https://api.example.com/auth/login");
httpPost.setEntity(entity);
httpPost.setHeader("User-Agent", "MyLoginApp/1.0");
HttpClient client = new DefaultHttpClient();
HttpResponse response = client.execute(httpPost);
if (response.getStatusLine().getStatusCode() == 200) {
return EntityUtils.toString(response.getEntity(), "UTF-8");
} else {
return "error:" + response.getStatusLine().getStatusCode();
}
} catch (Exception e) {
return "exception:" + e.getMessage();
}
}
@Override
protected void onPostExecute(String result) {
if (result.startsWith("error:")) {
Toast.makeText(MainActivity.this, "登录失败:" + result, Toast.LENGTH_LONG).show();
} else if (result.startsWith("exception:")) {
Toast.makeText(MainActivity.this, "网络异常,请检查连接", Toast.LENGTH_LONG).show();
} else {
// 解析 JSON 返回的 token
try {
JSONObject json = new JSONObject(result);
String token = json.getString("token");
saveTokenToLocal(token); // 本地持久化
startActivity(new Intent(MainActivity.this, HomeActivity.class));
} catch (JSONException e) {
Toast.makeText(MainActivity.this, "数据解析失败", Toast.LENGTH_SHORT).show();
}
}
}
}
逐行逻辑分析:
-
doInBackground: 在后台线程中执行网络请求,防止 ANR。 -
postParams收集用户输入,经UrlEncodedFormEntity编码。 -
httpPost.setHeader()添加 UA 标识,便于服务端日志追踪。 -
client.execute()发起同步阻塞调用。 - 状态码判断决定后续流程走向。
-
onPostExecute()回到主线程更新 UI,展示提示或跳转页面。
3.3.3 处理服务端返回的Session ID或错误提示
理想情况下,服务端应在认证成功后返回 JSON 格式的响应:
{
"status": "success",
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 3600
}
客户端应提取 token 并存入 SharedPreferences 或 EncryptedSharedPreferences :
private void saveTokenToLocal(String token) {
SharedPreferences sp = getSharedPreferences("auth", MODE_PRIVATE);
sp.edit().putString("access_token", token).apply();
}
后续所有请求均需在 Authorization 头部携带此 token:
httpPost.setHeader("Authorization", "Bearer " + token);
若登录失败,服务端可能返回:
{ "error": "invalid_credentials", "message": "用户名或密码错误" }
此时应在 UI 层给予明确反馈,提升用户体验。
3.4 迁移建议与性能对比
随着 Android 生态的发展,Apache HttpClient 已不再是推荐方案。了解其局限性并制定合理的迁移路径,是保障项目可持续发展的关键。
3.4.1 Android官方弃用HttpClient的原因分析
Google 在 Android 5.1 之后标记 HttpClient 为过时,在 6.0 中彻底移除内置支持,主要原因包括:
| 原因 | 说明 |
|---|---|
| 维护成本高 | Apache HttpClient 是独立项目,Android 团队难以同步更新 |
| 性能低下 | 缺乏连接复用、SPDY/HTTP/2 支持 |
| 安全漏洞频发 | SSL/TLS 实现存在缺陷,易受中间人攻击 |
| 体积庞大 | 引入过多无关类,增加 APK 大小 |
| 与现代标准脱节 | 不支持 WebSocket、拦截器、缓存策略等 |
相比之下,OkHttp 提供了更简洁的 API、更好的性能优化和活跃的社区支持。
3.4.2 向OkHttp过渡的技术路径规划
建议采取渐进式迁移策略:
- 评估现有接口数量与复杂度
- 封装统一的 OkHttp 工具类替代 HttpClient 调用
- 逐步替换各模块中的网络请求代码
- 移除
useLibrary 'org.apache.http.legacy'依赖 - 全面启用拦截器、连接池、GZIP 压缩等高级功能
示例:使用 OkHttp 发送相同登录请求
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("username", "zhangsan")
.add("password", "123456")
.build();
Request request = new Request.Builder()
.url("https://api.example.com/login")
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String result = response.body().string();
// 处理结果
}
}
@Override
public void onFailure(Call call, IOException e) {
// 处理失败
}
});
代码更加简洁,天然支持异步回调,无须手动管理线程。
综上所述,虽然 Apache HttpClient 曾是 Android 网络编程的基石之一,但时代演进要求开发者拥抱更高效、更安全的技术方案。掌握其原理是为了理解历史,而推动向 OkHttp 的迁移则是面向未来的选择。
4. 使用OkHttp发送同步与异步POST请求
在现代Android应用开发中,高效、稳定且可扩展的网络通信机制是保障用户体验的核心要素之一。随着HTTP/2、连接复用、GZIP压缩等技术的普及,传统 HttpURLConnection 和已废弃的 HttpClient 已难以满足高性能网络交互的需求。OkHttp作为Square公司推出的开源HTTP客户端库,凭借其简洁的API设计、强大的功能集成以及卓越的性能表现,已成为Android平台上事实上的标准网络框架。
OkHttp不仅支持同步与异步两种调用模式,还内置了连接池、缓存管理、拦截器链、自动重连等高级特性,极大简化了开发者在网络请求处理中的复杂度。特别是在POST请求场景下——如用户登录、数据提交、文件上传等涉及敏感或大量数据传输的操作——OkHttp提供了灵活的数据封装方式(JSON、表单、Multipart)、线程调度机制以及回调处理模型,使得开发者能够以更少的代码实现更健壮的网络逻辑。
本章将深入剖析OkHttp在POST请求中的实际应用,涵盖从基础组件构建到同步/异步执行流程,再到高级拦截器扩展的完整技术路径。通过理论结合实战的方式,系统性地展示如何利用OkHttp完成安全、高效的POST通信,并针对常见问题提出优化策略。
4.1 OkHttp框架核心组件与优势特性
OkHttp的设计遵循典型的面向对象架构原则,采用清晰的职责分离模式,其主要由四个核心类构成: OkHttpClient 、 Request 、 Call 和 Response 。这些组件共同构成了一个高内聚、低耦合的请求-响应处理链条,为开发者提供了一套直观而强大的编程接口。
4.1.1 OkHttpClient、Request、Call与Response模型解析
OkHttpClient 是整个OkHttp框架的入口点,负责配置全局行为,例如超时时间、缓存策略、连接池大小、拦截器链等。它是一个重量级对象,推荐在整个应用生命周期中仅创建一次并复用,避免资源浪费。
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
上述代码展示了如何通过 Builder 模式构建一个具备合理超时控制和自动重试能力的 OkHttpClient 实例。参数说明如下:
| 参数 | 说明 |
|---|---|
connectTimeout | 建立TCP连接的最大允许时间 |
writeTimeout | 向服务器写入请求体的最长等待时间 |
readTimeout | 读取服务器响应内容的最长等待时间 |
retryOnConnectionFailure | 是否在网络故障时尝试重新发起请求 |
接下来是 Request 对象,代表一次具体的HTTP请求。它封装了目标URL、请求方法(GET/POST等)、请求头及请求体信息。
RequestBody body = RequestBody.create(
MediaType.get("application/json; charset=utf-8"),
"{"username":"zhangsan", "password":"123456"}"
);
Request request = new Request.Builder()
.url("https://api.example.com/login")
.post(body)
.addHeader("User-Agent", "MyApp/1.0")
.build();
这里我们构造了一个以JSON格式提交用户凭证的POST请求。 RequestBody.create() 用于创建携带数据的请求体, MediaType 指定了内容类型和字符编码。
随后, Call 接口表示一个准备执行的实际HTTP调用。通过将 Request 传递给 OkHttpClient.newCall() 方法即可获得一个 Call 实例:
Call call = client.newCall(request);
最后,调用 execute() (同步)或 enqueue() (异步)方法触发请求,返回 Response 对象,包含状态码、响应头和响应体等内容:
try (Response response = call.execute()) {
if (response.isSuccessful()) {
String result = response.body().string();
// 处理结果
}
}
该过程体现了OkHttp高度模块化的结构:每个组件各司其职,组合灵活,便于测试与维护。
4.1.2 支持HTTP/2与连接池复用提升效率
OkHttp原生支持HTTP/2协议,能够在单一TCP连接上并行传输多个请求和响应,显著减少延迟。此外,其内置的 连接池 机制会缓存空闲的连接,当下次请求同一主机时直接复用已有连接,避免重复进行DNS解析、SSL握手和TCP三次握手,从而大幅提升请求吞吐量。
下面是一个Mermaid流程图,描述了OkHttp连接复用的工作流程:
sequenceDiagram
participant App as 应用层
participant Client as OkHttpClient
participant Pool as ConnectionPool
participant Server as 远程服务器
App->>Client: 发起新请求
Client->>Pool: 查询可用连接
alt 存在空闲连接
Pool-->>Client: 返回复用连接
Client->>Server: 直接发送请求
else 无可用连接
Client->>Server: 建立新TCP+TLS连接
Client->>Pool: 缓存连接供后续使用
end
Server-->>Client: 返回响应
Client-->>App: 返回Response
这种连接复用机制对于频繁访问相同后端服务的应用(如社交类App、电商后台接口轮询)具有重要意义。实验数据显示,在高并发场景下,启用连接池可使平均请求耗时降低30%以上。
4.1.3 内置GZIP压缩与缓存机制优化体验
OkHttp默认开启GZIP压缩支持。当服务器响应头中含有 Content-Encoding: gzip 时,OkHttp会自动解压响应体,开发者无需手动处理压缩逻辑。这在传输大体积JSON或HTML资源时尤为关键,能有效节省带宽并加快页面加载速度。
同时,OkHttp支持基于HTTP语义的响应缓存。通过设置本地磁盘缓存目录,可以对某些GET请求的结果进行离线缓存,提高弱网环境下的可用性:
File cacheDir = new File(context.getCacheDir(), "http-cache");
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024); // 10MB
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
尽管本章聚焦于POST请求,但理解OkHttp的整体能力有助于全面评估其在项目中的适用性。尤其是当应用混合使用多种请求类型时,统一使用OkHttp可降低维护成本并增强一致性。
4.2 同步POST请求的实现流程
虽然Android主线程不允许执行网络操作,但在特定场景下(如初始化配置加载、后台服务计算),仍需使用同步方式阻塞等待响应结果。OkHttp的 execute() 方法正是为此设计。
4.2.1 构建RequestBody传递JSON或表单数据
在POST请求中,数据通常以两种形式提交: application/json 或 application/x-www-form-urlencoded 。前者适用于结构化数据(如注册信息),后者常用于传统Web表单。
JSON格式示例:
JSONObject json = new JSONObject();
json.put("email", "user@example.com");
json.put("age", 25);
RequestBody requestBody = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"),
json.toString()
);
表单格式示例:
FormBody formBody = new FormBody.Builder()
.add("name", "李四")
.add("gender", "男")
.build();
FormBody 是OkHttp提供的专用类,用于生成符合 x-www-form-urlencoded 编码规则的请求体,相比手动拼接字符串更加安全可靠。
4.2.2 在独立线程中执行execute()方法阻塞调用
由于Android禁止在主线程执行网络请求,必须在子线程中调用 execute() :
new Thread(() -> {
try {
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
final String responseData = response.body().string();
// 切回主线程更新UI
runOnUiThread(() -> {
textView.setText(responseData);
});
} else {
handleError("请求失败:" + response.code());
}
} catch (IOException e) {
handleError("网络异常:" + e.getMessage());
}
}).start();
此段代码的关键在于:
- 使用 Thread 启动异步任务;
- 调用 execute() 获取 Response ;
- 使用 runOnUiThread() 安全刷新UI。
需要注意的是, response.body().string() 只能调用一次,因为OkHttp的响应体是流式读取的,读完即关闭。若需多次读取,应先缓存字符串。
4.2.3 解析响应内容并安全更新主线程UI
假设服务器返回如下JSON:
{
"code": 200,
"msg": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
可在同步回调中解析:
if (response.isSuccessful()) {
String rawJson = response.body().string();
JSONObject result = new JSONObject(rawJson);
int code = result.getInt("code");
if (code == 200) {
String token = result.getJSONObject("data").getString("token");
saveTokenToLocal(token); // 持久化存储
runOnUiThread(() -> showToast("登录成功"));
}
}
该逻辑展示了完整的“请求→响应→解析→持久化→UI反馈”闭环。虽然同步方式简单直观,但容易造成线程管理混乱,建议仅用于非UI相关任务。
4.3 异步POST请求与回调处理
异步请求是Android中最推荐的方式,OkHttp通过 enqueue() 方法配合 Callback 接口实现非阻塞调用,天然适配移动端事件驱动模型。
4.3.1 enqueue()方法注册Callback接口
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 网络层错误:连接超时、DNS失败等
runOnUiThread(() -> showErrorToast("网络不可用"));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseBody = response.body().string();
parseAndSaveData(responseBody);
runOnUiThread(() -> updateUI());
} else {
handleServerError(response.code());
}
}
});
onFailure() 捕获底层IO异常, onResponse() 处理正常响应。两者均运行在工作线程,因此更新UI必须切回主线程。
4.3.2 onResponse()与onFailure()中的业务逻辑分支
为了增强可维护性,可将响应解析抽离为独立方法:
private void parseAndSaveData(String jsonStr) {
try {
JSONObject json = new JSONObject(jsonStr);
String userId = json.getString("userId");
String sessionToken = json.getString("token");
SharedPreferences sp = getSharedPreferences("auth", MODE_PRIVATE);
sp.edit().putString("token", sessionToken).apply();
} catch (JSONException e) {
Log.e("ParseError", "JSON解析失败", e);
}
}
这样既保证了网络回调的简洁性,又提升了错误隔离能力。
4.3.3 避免内存泄漏:弱引用与生命周期绑定策略
长期持有Activity引用可能导致内存泄漏。解决方案之一是使用 WeakReference 包装上下文:
public class SafeCallback implements Callback {
private final WeakReference activityRef;
public SafeCallback(MainActivity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void onFailure(Call call, IOException e) {
MainActivity activity = activityRef.get();
if (activity != null && !activity.isFinishing()) {
activity.runOnUiThread(() -> activity.showError(e.getMessage()));
}
}
@Override
public void onResponse(Call call, Response response) {
MainActivity activity = activityRef.get();
if (activity != null && !activity.isFinishing()) {
// 安全更新UI
}
}
}
此外,结合 LifecycleObserver 或协程 CoroutineScope 可进一步实现请求与组件生命周期联动,防止无效回调执行。
4.4 高级功能扩展
OkHttp的强大之处不仅在于基础请求能力,更体现在其可扩展的拦截器体系。
4.4.1 拦截器实现日志记录与Token自动注入
自定义拦截器可用于添加通用头部,如认证Token:
public class AuthInterceptor implements Interceptor {
private String token;
public AuthInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request authorized = original.newBuilder()
.header("Authorization", "Bearer " + token)
.build();
return chain.proceed(authorized);
}
}
注册方式:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new AuthInterceptor("your-jwt-token"))
.addInterceptor(new LoggingInterceptor()) // 日志拦截器
.build();
4.4.2 自定义拦截器进行请求重试与监控埋点
实现智能重试逻辑:
public class RetryInterceptor implements Interceptor {
private final int maxRetries = 3;
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException lastException = null;
for (int i = 0; i <= maxRetries; i++) {
try {
response = chain.proceed(request);
if (response.isSuccessful() || i == maxRetries) break;
response.close(); // 释放资源
} catch (IOException e) {
lastException = e;
if (i == maxRetries) throw e;
}
sleepQuietly(i * 1000); // 指数退避
}
return response;
}
private void sleepQuietly(long millis) {
try { Thread.sleep(millis); } catch (InterruptedException ignored) {}
}
}
此拦截器实现了最多三次自动重试,适用于瞬时网络抖动场景。
下表对比不同请求方式的特点:
| 特性 | 合成请求 | 异步请求 | 优劣分析 |
|---|---|---|---|
| 执行方式 | 阻塞当前线程 | 非阻塞回调 | 异步更适合UI交互 |
| 线程管理 | 需手动创建线程 | 内部线程池调度 | 异步更安全 |
| 错误处理 | try-catch集中处理 | 分离onFailure/onResponse | 异步更清晰 |
| 内存风险 | 易导致ANR | 可能引发内存泄漏 | 均需注意生命周期 |
综上所述,OkHttp以其优雅的设计和丰富的功能,成为现代Android网络编程不可或缺的工具。掌握其同步与异步POST请求机制,不仅能提升开发效率,更能构建出高性能、高可用的移动应用。
5. Android与JavaWeb后端交互完整Demo实战流程
5.1 项目结构设计与前后端协作规范
在构建一个完整的 Android 与 JavaWeb 后端交互系统时,合理的项目结构和清晰的接口规范是保障开发效率和后期维护性的关键。本节将从整体架构出发,定义前后端通信的标准方式。
5.1.1 定义统一API接口文档(RESTful风格)
我们采用 RESTful 风格设计 API 接口,确保语义清晰、易于理解。以下为用户注册与登录模块的核心接口:
| 接口路径 | HTTP方法 | 功能描述 | 请求参数(示例) | 响应格式 |
|---|---|---|---|---|
/api/user/register | POST | 用户注册 | {"username": "zhangsan", "password": "123456"} | { "code": 200, "msg": "success", "data": {} } |
/api/user/login | POST | 用户登录 | {"username": "zhangsan", "password": "123456"} | { "code": 200, "msg": "success", "data": { "token": "eyJhbGciOiJIUzI1Ni..." } } |
/api/user/profile | GET | 获取用户信息 | Header: Authorization: Bearer | { "code": 200, "data": { "id": 1, "username": "zhangsan" } } |
所有接口返回标准 JSON 格式:
{
"code": 200,
"msg": "操作成功",
"data": {}
}
其中 code 为业务状态码(如 401 表示未授权), msg 提供可读提示, data 携带实际数据。
5.1.2 Spring Boot后端接收参数与返回JSON格式标准化
使用 Spring Boot 构建后端服务,Controller 层代码如下:
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/register")
public ResponseEntity
通过 @RequestBody 自动解析 JSON 请求体,并统一封装响应结构,便于前端解析处理。
5.2 Android端集成OkHttp与权限配置
5.2.1 添加Gradle依赖与网络权限声明
在 app/build.gradle 中添加 OkHttp 依赖:
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'org.jetbrains:annotations:24.0.1' // 可选注解支持
}
在 AndroidManifest.xml 中声明网络权限:
5.2.2 配置网络安全配置文件network_security_config.xml
创建 res/xml/network_security_config.xml 文件以允许 HTTPS 和调试环境下的 HTTP 请求:
localhost
10.0.2.2
your-backend-domain.com
并在 AndroidManifest.xml 应用配置中引用:
5.3 实现用户注册与登录全流程
5.3.1 表单验证、加密传输与HTTPS通信保障
在 Android 端进行基础表单校验:
private boolean validateForm(String username, String password) {
if (TextUtils.isEmpty(username)) {
Toast.makeText(this, "请输入用户名", Toast.LENGTH_SHORT).show();
return false;
}
if (password.length() < 6) {
Toast.makeText(this, "密码至少6位", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
使用 OkHttp 发起安全的 HTTPS POST 请求:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
MediaType JSON = MediaType.get("application/json; charset=utf-8");
String json = "{"username":"zhangsan","password":"123456"}";
Request request = new Request.Builder()
.url("https://your-backend-domain.com/api/user/login")
.post(RequestBody.create(json, JSON))
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(() -> Toast.makeText(LoginActivity.this, "网络错误", Toast.LENGTH_SHORT).show());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseBody = response.body().string();
// 解析 JSON 获取 token
JSONObject obj = new JSONObject(responseBody);
String token = obj.getJSONObject("data").getString("token");
saveToken(token); // 存储 Token
startActivity(new Intent(LoginActivity.this, MainActivity.class));
}
}
});
5.3.2 使用Token进行身份认证与后续请求携带
将获取的 JWT Token 存入 SharedPreferences,并在后续请求中加入认证头:
Request request = new Request.Builder()
.url("https://your-backend-domain.com/api/user/profile")
.header("Authorization", "Bearer " + retrievedToken)
.get()
.build();
5.4 异常处理与用户体验优化
5.4.1 网络不可用、超时、服务端错误分类捕获
OkHttp 回调中区分不同异常类型:
@Override
public void onFailure(Call call, IOException e) {
String message;
if (e instanceof SocketTimeoutException) {
message = "请求超时,请检查网络";
} else if (e instanceof UnknownHostException) {
message = "无法连接服务器";
} else {
message = "网络异常:" + e.getMessage();
}
runOnUiThread(() -> Toast.makeText(ctx, message, Toast.LENGTH_LONG).show());
}
5.4.2 提供Toast提示、进度条与离线缓存机制
展示加载动画:
progressBar.setVisibility(View.VISIBLE);
// 请求完成后隐藏
progressBar.setVisibility(View.GONE);
结合 Room 数据库实现离线缓存用户信息,提升弱网体验。
5.5 完整Demo部署与测试验证
5.5.1 本地Tomcat服务器运行Spring MVC后端
打包 Spring Boot 项目为 WAR 文件,部署至本地 Tomcat 的 webapps 目录,启动服务并访问 http://localhost:8080/your-app/api/user/login 验证接口可用性。
5.5.2 使用Postman模拟接口验证逻辑一致性
在 Postman 中发送 POST 请求:
- URL:
https://localhost:8443/api/user/login - Body (raw, JSON):
{ "username": "test", "password": "123456" }
- Headers:
Content-Type: application/json
观察返回结果是否符合预期格式。
5.5.3 真机调试与性能监控工具使用实践
使用 Android Studio 的 Logcat 查看网络日志,利用 Network Profiler 监控请求耗时与流量消耗。配合 Stetho 或 Chucker 可视化拦截 OkHttp 请求,极大提升调试效率。
sequenceDiagram
participant A as Android客户端
participant S as JavaWeb后端
participant DB as 数据库
A->>S: POST /login (JSON Credentials)
S->>DB: 查询用户凭证
DB-->>S: 返回验证结果
S->>S: 生成JWT Token
S-->>A: 返回Token(JSON)
A->>A: 保存Token至本地
A->>S: GET /profile (带Authorization头)
S->>S: 验证Token有效性
S-->>A: 返回用户资料
该流程图展示了完整的认证交互过程,涵盖关键节点的数据流动方向。
本文还有配套的精品资源,点击获取
简介:在Android应用开发中,使用GET和POST方法与服务器进行数据交互是基础且关键的技术。本文基于传智播客张泽华Android视频54-57的代码示例,详细解析如何在Android端利用HttpURLConnection、HttpClient和OkHttp等技术实现HTTP请求。涵盖GET请求获取数据、POST提交表单、与JavaWeb后端(如Spring MVC)交互、网络权限配置及HTTPS安全通信等内容。本Demo经过验证,帮助开发者掌握移动端网络编程的核心流程与最佳实践,适用于登录、注册等常见业务场景。
本文还有配套的精品资源,点击获取







