為什麼要二次封裝
- 原生
HttpURLConnection 代碼冗長,異常處理分散。
- 項目中常需統一:連接/讀取超時、請求頭、參數編碼、日誌、重試策略。
- 封裝後業務代碼只關心「URL、參數、回調」。
完整工具類(基於 HttpURLConnection,無第三方依賴)
public class HttpClientUtil {
private static final int DEFAULT_CONN_TIMEOUT = 10_000; // 10s
private static final int DEFAULT_READ_TIMEOUT = 10_000;
private static final String DEFAULT_CHARSET = "UTF-8";
/** 同步 POST,返回響應體字符串,非 2xx 拋異常 */
public static String post(String url, Map params) throws IOException {
return post(url, params, DEFAULT_CONN_TIMEOUT, DEFAULT_READ_TIMEOUT);
}
/** 同步 POST,自定義超時 */
public static String post(String url, Map params, int connTimeout, int readTimeout) throws IOException {
HttpURLConnection conn = null;
try {
URL u = new URL(url);
conn = (HttpURLConnection) u.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(connTimeout);
conn.setReadTimeout(readTimeout);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + DEFAULT_CHARSET);
conn.setRequestProperty("Accept", "application/json");
// 寫參數
if (params != null && !params.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (Map.Entry e : params.entrySet()) {
if (sb.length() > 0) sb.append('&');
sb.append(URLEncoder.encode(e.getKey(), DEFAULT_CHARSET))
.append('=')
.append(URLEncoder.encode(e.getValue(), DEFAULT_CHARSET));
}
try (OutputStream os = conn.getOutputStream()) {
os.write(sb.toString().getBytes(DEFAULT_CHARSET));
}
}
int code = conn.getResponseCode();
InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, DEFAULT_CHARSET))) {
StringBuilder resp = new StringBuilder();
String line;
while ((line = br.readLine()) != null) resp.append(line);
if (code >= 400) throw new IOException("HTTP " + code + ": " + resp);
return resp.toString();
}
} finally {
if (conn != null) conn.disconnect();
}
}
/** 異步 POST,回調在主線程(需配合 Handler/RunOnUiThread) */
public static void postAsync(String url, Map params, Callback cb) {
new Thread(() -> {
try {
String result = post(url, params);
cb.onSuccess(result);
} catch (IOException e) {
cb.onFailure(e);
}
}).start();
}
public interface Callback {
void onSuccess(String responseBody);
void onFailure(IOException e);
}
}
使用示例
// 在子線程/IntentService/RxJava/協程中調用
Map params = new HashMap<>();
params.put("userId", "123");
params.put("action", "login");
try {
String json = HttpClientUtil.post("https://api.example.com/do", params);
// 解析 json...
} catch (IOException e) {
Log.e("HTTP", "請求失敗", e);
}
// 或異步
HttpClientUtil.postAsync(url, params, new HttpClientUtil.Callback() {
@Override public void onSuccess(String body) { runOnUiThread(() -> parse(body)); }
@Override public void onFailure(IOException e) { runOnUiThread(() -> toast("網絡錯誤")); }
});
進階擴展建議
- 換用 OkHttp:連接池、透明 GZIP、HTTP/2、攔截器鏈、取消標籤,生產環境首選。
- 統一錯誤碼映射:將 HTTP 狀態碼、業務 code 統一為 AppException 層級。
- 請求/響應日誌攔截:OkHttp
HttpLoggingInterceptor 或自定義 Filter。
- 證書釘固/雙向認證:高安全場景配置
SSLSocketFactory。
避坑提醒
- **主線程網絡請求會拋 NetworkOnMainThreadException** —— 必須在子線程/異步任務中調用同步方法。
- **參數編碼必須雙端一致**(UTF-8),
URLEncoder.encode 空格編為 +,符合 application/x-www-form-urlencoded 規範。
- **大文件上傳請用 Multipart**,別塞進表單字段。
- **關閉連接** ——
conn.disconnect() 在 finally 塊,防連接洩漏。