网络编程(上):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常用注解:

注解用途示例
@GETGET请求@GET("users")
@POSTPOST请求@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解析失败。

知识体系总览

下面这张图,是我自己总结的网络编程知识体系。你可以把它当成一张地图,随时回来看看。

Android网络编程知识体系 HTTP协议基础 OkHttp框架 Retrofit框架 请求方法 / 状态码 GET POST PUT DELETE 200 404 500 请求头 / 响应头 核心配置 / 拦截器 超时 / 缓存 / 连接池 日志拦截器 鉴权拦截器 注解驱动 / 接口定义 @GET @POST @Path @Query @Body ConverterFactory 网络请求封装:统一管理、错误处理、拦截器链 目标:稳定、高效、易维护的网络层

本章小结:

  • HTTP协议是网络通信的基础,理解请求/响应模型、状态码、请求头是关键。
  • OkHttp是高性能的HTTP客户端,负责连接管理、超时控制、拦截器链。
  • Retrofit基于OkHttp,用注解方式定义接口,让代码更简洁。
  • 封装网络层,统一管理配置、错误处理、拦截器,是项目工程化的必备技能。

最后说一句:网络编程没有捷径。多写、多调试、多看日志。我刚开始学的时候,每个请求都要用Charles抓包看一遍,慢慢就摸透了。你也能做到。


公众号:蓝海资料掘金营,微信deep3321