//xxxxx为具体的网络地址 URL url = new URL( "xxxxx" ); connection = (HttpURLConnection) url . openConnection(); connection . connect(); //进行一些操作 ... ... ... ... ... } catch (IOException e) { e . printStackTrace(); } finally { if (connection != null ) { connection . disconnect();

最近在8.0的手机里跑类似上述代码时,突然发现会概率性地打印类似如下的log:

A connection to xxxxxx was leaked. Did you forget to close a response body?

仔细check了一下代码,发现connection用完后,已经disconnect了,
怎么还会打印这种让人觉得不太舒服的代码?

为了解决这个问题,在国内外的网站上找了很久,
但都没能找到真正可行的解决方案。

无奈之下,只好硬撸了一边源码,总算是找到了问题的原因和一个解决方案。
因此,在本片博客中记录一下比较重要的地方。

Android的源码中,我们知道URL的openConnection函数的底层实现依赖于OkHttp库,
对于这部分的流程,我之后专门写一篇文档记录一下。

现在我们需要知道的是:
OkHttp库中的创建的Http链接为RealConnection对象。
为了达到复用的效果,OkHttp专门创建了ConnectionPool对象来管理所有的RealConnection。
这有点像线程池会管理所有的线程一样。

当我们创建一个新的RealConnection时,会调用ConnectionPool的put函数:

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (connections.isEmpty()) {
        //执行一个cleanupRunnable
        executor.execute(cleanupRunnable);
    //将新的connection加入池子中
    connections.add(connection);

现在,我们来看看cleanupRunnable会干些啥:

private Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
        while (true) {
            //容易看出,其实就是周期性地执行cleanup函数
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
                long waitMillis = waitNanos / 1000000L;
                waitNanos -= (waitMillis * 1000000L);
                synchronized (ConnectionPool.this) {
                    try {
                        ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                    } catch (InterruptedException ignored) {

cleanup函数的真面目如下:

long cleanup(long now) {
    //记录在使用的connection
    int inUseConnectionCount = 0;
    //记录空闲的connection
    int idleConnectionCount = 0;
    //记录空闲时间最长的connection
    RealConnection longestIdleConnection = null;
    //记录最长的空闲时间
    long longestIdleDurationNs = Long.MIN_VALUE;
    synchronized (this) {
    for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
            // If the connection is in use, keep searching.
            // 轮询每一个RealConnection
            if (pruneAndGetAllocationCount(connection, now) > 0) {
                inUseConnectionCount++;
                continue;
            idleConnectionCount++;
            //找到空闲时间最长的RealConnection
            long idleDurationNs = now - connection.idleAtNanos;
            if (idleDurationNs > longestIdleDurationNs) {
                longestIdleDurationNs = idleDurationNs;
                longestIdleConnection = connection;
        //空闲时间超过限制或空闲connection数量超过限制,则移除空闲时间最长的connection
        if (longestIdleDurationNs >= this.keepAliveDurationNs
                || idleConnectionCount > this.maxIdleConnections) {
            // We've found a connection to evict. Remove it from the list, then close it below (outside
            // of the synchronized block).
            connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {
            // A connection will be ready to evict soon.
            //返回下一次执行cleanup需等待的时间
            return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {
            // All connections are in use. It'll be at least the keep alive duration 'til we run again.
            // 返回最大可等待时间
            return keepAliveDurationNs;
         } else {
            // No connections, idle or in use.
            return -1;
    //特意放到同步锁的外面释放,减少持锁时间
    Util.closeQuietly(longestIdleConnection.getSocket());
    return 0;

通过cleanup函数,不难看出该函数主要的目的就是:
逐步清理connectionPool中已经空闲的RealConnection。

现在唯一的疑点就是上文中的pruneAndGetAllocationCount函数了:

* Prunes any leaked allocations and then returns the number of remaining live allocations on * {@code connection}. Allocations are leaked if the connection is tracking them but the * application code has abandoned them. Leak detection is imprecise and relies on garbage * collection. private int pruneAndGetAllocationCount(RealConnection connection, long now) { //获取使用该RealConnection的对象的引用 List<Reference<StreamAllocation>> references = connection.allocations; for (int i = 0; i < references.size(); ) { Reference<StreamAllocation> reference = references.get(i); //引用不为null,说明仍有java对象持有它 if (reference.get() != null) { continue; //没有持有它的对象,说明上层持有RealConnection已经被回收了 // We've discovered a leaked allocation. This is an application bug. Internal.logger.warning("A connection to " + connection.getRoute().getAddress().url() + " was leaked. Did you forget to close a response body?"); //移除引用 references.remove(i); connection.noNewStreams = true; // If this was the last allocation, the connection is eligible for immediate eviction. //没有任何引用时, 标记为idle,等待被cleanup if (references.isEmpty()) { connection.idleAtNanos = now - keepAliveDurationNs; return 0; return references.size();

从上面的代码可以看出,pruneAndGetAllocationCount发现没有被引用的RealConnection时,
就会打印上文提到的leaked log。

个人猜测,如果开头的代码执行完毕后,GC先回收HttpURLConnection(非直接持有)等持有RealConnection的对象,后回收RealConnection。
且在回收HttpURLConnection后,回收RealConnection前,刚好执行了pruneAndGetAllocationCount,就可能会打印这种log。
这也是注释中提到的,pruneAndGetAllocationCount依赖于GC。

不过从代码来看,这并没有什么问题,Android系统仍会回收这些资源。

在文章开头的代码中,最后调用的HttpURLConnection的disconnect函数。
该函数仅会调用StreamAllocation的cancel函数,且最终调用到RealConnection的cancel函数:

public void cancel() {
    // Close the raw socket so we don't end up doing synchronous I/O.
    Util.closeQuietly(rawSocket);

可以看出,该方法仅关闭了socket,并没有移除引用,不会解决我们遇到的问题。

经过不断地尝试和阅读源码,我发现利用下述方式可以解决这个问题:

HttpURLConnection connection = null;
try {
    //xxxxx为具体的网络地址
    URL url = new URL("xxxxx");
    connection = (HttpURLConnection) url.openConnection();
    connection.connect();
    //进行一些操作
    ...............
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (connection != null) {
    try {
        //主动关闭inputStream
        //这里不需要进行判空操作
        connection.getInputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        connection.disconnect();

当我们主动关闭HttpURLConnection的inputStream时,将会先后调用到StreamAllocation的noNewStreams和streamFinished函数:

public void noNewStreams() {
    deallocate(true, false, false);
public void streamFinished(HttpStream stream) {
    synchronized (connectionPool) {
        if (stream == null || stream != this.stream) {
            throw new IllegalStateException("expected " + this.stream + " but was " + stream);
    //调用deallocate
    deallocate(false, false, true);
//连续调用两次,第1、3个参数分别为true
private void deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    RealConnection connectionToClose = null;
    synchronized (connectionPool) {
        if (streamFinished) {
            //第二次,stream置为null
            this.stream = null;
        if (released) {
            this.released = true;
        if (connection != null) {
            if (noNewStreams) {
                //第一次,noNewStreams置为true
                connection.noNewStreams = true;
            //stream此时为null, 其它两个条件满足一个
            if (this.stream == null && (this.released || connection.noNewStreams)) {
                //就可以执行release函数
                release(connection);
                if (connection.streamCount > 0) {
                    routeSelector = null;
                //idle的RealConnection可以在下文被关闭
                if (connection.allocations.isEmpty()) {
                    connection.idleAtNanos = System.nanoTime();
                    if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
                        connectionToClose = connection;
                connection = null;
    if (connectionToClose != null) {
        Util.closeQuietly(connectionToClose.getSocket());
//最后看看release函数
private void release(RealConnection connection) {
    for (int i = 0, size = connection.allocations.size(); i < size; i++) {
        Reference<StreamAllocation> reference = connection.allocations.get(i);
        //移除该StreamAllocation对应的引用
        //解决我们遇到的问题
        if (reference.get() == this) {
            connection.allocations.remove(i);
            return;
    throw new IllegalStateException();

到此,我们终于知道出现该问题的原因及对应的解决方案了。

上述代码省略了HttpURLConnection及底层OkHttp的许多流程,
仅给出了重要的部分,后续我会专门写一篇博客来补充分析这部分代码。

这个问题说实话,个人感觉并不是很重要,
但想真正明白原理,还是需要细致阅读源码的。
一旦真正搞懂,确实有点GAI爷歌里的感觉:
一往无前虎山行,拨开云雾见光明。

Android-杰里-OkHttp Jerry只是AndroidOKHTTP的实用工具,它具有FLUENT APIS 。 从ok-http 3开始,square已更改了许多API,因此我仍在开发新版本的Jerry。 我待会儿给一个样品。 这很简单! 我也在我的书房里给了一个枫糖。 RequestParams params = new RequestParams(); params.put("key", "value"); try { params.put("fileKey",new File("filepath")); } catch (FileNotFoundException e) { e.printStackTrace(); ​ 安卓开发网络请求可谓是安卓开发的灵魂,如果你不会网络请求,那么你开发的应用软件就是一具没有灵魂的枯骨。​ 在安卓开发中进行网络请求和java中的网络请求有异曲同工之妙,但是安卓软件毕竟的安装在我们手机上的,而平常的应用软件下载后会要求你给与权限,否则就没办法使用,网络请求也需要对应的权限,否则就没法进行联网操作。​ 首先在AndroidManifest.xml文件中添加网络请求权限。要在manifest标签内,application标签外添加​ 新建java文件,创建静态方法,返回请求后的结果。 最近在一个程序中使用okhttp调用http接口。开始时一切正常,但是测试运行一段时间后,okhttp就会报告recv失败。同时在调用端机器上,netstat显示很多套接字是TIMEWAIT状态。原来每次调用接口,okhttp都建立了一个新连接。而被调用的服务器在连接超过一定数量后会拒绝服务。 最初的想法是用连接池降低连接数。 OkHttpClient httpClient = new OkHttpClient.Builder() .connectionPool(new Connection 下面是报错内容,使用okhttp,的时候报错的.这个不关闭,好像时间久了会报内存溢出错误. W/OkHttp: A connection to http://172.19.128.64:8061/ was leaked. Did you forget to close a response body? 比如,下面这样解决: //4.同步人脸库操作 private void faceAsync() { //1.这里去同步照片到人脸库 //String ... /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ RealConnection get(Address address, StreamAllocation streamAllocation) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (con 概述java的Closeable和Cloneable两个接口,一字之差,但是要完成的功能却基本没有什么联系。最大的相同点在于,这两个就是java基本类库有特殊处理的接口,不按规则来,加班... 但使用中遇到问题: 1.在公司开发团队开发,有可能会遇到不能连外网的情况,使我们无法下载Maven构件 2.公司的开发团队比较大,下载的Maven构件也比较多,每人都需要连接外网的中央仓库去下载构件,外网带宽占用很大,下载速度很慢,影响工作效率。 3.一些第三方构件数量多,各项目使用比 这个类主要是用来配置okhttp这个框架的,通俗一点讲就是这个类是管理这个框架的各种设置的。Call 类的工厂,通过 OkHttpClient 才能得到 Call 对象。只要掌握 http 请求的原理,使用起 okhttp 来也就不是什么问题了。首先 OkHttpClient 是用来设置关于请求工具的一些参数的,比如超时时间、是否缓存等等。Call 对象是发起 Http 请求的对象,通过 Call 对象来发起请求。 在调用一个耗时较长的接口时,我们往往需要显示一个加载框,以便让用户知道我们的 APP 正在工作而不是卡死。 一般的做法,是这样实现: showLoading(); // 在请求开始之前显示加载框 http.async('/api/...') .setOnComplete(state -> { // 在请求结束(成功|失败|异常)之后关闭加载框 hideLoading();