作为一名在Android开发领域深耕多年的工程师,我深知网络请求是移动应用开发中最基础也最关键的技能之一。今天我将通过一个完整的项目案例,带大家从最底层的HttpURLConnection开始,逐步深入到OkHttp和Retrofit2等流行框架,同时详细讲解JSON数据处理的各种技巧。
这个教程特别适合有一定Android基础但想系统学习网络编程的开发者。我们将围绕用户信息查询(GET)和登录(POST)这两个典型场景,使用同一组API接口进行演示,这样你能更清晰地看到不同技术方案之间的差异和演进。
很多新手开发者容易陷入一个误区:认为网络请求就是简单地调用API发送数据。但实际上,理解底层协议对于调试网络问题、优化请求性能至关重要。我曾遇到过这样一个案例:一个看似简单的接口调用在弱网环境下频繁失败,最终发现是因为没有正确设置TCP超时参数。
网络协议本质上是一套通信规则,它规定了数据如何打包、传输、校验以及连接如何建立和关闭。在Android开发中,我们主要工作在应用层,但必须了解下层协议的工作机制。
实际开发中最常用的是TCP/IP四层模型:
对于移动开发者来说,最重要的是理解传输层和应用层的关系。当我们在代码中创建一个HTTP请求时,实际上是应用层协议在驱动下层的TCP连接。
TCP通过三次握手确保双方都能正常通信:
这个过程的超时设置会直接影响连接建立的效率。在OkHttp中,我们可以通过.connectTimeout()方法来配置。
连接终止时的四次挥手:
在代码中,我们必须确保及时关闭连接,否则会导致资源泄漏。HttpURLConnection的disconnect()方法就是用于此目的。
最常用的两种方法:
一个完整的HTTP请求包含:
HTTP响应包含:
在AndroidManifest.xml中添加网络权限:
xml复制<uses-permission android:name="android.permission.INTERNET"/>
对于HTTP明文请求,需要配置网络安全策略:
xml复制<application
android:usesCleartextTraffic="true">
</application>
或者为特定域名配置:
xml复制<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">yourdomain.com</domain>
</domain-config>
</network-security-config>
java复制private void sendGetRequest() {
new Thread(() -> {
try {
URL url = new URL("http://example.com/api?param=value");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(8000);
InputStream in = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
runOnUiThread(() -> {
// 更新UI
});
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
关键点:
java复制private void sendPostRequest() {
new Thread(() -> {
try {
URL url = new URL("http://example.com/api/login");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json");
String jsonBody = "{\"username\":\"test\",\"password\":\"123\"}";
OutputStream os = conn.getOutputStream();
os.write(jsonBody.getBytes());
os.flush();
// 处理响应...
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
注意事项:
创建JSON:
java复制JSONObject json = new JSONObject();
json.put("name", "张三");
json.put("age", 25);
JSONArray hobbies = new JSONArray();
hobbies.put("篮球");
hobbies.put("音乐");
json.put("hobbies", hobbies);
解析JSON:
java复制String jsonStr = "{\"name\":\"张三\",\"age\":25}";
JSONObject json = new JSONObject(jsonStr);
String name = json.getString("name");
int age = json.getInt("age");
添加依赖:
gradle复制implementation 'com.google.code.gson:gson:2.8.9'
对象转JSON:
java复制User user = new User("张三", 25);
Gson gson = new Gson();
String json = gson.toJson(user);
JSON转对象:
java复制String json = "{\"name\":\"张三\",\"age\":25}";
User user = gson.fromJson(json, User.class);
gradle复制implementation 'com.squareup.okhttp3:okhttp:4.9.3'
java复制OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://example.com/api")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 处理失败
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String json = response.body().string();
// 处理响应
}
}
});
java复制MediaType JSON = MediaType.get("application/json; charset=utf-8");
String jsonBody = "{\"username\":\"test\"}";
RequestBody body = RequestBody.create(jsonBody, JSON);
Request request = new Request.Builder()
.url("http://example.com/api")
.post(body)
.build();
client.newCall(request).enqueue(...);
添加日志拦截器:
java复制HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
自定义拦截器:
java复制class AuthInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("Authorization", "Bearer token")
.build();
return chain.proceed(request);
}
}
gradle复制implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
java复制public interface ApiService {
@GET("user/info")
Call<User> getUserInfo(@Query("user_id") int userId);
@POST("user/login")
Call<LoginResponse> login(@Body LoginRequest request);
}
java复制Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService api = retrofit.create(ApiService.class);
java复制api.getUserInfo(123).enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User user = response.body();
// 处理用户数据
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
// 处理错误
}
});
使用OkHttp:
java复制File file = new File(path);
RequestBody fileBody = RequestBody.create(file, MediaType.parse("image/*"));
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(), fileBody)
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
使用Retrofit:
java复制@Multipart
@POST("upload")
Call<ResponseBody> uploadFile(@Part MultipartBody.Part file);
使用OkHttp:
java复制Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try (InputStream is = response.body().byteStream();
FileOutputStream fos = new FileOutputStream(file)) {
byte[] buf = new byte[8192];
int len;
while ((len = is.read(buf)) > 0) {
fos.write(buf, 0, len);
}
}
}
});
java复制new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
java复制Cache cache = new Cache(cacheDir, cacheSize);
client.cache(cache);
在实际项目中,我通常会根据以下因素选择技术方案:
从个人经验来看,Retrofit2+OkHttp的组合在大多数场景下都是最佳选择,它提供了良好的抽象和扩展性,同时保持了足够的灵活性。