网络编程(上):HTTP协议基础、OkHttp框架使用、Retrofit框架使用、网络请求封装与拦截器
网络编程,说白了就是让App跟服务器“说话”。你想想看,一个不能联网的App,跟单机游戏有什么区别?我刚开始做Android时,总觉得网络请求很简单——不就是发个请求收个响应吗?直到我被线上崩溃、数据解析失败、证书问题折磨了三个通宵……嗯,从那以后我再也不敢小看网络层了。
今天这一章,我们就把网络编程的底裤扒干净。从HTTP协议开始,到OkHttp、Retrofit,再到拦截器与封装。我会把我踩过的坑、总结的经验,一股脑儿倒给你。
1. HTTP协议基础——你每天都在用,但你真的懂吗?
HTTP协议,是App与服务器通信的“通用语言”。你刷微博、看视频、发朋友圈,底层全是HTTP在干活。
我个人习惯把HTTP理解成“快递服务”:
- 请求(Request):你下单,告诉快递员你要什么。
- 响应(Response):快递员把包裹送到你手上。
- 状态码:快递员的反馈——200是“送到了”,404是“地址不存在”,500是“仓库炸了”。
一个典型的HTTP请求长这样:
POST /api/login HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer xxxxxx
{"username":"admin","password":"123456"}
响应呢?大概是这样:
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: sessionId=abc123
{"code":0,"message":"success","data":{"token":"xxx"}}
核心要点:
- 请求方法:GET(查)、POST(增)、PUT(改)、DELETE(删)。别搞混了,我见过有人用GET传密码……
- 请求头:Content-Type告诉服务器数据格式,Authorization传令牌。
- 状态码:2xx成功,3xx重定向,4xx客户端错误,5xx服务器错误。
- 无状态:HTTP本身不记你是谁,所以需要Cookie或Token来维持会话。
避坑指南:我曾经在项目里用GET请求传了200KB的图片数据,结果服务器直接返回414(URI太长)。记住:GET有长度限制,上传数据用POST。
2. OkHttp框架使用——Android网络请求的“发动机”
OkHttp是Square公司出品的高性能HTTP客户端。Android官方也推荐用它。说白了,它就是帮你把HTTP请求的脏活累活全干了——连接复用、超时重试、Gzip压缩,样样精通。
先看一个最简单的GET请求:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.example.com/users")
.get()
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 网络挂了,或者超时了
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String body = response.body().string();
// 解析body
}
}
});
嗯,这里要注意:response.body().string()只能调用一次,第二次调用会返回空。为什么?因为响应体是流式的,读完就没了。我刚开始学的时候,调试了半天才发现这个问题。
再看一个POST请求,带JSON参数:
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String json = "{\"username\":\"admin\",\"password\":\"123456\"}";
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url("https://api.example.com/login")
.post(body)
.build();
OkHttp核心配置:
- 超时时间:connectTimeout、readTimeout、writeTimeout。别设太短,也别设太长。我一般设15秒。
- 拦截器:后面单独讲,这是OkHttp的灵魂。
- 缓存:设置Cache目录和大小,可以省流量。
- 连接池:默认支持5个Keep-Alive连接,减少握手开销。
避坑指南:我曾经在子线程里直接调用了client.newCall(request).execute()(同步方法),结果ANR了。记住:Android主线程不能做网络请求,一定要用enqueue()异步执行,或者自己开线程。
3. Retrofit框架使用——让网络请求优雅得像首诗
Retrofit也是Square家的,它是对OkHttp的进一步封装。你想想看,如果每个请求都要手动构建Request、解析Response,那代码得多恶心?Retrofit用注解的方式,把HTTP请求变成了Java接口调用。
先定义一个接口:
public interface ApiService {
@GET("users")
Call<List<User>> getUsers();
@POST("login")
Call<LoginResponse> login(@Body LoginRequest request);
@GET("users/{id}")
Call<User> getUser(@Path("id") int userId);
@GET("search")
Call<SearchResult> search(@Query("keyword") String keyword);
}
然后创建Retrofit实例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient) // 可以传入自定义的OkHttpClient
.build();
ApiService api = retrofit.create(ApiService.class);
// 发起请求
Call<List<User>> call = api.getUsers();
call.enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
List<User> users = response.body();
// 更新UI
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
// 处理错误
}
});
Retrofit常用注解:
| 注解 | 用途 | 示例 |
|---|---|---|
| @GET | GET请求 | @GET("users") |
| @POST | POST请求 | @POST("login") |
| @Path | 替换URL中的占位符 | @Path("id") |
| @Query | 添加查询参数 | @Query("page") |
| @Body | 请求体(自动序列化) | @Body LoginRequest |
| @Header | 添加请求头 | @Header("Authorization") |
避坑指南:我曾经把baseUrl写成了"https://api.example.com"(没有斜杠),然后接口路径是"users",结果拼接出来的URL变成了"https://api.example.comusers"。记住:baseUrl必须以/结尾,接口路径不要以/开头。
4. 网络请求封装与拦截器——让代码更健壮、更易维护
在实际项目中,我们不会直接new Retrofit实例。为什么?因为每个页面都写一遍baseUrl、ConverterFactory,那维护起来就是灾难。我个人习惯封装一个网络层,统一管理。
先看一个简单的封装:
public class RetrofitClient {
private static final String BASE_URL = "https://api.example.com/";
private static final int TIMEOUT = 15;
private static OkHttpClient okHttpClient;
private static Retrofit retrofit;
static {
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(new LoggingInterceptor()) // 日志拦截器
.addInterceptor(new AuthInterceptor()) // 鉴权拦截器
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
public static <T> T create(Class<T> serviceClass) {
return retrofit.create(serviceClass);
}
}
然后调用就变成了:
ApiService api = RetrofitClient.create(ApiService.class);
Call<List<User>> call = api.getUsers();
干净、清爽、统一。所有配置都在一个地方,改起来也方便。
拦截器——OkHttp的灵魂
拦截器是OkHttp最强大的功能之一。它可以在请求发出前、响应返回后,插入自定义逻辑。说白了,就是AOP(面向切面编程)的思想。
我常用的拦截器有这几种:
- 日志拦截器:打印请求和响应的详细信息,方便调试。
- 鉴权拦截器:自动在请求头中添加Token。
- 重试拦截器:请求失败时自动重试。
- 缓存拦截器:根据网络状态决定是否使用缓存。
看一个鉴权拦截器的例子:
public class AuthInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// 从SharedPreferences中读取Token
String token = getTokenFromLocal();
Request request = original.newBuilder()
.header("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
}
}
嗯,这里要注意:拦截器里不要做耗时操作,比如读写数据库。因为拦截器是在OkHttp的线程池里执行的,阻塞太久会影响其他请求。
警告:拦截器可以修改请求和响应,但不要滥用。我曾经见过一个同事在拦截器里修改了响应体,结果导致下游解析失败。拦截器只做“横切关注点”的事情,比如日志、鉴权、重试。不要在里面改业务数据。
统一错误处理
网络请求不可能永远成功。我习惯在封装层统一处理错误,而不是在每个调用处都写一遍try-catch。
public class NetworkCallback<T> implements Callback<T> {
@Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
onSuccess(response.body());
} else {
onError(new HttpException(response.code(), response.message()));
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
if (t instanceof SocketTimeoutException) {
onError(new NetworkException("连接超时,请检查网络"));
} else if (t instanceof UnknownHostException) {
onError(new NetworkException("无法连接服务器"));
} else {
onError(new NetworkException("网络异常:" + t.getMessage()));
}
}
public abstract void onSuccess(T data);
public abstract void onError(Throwable e);
}
这样,调用方只需要关心成功和失败两种情况,不用管底层是超时还是DNS解析失败。
知识体系总览
下面这张图,是我自己总结的网络编程知识体系。你可以把它当成一张地图,随时回来看看。
本章小结:
- HTTP协议是网络通信的基础,理解请求/响应模型、状态码、请求头是关键。
- OkHttp是高性能的HTTP客户端,负责连接管理、超时控制、拦截器链。
- Retrofit基于OkHttp,用注解方式定义接口,让代码更简洁。
- 封装网络层,统一管理配置、错误处理、拦截器,是项目工程化的必备技能。
最后说一句:网络编程没有捷径。多写、多调试、多看日志。我刚开始学的时候,每个请求都要用Charles抓包看一遍,慢慢就摸透了。你也能做到。
公众号:蓝海资料掘金营,微信deep3321