笔记--解决Android使用retrofit2 OkHttp3短时间内大量通讯提示Socket Failed:EMFILE

学更好的别人,

做更好的自己。

——《微卡智享》

669e67e94a91539c0e3419b71231e39f.png

本文长度为2359,预计阅读5分钟

前言

Androidd在做Http请求,使用Retrofit2是一个比较方便的事,这个我一直也用了蛮久了,只不过最近在项目中突然遇到了问题,也是通过分析测试后解决,顺便做一下笔记。

5d3ad6032e4ce2461548d085f96009cd.png

事件回顾

53a5d692a795057635efd7fd23e3fa48.png

微卡智享

报错图片

e0b47205a45bc8c80ae4fa26a9cfbfc0.png

当时程序上是做盘点的业务流程,点击盘点的按钮时会同时有35个Http的请求,实际操作时是不停地地点击盘点,多次后出现的这个情况,具体的定位分析里这个操作除了Http请求返回数据后UI显示,基本没啥别的业务逻辑操作,所以问题也比较好定位,应该是同一时间多次请求Http造成的原因。

请求源码

package networking.retrofit;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;


import okhttp3.ConnectionPool;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;




/**
 * 获取Retrofit类用于Http通信
 */


public class retrofitAPIManager<T> {
    //基本URL地址
    public static String SERVER_URL = "url";
    //Cookies类型  0-每次注册时登记   1-按每次访问的URL登记
    public static int Cookiestype = 0;
    //Cookies类型如果为每次注册登记时用到检索关键前
    public static String Cookiecontains = "login";
    //Cookies类型如果为每次注册登记时用到Key
    public static String CookiesKey = "SumSoft";


    public T provideClientApi(Class<T> tClass) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(SERVER_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(genericClient())
                .build();
        return retrofit.create(tClass);
    }




    //获取OkHttpClient
    public static OkHttpClient genericClient() {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .cookieJar(new CookieJar() {
                    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();


                    @Override
                    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                        //根据类型来判断存入的Cookies用于后面读取用
                        if (Cookiestype == 0) {
                            //判断url里面是注册的更新Key
                            if (url.toString().contains(Cookiecontains)) {
                                cookieStore.put(CookiesKey, cookies);
                            }
                        } else {
                            cookieStore.put(url.toString(), cookies);
                        }


                    }


                    @Override
                    public List<Cookie> loadForRequest(HttpUrl url) {
                        List<Cookie> cookies;
                        if (Cookiestype == 0) {
                            cookies = cookieStore.get(CookiesKey);
                        } else {
                            cookies = cookieStore.get(url.toString());
                        }
                        return cookies != null ? cookies : new ArrayList<Cookie>();
                    }
                })
                .build();


        return httpClient;
    }
}

其实这个封装好的源码一直用了很久了,都正常使用,一直使用的Retrofit2+OkHttp3进行网络请求,所以没再深入研究,既然出现问题了,那就研究下有没有相关遇到这个问题的,网上搜了下资料:

37f33919e767903994a3b94fb48ac0bc.png

165955949e7ab4d9858fc729a075bc6c.png

从OkHttp的Github源码是可以看到,上面说了,同一台设备在短时间内发起了200次http的请求,就会报这个错误了。

那我们就从OkHttp中来看看分析下,上面代码中使用genericClient创建的OkHttp,用到了new OkHttpClient.Builder(),那我们就分析下这里怎么处理的,OkHttpClient的ConnectionPool网络请求的线程池,在OkHttpClient源码中:

OkHttpClient.java中

e037d67a220194307e604306cc09b4d6.png

/**
     * Sets the connection pool used to recycle HTTP and HTTPS connections.
     *
     * <p>If unset, a new connection pool will be used.
     */
    public Builder connectionPool(ConnectionPool connectionPool) {
      if (connectionPool == null) throw new NullPointerException("connectionPool == null");
      this.connectionPool = connectionPool;
      return this;
    }

上面可以看到创建的连接线程池,那我们再找这个ConnectionPool。

ConnectionPool.java

573fe787e50fe74b98e6fcb088d65798.png

/**
   * Create a new connection pool with tuning parameters appropriate for a single-user application.
   * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
   * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
   */
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }


  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);


    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }

