20、Android网络框架:HttpURLConnection、OkHttp、Retrofit原理与连接池管理

网络请求,说白了就是App跟服务器之间的一次“对话”。我做了这么多年Android,发现很多同学把网络框架用成了“黑盒”——调个接口、解析个JSON,完事。但一旦遇到连接超时、DNS劫持、内存泄漏,就抓瞎了。

今天咱们就把这三个框架的底裤扒干净。从最原始的HttpURLConnection,到工业级标配的OkHttp,再到优雅到极致的Retrofit。最后再聊聊那个容易被忽略但极其重要的——连接池管理。

20.1 HttpURLConnection:最底层的“老黄牛”

HttpURLConnection是JDK自带的网络工具。说白了,它就是最原始的HTTP客户端。我2012年刚入行时,Android官方文档还推荐用它。现在嘛,基本被OkHttp取代了。

它的核心流程其实很简单:

  1. 创建URL对象
  2. 调用openConnection()获取连接
  3. 设置请求方法、头、超时等参数
  4. 获取输入流,读取响应

来个最简示例:

URL url = new URL("https://api.example.com/data");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);

int code = conn.getResponseCode();
if (code == 200) {
    InputStream in = conn.getInputStream();
    // 读取数据...
}
conn.disconnect();

嗯,这里要注意一个坑。我在项目中遇到过,disconnect()如果不调用,连接不会立即释放。但如果你用了连接池(HttpURLConnection在Android 2.3之后默认支持),频繁调用disconnect()反而会影响复用。

我曾经踩过的坑:在循环中反复创建HttpURLConnection,没有复用连接。结果就是——大量TIME_WAIT状态的TCP连接,端口被耗尽,App直接卡死。后来改成复用同一个实例,问题解决。

HttpURLConnection的底层,其实是对Socket的封装。它处理了HTTP协议的请求行、请求头、响应码、分块传输等细节。但有个致命问题——它不支持HTTP/2,也不支持连接池的精细控制。

20.2 OkHttp:工业级的“瑞士军刀”

OkHttp现在几乎是Android网络请求的标配了。为什么?因为它把HttpURLConnection那些“脏活累活”全包了。

我个人习惯把OkHttp的核心能力归纳为三点:

  • 连接池复用:默认支持5个空闲连接,复用TCP连接,减少三次握手
  • 拦截器链:请求/响应可以层层拦截,做日志、重试、缓存等
  • HTTP/2支持:多路复用,一个连接并发处理多个请求

来看一个典型的OkHttp使用:

OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
        .addInterceptor(new HttpLoggingInterceptor())
        .build();

Request request = new Request.Builder()
        .url("https://api.example.com/data")
        .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 {
        // 处理成功
    }
});

你想想看,OkHttp的enqueue为什么是异步的?因为它内部维护了一个线程池。每个Call会被封装成一个AsyncCall,丢到线程池里执行。而同步的execute,则直接在当前线程执行。

核心原理:OkHttp的请求流程其实是一条“拦截器链”。从用户自定义的拦截器,到重试拦截器、桥接拦截器、缓存拦截器,最后到网络拦截器。每个拦截器都可以修改请求或响应。我经常用这个机制来做统一的Token注入和错误码处理。

20.3 Retrofit:优雅的“门面模式”

Retrofit不是网络框架,它是个“包装器”。它把OkHttp的调用封装成了Java接口。你只需要定义接口和方法,Retrofit帮你生成实现类。

说白了,Retrofit就是“注解驱动”的HTTP客户端。我刚开始用的时候,觉得这玩意儿太神奇了——一个注解就能搞定URL拼接、参数序列化、响应解析。

public interface ApiService {
    @GET("users/{id}")
    Call<User> getUser(@Path("id") int userId);

    @POST("users")
    Call<User> createUser(@Body User user);
}

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .client(okHttpClient)
        .build();

ApiService service = retrofit.create(ApiService.class);
Call<User> call = service.getUser(123);
call.enqueue(new Callback<User>() {
    // ...
});

它的原理其实不复杂:

  1. 通过动态代理,在运行时生成接口的实现类
  2. 每个方法调用,解析注解,构建一个OkHttp的Request对象
  3. 调用OkHttp的newCall()执行请求
  4. 响应回来之后,通过Converter把ResponseBody转成你指定的类型
我建议:Retrofit的ConverterFactory一定要选对。Gson虽然快,但遇到复杂泛型容易出问题。我个人更推荐Moshi或者Kotlin Serialization。另外,如果你用RxJava,记得加addCallAdapterFactory(RxJava2CallAdapterFactory.create())

20.4 连接池管理:被忽视的性能杀手

连接池,说白了就是“复用TCP连接”。每次HTTP请求都要经历三次握手,如果请求频繁,这个开销非常大。连接池把用过的连接缓存起来,下次请求直接复用。

OkHttp的ConnectionPool默认配置是这样的:

参数 默认值 说明
最大空闲连接数 5 超过这个数,最早的空闲连接会被关闭
存活时间 5分钟 空闲连接超过5分钟,会被清理
清理线程 后台守护线程 每30秒检查一次,清理过期连接

我曾经在项目中遇到过一个问题:用户反馈App在弱网环境下特别慢。排查后发现,连接池的存活时间设置得太短,导致频繁重建连接。后来我把存活时间从5分钟改成10分钟,问题明显改善。

注意:连接池不是越大越好。如果空闲连接太多,会占用系统资源。我建议根据App的实际并发量来调整。一般App,5个空闲连接足够了。如果是直播或IM类App,可以适当调大。

连接池的底层实现,其实是一个Deque<RealConnection>。每个RealConnection对应一个TCP连接。OkHttp会维护一个清理线程,定期遍历这个队列,把过期的连接移除并关闭。

来看一个连接池的清理逻辑简化版:

// 伪代码,展示核心逻辑
void cleanup() {
    long now = System.nanoTime();
    for (RealConnection connection : connections) {
        if (connection.isExpired(now)) {
            connections.remove(connection);
            connection.close();
        }
    }
    // 如果还有空闲连接,下次清理在30秒后
    // 如果没有空闲连接,清理线程可以休眠更久
}

嗯,这里要注意。如果你手动调用了connection.close(),那这个连接就不会被复用。所以,除非你确定不再需要这个连接,否则不要手动关闭。

20.5 三者的关系与选型建议

这三者的关系,我用一张图来展示:

Android网络框架关系图 App 应用层 Retrofit(门面) OkHttp(核心引擎) ConnectionPool(连接池) HttpURLConnection(底层) 注解驱动,动态代理 拦截器链,HTTP/2 复用TCP,减少握手 最底层,不推荐直接使用

选型建议其实很简单:

  • 新项目:直接用Retrofit + OkHttp。别犹豫,这是目前最成熟的方案。
  • 老项目维护:如果还在用HttpURLConnection,建议逐步迁移到OkHttp。别一次性改完,容易出问题。
  • 轻量级场景:比如只是偶尔发个请求,用OkHttp的OkHttpClient直接调用就行,没必要上Retrofit。
我的经验:Retrofit + OkHttp + Moshi + RxJava/Kotlin Coroutines,这套组合拳我用了5年,几乎没有遇到解决不了的问题。唯一要注意的是,Retrofit的Call对象一定要在合适的时机取消,否则会导致内存泄漏。

好了,关于Android网络框架的核心原理和连接池管理,就聊到这儿。记住,框架只是工具,理解它的原理,你才能用好它。


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