23、网络请求之HttpURLConnection:GET与POST请求、设置超时、读取响应流
网络请求,说白了就是让你的App跟服务器说上话。
Android里做网络请求的方式有好几种,但HttpURLConnection是官方最基础、最原生的一套API。我个人习惯在简单项目或者不想引入第三方库时,直接用这个。它轻量、可控,而且从Android 2.3开始,官方就推荐用它替代HttpClient了。
核心要点:HttpURLConnection是Java标准库的一部分,Android对它做了封装。它支持HTTP/HTTPS,能处理GET、POST,还能设置超时、读取响应流。你想想看,这些不就是网络请求最核心的几件事吗?
23.1 知识体系总览
先给你一张图,把本章的知识结构理清楚。我每次讲网络请求,都会先画这个图,心里有谱了再动手写代码。
23.2 GET请求:从服务器拿数据
GET请求,说白了就是把参数拼在URL后面,问服务器要东西。比如你要查天气,URL里带上城市名就行。
我记得刚入行那会儿,有个同事写GET请求时忘了对参数做URL编码,结果参数里带了个空格,服务器直接返回400。嗯,这里要注意:所有参数都必须用URLEncoder.encode()编码。
// 一个标准的GET请求示例
URL url = new URL("https://api.example.com/data?city=" + URLEncoder.encode("北京", "UTF-8") + "&page=1");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法,默认就是GET,但显式写出来更清晰
connection.setRequestMethod("GET");
// 设置请求头,模拟浏览器行为
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
connection.setRequestProperty("Accept", "application/json");
// 设置超时(后面会细讲)
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// 发起连接
connection.connect();
// 检查响应码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取响应流
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 这里response.toString()就是服务器返回的数据
} else {
// 处理错误
System.out.println("请求失败,响应码:" + responseCode);
}
// 最后一定要关闭连接
connection.disconnect();
我的小技巧:我在项目中习惯把读取响应流封装成一个工具方法,传入InputStream,返回String。这样不管GET还是POST,复用起来都方便。另外,记得在finally块里关闭连接,防止内存泄漏。
23.3 POST请求:往服务器发数据
POST请求跟GET不一样,数据是放在请求体里的,不是拼在URL上。你想想看,登录、提交表单、上传文件,这些场景都得用POST。
我曾经踩过一个坑:POST请求忘了设置Content-Type,服务器不知道我发的是什么格式的数据,直接返回415(Unsupported Media Type)。所以,Content-Type必须明确指定。
URL url = new URL("https://api.example.com/login");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置为POST请求
connection.setRequestMethod("POST");
// 关键:允许输出,因为我们要往服务器写数据
connection.setDoOutput(true);
// 设置请求头
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestProperty("Accept", "application/json");
// 设置超时
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// 构建请求体数据(JSON格式)
String jsonBody = "{\"username\":\"admin\",\"password\":\"123456\"}";
// 写入请求体
OutputStream os = connection.getOutputStream();
os.write(jsonBody.getBytes("UTF-8"));
os.flush();
os.close();
// 发起连接(其实调用getOutputStream后会自动连接,但显式调用更规范)
connection.connect();
// 读取响应
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
System.out.println("登录成功:" + response.toString());
} else {
System.out.println("登录失败,响应码:" + responseCode);
}
connection.disconnect();
注意:POST请求的setDoOutput(true)必须在connect()之前调用。我见过有人把顺序搞反了,结果写入数据时直接抛异常。另外,请求体数据不要太大,否则要考虑分块传输。
23.4 设置超时:别让用户等太久
网络请求最怕什么?怕卡死。用户点了个按钮,转圈转了30秒还没反应,换你你也烦。所以,超时设置是必须的。
HttpURLConnection提供了两个超时参数:
| 参数 | 作用 | 建议值 |
|---|---|---|
| connectTimeout | 建立连接的超时时间。如果服务器连不上,到这个时间就放弃。 | 5-10秒 |
| readTimeout | 读取数据的超时时间。如果服务器响应慢,到这个时间就放弃。 | 10-15秒 |
我个人习惯把connectTimeout设成5秒,readTimeout设成10秒。为什么?因为连接超时通常意味着网络不通或者服务器挂了,没必要等太久。而读取超时可能只是服务器处理慢,稍微多给点时间。
connection.setConnectTimeout(5000); // 5秒
connection.setReadTimeout(10000); // 10秒
避坑指南:我曾经在一个弱网环境下测试,发现即使设置了超时,有时候还是会卡住。后来排查发现,是因为我在主线程里做了网络请求,而Android 4.0以后不允许在主线程做网络操作。所以,网络请求一定要放在子线程里,配合Handler或者AsyncTask来更新UI。
23.5 读取响应流:拿到服务器返回的数据
不管是GET还是POST,最终都要读取服务器返回的数据。这里有个细节:响应流要按字符读取,而不是字节。因为服务器返回的通常是文本(JSON、XML、HTML),用BufferedReader包装一下,按行读取最方便。
嗯,这里还要注意编码问题。如果服务器返回的Content-Type里指定了charset,就用那个编码。如果没有指定,我一般默认用UTF-8,因为现在大部分API都用UTF-8。
// 通用的响应流读取方法
private String readResponse(HttpURLConnection connection) throws IOException {
InputStream inputStream = null;
BufferedReader reader = null;
try {
// 如果响应码是200,用getInputStream();否则用getErrorStream()
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
inputStream = connection.getInputStream();
} else {
inputStream = connection.getErrorStream();
}
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} finally {
// 关闭资源
if (reader != null) {
try { reader.close(); } catch (IOException e) { e.printStackTrace(); }
}
if (inputStream != null) {
try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
我的经验:读取响应流时,别忘了处理getErrorStream()。服务器返回4xx或5xx时,响应体里可能包含错误详情,用getErrorStream()才能读到。我之前有个项目,一直用getInputStream(),结果服务器返回400时直接抛异常,连错误信息都拿不到。
23.6 完整示例:GET与POST二合一
最后,给你一个完整的工具类示例。我在项目中就是这么用的,把GET和POST封装成两个静态方法,传入URL和参数,返回响应字符串。简洁、实用、可复用。
public class HttpUtil {
private static final int CONNECT_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 10000;
// GET请求
public static String doGet(String urlStr, Map<String, String> params) throws IOException {
// 拼接参数
if (params != null && !params.isEmpty()) {
StringBuilder sb = new StringBuilder(urlStr);
sb.append("?");
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
.append("=")
.append(URLEncoder.encode(entry.getValue(), "UTF-8"))
.append("&");
}
urlStr = sb.substring(0, sb.length() - 1); // 去掉最后一个&
}
HttpURLConnection connection = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(CONNECT_TIMEOUT);
connection.setReadTimeout(READ_TIMEOUT);
connection.connect();
return readResponse(connection);
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
// POST请求
public static String doPost(String urlStr, String jsonBody) throws IOException {
HttpURLConnection connection = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setConnectTimeout(CONNECT_TIMEOUT);
connection.setReadTimeout(READ_TIMEOUT);
// 写入请求体
OutputStream os = connection.getOutputStream();
os.write(jsonBody.getBytes("UTF-8"));
os.flush();
os.close();
return readResponse(connection);
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
// 读取响应流(复用)
private static String readResponse(HttpURLConnection connection) throws IOException {
InputStream inputStream = null;
BufferedReader reader = null;
try {
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
inputStream = connection.getInputStream();
} else {
inputStream = connection.getErrorStream();
}
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} finally {
if (reader != null) {
try { reader.close(); } catch (IOException e) { e.printStackTrace(); }
}
if (inputStream != null) {
try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
}
好了,HttpURLConnection的核心用法就这些。GET、POST、超时、响应流,四个点串起来,你就能搞定大部分网络请求场景了。记住:子线程执行、设置超时、关闭连接,这三个习惯养成了,网络请求这块基本不会出大问题。
公众号:蓝海资料掘金营,微信deep3321