【Android】HttpURLConnection解析

【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 开发中我们更倾向于使用 OkHttpRetrofit 等高级网络框架,但理解底层的 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-TypeAuthorization 多次设置同名会覆盖 conn.setRequestProperty("Content-Type", "application/json");

    例如:

    java 复制代码
    conn.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 错误信息 若无错误体则返回 null InputStream err = conn.getErrorStream();
    String getHeaderField(String name) 获取指定响应头的值 快速读取 Content-TypeLocation 不区分大小写;不存在返回 null String type = conn.getHeaderField("Content-Type");
    String getContentType() 获取响应的 MIME 类型 判断数据格式(JSON/XML等) 可能为 null if (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 格式)。
  • 必须在子线程中使用,避免主线程网络限制。
  • 需要手动处理超时、编码、响应码、流关闭等细节。
  • 虽然功能完整,但代码较繁琐;现代开发更推荐使用 OkHttpRetrofit
相关推荐
五阿哥永琪2 小时前
java8新特性 时间间隔类 Duration和Period
java
闻哥2 小时前
Docker Swarm 负载均衡深度解析:VIP vs DNSRR 模式详解
java·运维·jvm·docker·容器·负载均衡
panzer_maus2 小时前
工厂模式、代理模式与单例模式的介绍
java·设计模式·代理模式
小林学编程2 小时前
模型上下文协议(MCP)的理解
java·后端·llm·prompt·resource·tool·mcp协议
亘元有量-流量变现3 小时前
鸿蒙、安卓、苹果音频设备技术深度解析与开发实践
android·wpf·harmonyos·亘元有量·积分墙
软泡芙3 小时前
【Bug】ReactiveUI WPF绑定中依赖属性不更新的问题分析与解决方案
java·bug·wpf
小程故事多_803 小时前
Harness实战指南,在Java Spring Boot项目中规范落地OpenSpec+Claude Code
java·人工智能·spring boot·架构·aigc·ai编程
浪扼飞舟3 小时前
WPF输入验证(ValidationRule)
java·javascript·wpf
砍材农夫8 小时前
spring-ai 第四多模态API
java·人工智能·spring