【Android】HttpURLConnection解析
文章目录
- 【Android】HttpURLConnection解析
- [1. HttpURLConnection概述](#1. HttpURLConnection概述)
- [2. HttpURLConnection核心方法解析](#2. HttpURLConnection核心方法解析)
- [3. Get请求的使用方法](#3. Get请求的使用方法)
- [4. POST请求的使用方法](#4. POST请求的使用方法)
- [5. 请求头的设置](#5. 请求头的设置)
- [6. 上传文件](#6. 上传文件)
- [7. 下载文件](#7. 下载文件)
- [8. 总结](#8. 总结)
1. HttpURLConnection概述
虽然现代 Android 开发中我们更倾向于使用 OkHttp 或 Retrofit 等高级网络框架,但理解底层的 HttpURLConnection 仍然是非常有价值的。它不仅是 Java 标准库的一部分,也是许多网络库的基石。
HttpURLConnection 是 Java 提供的标准 HTTP 客户端类,位于 java.net 包中。在 Android 中,它是系统内置的、无需额外依赖的网络通信方式。
2. HttpURLConnection核心方法解析
-
连接参数设置
方法签名 功能 使用时机 注意事项 示例 void setRequestMethod(String method)设置 HTTP 请求方法(GET/POST/PUT/DELETE) 连接前配置 必须在连接前调用;默认为 "GET"conn.setRequestMethod("POST");void setDoOutput(boolean doOutput)是否允许写入请求体 发送 POST/PUT 数据前 设为 true才能调用getOutputStream()conn.setDoOutput(true);void setConnectTimeout(int ms)设置连接超时 防止长时间卡死 建议必设(默认无限等待) conn.setConnectTimeout(10_000);void setReadTimeout(int ms)设置读取超时 控制接收数据的最大等待时间 同上,建议设置 conn.setReadTimeout(15_000);void disconnect()释放连接资源 请求完成后必须调用 放在 finally块中,防止连接泄漏finally { if (conn != null) conn.disconnect(); } -
设置请求头
方法签名 功能 使用时机 注意事项 示例 void setRequestProperty(String key, String value)设置请求头(覆盖同名) 添加 Content-Type、Authorization等多次设置同名会覆盖 conn.setRequestProperty("Content-Type", "application/json");例如:
javaconn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); // 告诉服务器我们发送的是 UTF-8 编码的 JSON 数据 conn.setRequestProperty("Accept", "application/json"); // 告诉服务器我们希望接收 JSON 格式的响应 -
发送请求
方法签名 功能 使用时机 注意事项 示例 OutputStream getOutputStream()获取输出流以发送请求体(如 JSON) 需上传数据时 调用即触发连接;需配合 setDoOutput(true)os.write(json.getBytes("UTF-8")); -
获取响应
方法签名 功能 使用时机 注意事项 示例 int getResponseCode()获取 HTTP 响应状态码(如 200、404) 判断请求是否成功 首次调用会发起实际网络请求 if (conn.getResponseCode() == 200) { ... }InputStream getInputStream()获取成功响应(2xx)的数据流 读取正常返回内容 仅适用于 2xx 响应;只能读一次 new BufferedReader(new InputStreamReader(conn.getInputStream()));InputStream getErrorStream()获取错误响应(4xx/5xx)的数据流 处理 API 错误信息 若无错误体则返回 nullInputStream err = conn.getErrorStream();String getHeaderField(String name)获取指定响应头的值 快速读取 Content-Type、Location等不区分大小写;不存在返回 nullString type = conn.getHeaderField("Content-Type");String getContentType()获取响应的 MIME 类型 判断数据格式(JSON/XML等) 可能为 nullif (conn.getContentType().contains("json")) { ... }String getContentEncoding()获取内容编码(如 "gzip") 判断是否需手动解压 Android 不自动解压 gzip if ("gzip".equals(conn.getContentEncoding())) { in = new GZIPInputStream(in); }int getContentLength()获取响应体字节长度 预分配缓冲区或显示进度 返回 -1表示长度未知(如 chunked)byte[] buf = new byte[conn.getContentLength()];
需要注意的是,从 Android 3.0(API 11)开始,主线程禁止网络操作 。使用 HttpURLConnection 必须在子线程中执行,否则会抛出 NetworkOnMainThreadException。
3. Get请求的使用方法
创建Get请求方法:
java
public static String sendGetRequest(String urlString) throws IOException {
// 创建URL对象
URL url = new URL(urlString);
// 得到Connection对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置请求方法(默认就是 GET,可省略)
conn.setRequestMethod("GET");
// 设置连接参数
conn.setConnectTimeout(5000); // 连接超时 5s
conn.setReadTimeout(10000); // 读取超时 10s
// 连接
conn.connect(); // 一般不用写,因为 URLConnection 类会自动处理,除非要提前打开连接
// 获取响应码
int responseCode = conn.getResponseCode();
Log.d(TAG, "响应码:" + responseCode);
// 读取响应内容
StringBuilder response = new StringBuilder();
// 得到响应流
// 这里可以根据响应码来决定调用哪个流,2xx就调getInputStream(),非2xx就调getErrorStream()
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line;
// 将响应流转换成字符串
while ((line = reader.readLine()) != null) {
response.append(line).append("\n");
}
} finally {
conn.disconnect(); // 关闭连接
}
return response.toString();
}
在子线程中开始请求:
java
String urlStr = "https://httpbin.org/get?name=Java";
button.setOnClickListener(v -> {
new Thread(() -> {
try {
String res = sendGetRequest(urlStr);
// 更新 UI 需切回主线程
runOnUiThread(() -> {
((TextView) findViewById(R.id.text)).setText(res);
});
} catch (IOException e) { e.printStackTrace(); }
}).start();
});
分析:
-
使用
url.openConnection();打开连接,不过openConnection()返回的是URLConnection,需强转为HttpURLConnection才能使用 HTTP 特有方法。 -
conn.setRequestMethod("GET");设置请求方法,默认就是GET请求 -
一般无需显示调用
conn.connect();,因为getResponseCode()或getInputStream()会自动触发连接。除非想提前建立连接。 -
获取响应码后,可以根据响应码来决定调用哪个流,2xx就调getInputStream(),非2xx就调getErrorStream()
-
使用
StandardCharsets.UTF_8将字符编码指定为UTF-8,避免中文乱码 -
conn.disconnect()放在finally中,请求完毕后释放连接 -
conn.getInputStream()得到一个流对象,从这个流对象中只能读取一次数据,第二次读取时将会得到空数据。
4. POST请求的使用方法
创建POST请求方法:
java
public static String sendPostRequest(String urlStr, String jsonPayload) throws IOException {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置请求方法和头
conn.setRequestMethod("POST"); // 设置请求方式为POST
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); // 告诉服务器我们发送的是 UTF-8 编码的 JSON 数据
conn.setRequestProperty("Accept", "application/json"); // 告诉服务器我们希望接收 JSON 格式的响应
conn.setDoOutput(true); // 表示我们要向服务器写入数据(即发送请求体)。
conn.setConnectTimeout(5000); // 连接超时:5秒
conn.setReadTimeout(10000); // 读取超时:10秒
// 写入请求体
// 获取输出流以发送请求体
try (OutputStream os = conn.getOutputStream()) {
// 将JSON字符串转为byte数组
byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8);
// 发送请求
os.write(input, 0, input.length);
}
// 读取响应
int responseCode = conn.getResponseCode();
Log.d(TAG, "POST响应码:" + responseCode);
// 根据响应码获取响应流
InputStream is = (responseCode >= 200 && responseCode < 300) ? conn.getInputStream() : conn.getErrorStream();
StringBuilder response = new StringBuilder();
// 将字节流转换为缓冲字符流,避免频繁I/O,提高效率
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line = null;
// 转成字符串
while ((line = reader.readLine()) != null) {
response.append(line).append("\n");
}
} finally {
// 关闭连接
conn.disconnect();
}
return response.toString();
}
在子线程中开启:
java
String urlStr = "https://httpbin.org/get?name=Java";
button.setOnClickListener(v -> {
new Thread(() -> {
try {
String res = sendGetRequest(urlStr);
// 更新 UI 需切回主线程(用 runOnUiThread 或 Handler)
String resp = sendRequest("https://httpbin.org/post", res);
runOnUiThread(() -> {
((TextView) findViewById(R.id.text)).setText(resp);
});
} catch (IOException e) { e.printStackTrace(); }
}).start();
});
这里通过POST请求把GET方法请求到的JSON字符串当作参数传递
分析:
-
POST方法和GET方法类似,POST方法发送数据是通过HttpURLConnection得到一个OutputStream字节输出流来实现,这个字节流是写在
try-with-resources中的,会自动关闭,释放资源 -
在GET方法中提到,可以根据响应码来决定获取哪个响应流,
responseCode >= 200 && responseCode < 300表示2xx响应码就获取getInputStream()字节输入流,避免关闭连接时出错 -
要是使用POST方法来传递键值对参数,传入的字符串采用键值对格式,键与值之间用连接,每个键值对之间用&连接,例如:"userName=zhangsan&password=123456"
5. 请求头的设置
设置请求头通过void setRequestProperty(String key, String value)方法实现,重复设置则会覆盖同名字段 ,需要注意的是,该方法必须在连接建立之前调用(即在 getInputStream()、getResponseCode() 等触发网络操作之前),一旦调用了 getInputStream()、getResponseCode()、getOutputStream(),连接就已建立,不能再修改请求头。
常用请求头:
| 请求头 | 作用 | 示例 |
|---|---|---|
Content-Type |
告诉服务器请求体的 MIME 类型和编码 | conn.setRequestProperty("Content-Type", "application/json; charset=utf-8"); |
Authorization |
身份认证(如 Bearer Token、Basic Auth) | conn.setRequestProperty("Authorization", "Bearer " + token); conn.setRequestProperty("Authorization", "Basic " + base64Credentials); |
User-Agent |
标识客户端(有些 API 要求特定 UA) | conn.setRequestProperty("User-Agent", "MyApp/1.0 Android"); |
Accept |
告诉服务器期望的响应格式 | conn.setRequestProperty("Accept", "application/json"); |
Accept-Encoding |
声明支持的压缩格式(如 gzip) | conn.setRequestProperty("Accept-Encoding", "gzip"); (注意:Android 会自动处理 gzip 响应,但需手动解压) |
Cookie |
发送 Cookie(用于会话保持) | conn.setRequestProperty("Cookie", "JSESSIONID=abc123"); 或用 addRequestProperty 发多个 |
Cache-Control |
控制缓存行为 | conn.setRequestProperty("Cache-Control", "no-cache"); |
例如:
java
// 设置请求头
// Content-Type: 告诉服务器请求体是 UTF-8 编码的 JSON
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
// Authorization: Bearer Token 方式
conn.setRequestProperty("Authorization", "Bearer " + token);
// User-Agent: 标识客户端
conn.setRequestProperty("User-Agent", "MyApp/1.0 Android");
// Accept: 希望服务器返回 JSON
conn.setRequestProperty("Accept", "application/json");
// Accept-Encoding: 声明支持 gzip(Android 不会自动解压,需手动处理)
conn.setRequestProperty("Accept-Encoding", "gzip");
// Cookie: 发送会话 ID(如从登录响应中获取)
conn.setRequestProperty("Cookie", "JSESSIONID=abc123xyz; lang=zh-CN");
// Cache-Control: 禁用缓存(确保获取最新数据)
conn.setRequestProperty("Cache-Control", "no-cache");
6. 上传文件
在上面的POST请求中,我们通过HttpURLConnection得到了一个OutputStream,通过这个字节流就可以向服务器发送数据,那么上传文件也就是一样的做法,可以先将文件转换成流,通过输出流将文件发送到服务器。
java
public static void writeMultipartFile(DataOutputStream dos, String paramName, File file) throws IOException {
// 表单中的唯一分隔符
String boundary = "----" + UUID.randomUUID().toString();
String CRLF = "\r\n";
// 设置HttpURLConnection,与POST请求一致
// 设置 Content-Type(这行通常在 HttpURLConnection 上设置)
// 表示这次 POST 请求的 body 不是普通文本或 JSON,而是一个多部分混合数据(multipart),各部分用 boundary 字符串来分隔。
// conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
// 文件头部
dos.writeBytes("--" + boundary + CRLF); // 开始一个新的part
dos.writeBytes("Content-Disposition: form-data; name=\"" + paramName + "\"; filename=\"" + file.getName() + "\"" + CRLF);
// 自动根据文件扩展名(如 .jpg, .pdf)猜测 MIME 类型,如果猜不到(如 .xyz),就用通用二进制类型 application/octet-stream。
String type = URLConnection.guessContentTypeFromName(file.getName());
String contentType = (type != null) ? type : "application/octet-stream"; // 二进制类型
dos.writeBytes("Content-Type: " + contentType + CRLF);
dos.writeBytes(CRLF);
// 文件内容
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buf = new byte[4096];
int len;
while ((len = fis.read(buf)) != -1) {
dos.write(buf, 0, len);
}
}
// 结束标记
dos.writeBytes(CRLF + "--" + boundary + "--" + CRLF);
// 后续:获取响应内容,与GET、POST请求一致
}
分析:
- 由于HTTP 请求中多部分数据的格式规范要求,上传文件不单单是文件内容的上传,还要通过边界来分割请求中的不同部分。比如下面这个表单格式:

上传了两个键值对参数和一个文件,每一部分都有自己的头部信息和分割线,用于标记各个部分,只要我们能模拟这个格式,也就可以实现上传文件的功能
- 方法参数
DataOutputStream dos:是HTTP 连接的输出流(HttpURLConnection.getOutputStream()返回),用于写入请求体。 - 方法参数
paramName:表单字段名,比如后端用@RequestParam("file")接收,这里就传"file"。 String boundary = "----" + UUID.randomUUID().toString();用于生成唯一分隔符,用作multipart 各部分之间的分隔线。dos.writeBytes("Content-Type: " + contentType + CRLF);写入的Content-Type是当前部分的类型,而不是整个请求的类型。
7. 下载文件
下载文件就很简单了,只要能从HttpURLConnection中得到输入流,就可以中流中读取数据,示例如下:
java
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import android.util.Log;
public static boolean downloadFile(String fileUrl, String savePath) {
try {
// 从 URL 提取文件名(如 https://x.com/photo.jpg → photo.jpg)
String fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1);
if (fileName.isEmpty()) fileName = "downloaded_file";
// 拼接完整保存路径
File file = new File(savePath, fileName);
// 确保父目录存在
file.getParentFile().mkdirs();
// HttpURLConnection创建
URL url = new URL(fileUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10_000);
conn.setReadTimeout(30_000);
conn.setRequestMethod("GET");
conn.setInstanceFollowRedirects(true);
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.d(TAG, "下载失败: HTTP " + conn.getResponseCode());
return false;
}
// 下载文件
try (InputStream is = conn.getInputStream();
FileOutputStream fos = new FileOutputStream(file)) {
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
}
Log.d(TAG, "已保存: " + file.getAbsolutePath());
return true;
} catch (Exception e) {
Log.d(TAG, "下载出错: " + e.getMessage());
return false;
}
}
使用示例:
java
// 注意:必须在子线程中调用
new Thread(() -> {
String url = "https://picsum.photos/200/300";
// 使用应用私有目录,无需申请权限
File saveDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
boolean success = downloadFile(url, saveDir.getAbsolutePath());
if (success) {
runOnUiThread(() -> {
Toast.makeText(this, "下载完成!文件位置:" + new File(saveDir, url.substring(url.lastIndexOf('/') + 1)).getAbsolutePath(), Toast.LENGTH_LONG).show();
});
}
}).start();
分析:
-
仅需传入文件下载URL,自动提取文件名并下载到当前目录
-
使用
try-with-resources自动管理流,避免资源泄露
8. 总结
HttpURLConnection是 Android 和 Java 中内置的轻量级 HTTP 客户端,适合基础网络请求。- GET 请求:直接打开连接读取输入流;POST 请求:需设置
setDoOutput(true)并写入数据。 - 支持文件下载(保存到指定路径)和文件上传(手动构造 multipart 格式)。
- 必须在子线程中使用,避免主线程网络限制。
- 需要手动处理超时、编码、响应码、流关闭等细节。
- 虽然功能完整,但代码较繁琐;现代开发更推荐使用 OkHttp 或 Retrofit。