ConnectionPool中的构造方法里默认最大线程空闲数是5,keepAlive时间为5分钟。也就是发起一次网络连接后,5分钟内不会断开连接。

这样像刚才我们多次点击盘点的时,短时间内链接不会断开,很快就直接200个了,所以问题应该就出在这里,修改了ConnectPool的连接参数就可以解决这个问题。

解决方法

在创建OkHttpClient.Builder中加入ConnectPool的设置。所以在原来的创建代码中加入了connectPool设置其连接时间,保活时间,我这里设置了500毫秒的时间,如下图

82e9d54d1919ecfbc9a22c644345fa88.png

//获取OkHttpClient
    public static OkHttpClient genericClient() {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .cookieJar(new CookieJar() {
                    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();


                    @Override
                    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                        //根据类型来判断存入的Cookies用于后面读取用
                        if (Cookiestype == 0) {
                            //判断url里面是注册的更新Key
                            if (url.toString().contains(Cookiecontains)) {
                                cookieStore.put(CookiesKey, cookies);
                            }
                        } else {
                            cookieStore.put(url.toString(), cookies);
                        }


                    }


                    @Override
                    public List<Cookie> loadForRequest(HttpUrl url) {
                        List<Cookie> cookies;
                        if (Cookiestype == 0) {
                            cookies = cookieStore.get(CookiesKey);
                        } else {
                            cookies = cookieStore.get(url.toString());
                        }
                        return cookies != null ? cookies : new ArrayList<Cookie>();
                    }
                })
                //设置okhttp连接池保活时间,源码中默认是5分钟,这里改为500毫秒。
                .connectionPool(new ConnectionPool(5,
                        500, TimeUnit.MILLISECONDS))
                .build();


        return httpClient;
    }

完整代码

package networking.retrofit;




import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;


import okhttp3.ConnectionPool;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;




/**
 * Created by Administrator on 2016-12-06.
 * 获取Retrofit类用于Http通信
 */


public class retrofitAPIManager<T> {
    //基本URL地址
    public static String SERVER_URL = "url";
    //Cookies类型  0-每次注册时登记   1-按每次访问的URL登记
    public static int Cookiestype = 0;
    //Cookies类型如果为每次注册登记时用到检索关键前
    public static String Cookiecontains = "login";
    //Cookies类型如果为每次注册登记时用到Key
    public static String CookiesKey = "SumSoft";


    public T provideClientApi(Class<T> tClass) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(SERVER_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(genericClient())
                .build();
        return retrofit.create(tClass);
    }




    //获取OkHttpClient
    public static OkHttpClient genericClient() {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .cookieJar(new CookieJar() {
                    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();


                    @Override
                    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                        //根据类型来判断存入的Cookies用于后面读取用
                        if (Cookiestype == 0) {
                            //判断url里面是注册的更新Key
                            if (url.toString().contains(Cookiecontains)) {
                                cookieStore.put(CookiesKey, cookies);
                            }
                        } else {
                            cookieStore.put(url.toString(), cookies);
                        }


                    }


                    @Override
                    public List<Cookie> loadForRequest(HttpUrl url) {
                        List<Cookie> cookies;
                        if (Cookiestype == 0) {
                            cookies = cookieStore.get(CookiesKey);
                        } else {
                            cookies = cookieStore.get(url.toString());
                        }
                        return cookies != null ? cookies : new ArrayList<Cookie>();
                    }
                })
                //设置okhttp连接池保活时间,源码中默认是5分钟,这里改为500毫秒。
                .connectionPool(new ConnectionPool(5,
                        500, TimeUnit.MILLISECONDS))
                .build();


        return httpClient;
    }
}

444ccc0db7a186445047e642b83e47c5.png

be431b8721377e194695cbb00641f722.png

往期精彩回顾

d26a3e31b0075599548442dfb354d52e.png

笔记---Linux安装OpenCV及VSCode的配置编译


1d60c43231ae5e5b33712f09acbfed73.png

AI自动还原OpenCV制作的九宫格拼图游戏(附源码)


386c9417e96730cc41d5d9fd87d2c9a1.png

C++ OpenCV制作九宫格拼图游戏


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值