【问题标题】:okhttp3 too many file descriptors issueokhttp3 文件描述符过多问题
【发布时间】:2017-02-26 21:47:54
【问题描述】:

我有一个下载图像的图像“管理器”。 以前我为此使用了毕加索库,如下所示

class DownloadImage implements Runnable {

    String url;
    Context context;

    public DownloadImage(String url, Context context) {
        this.url = url;
        this.context = context;
    }

    @Override
    public void run() {
        try {
            String hash = Utilities.getSha1Hex(url);
            FileOutputStream fos = context.openFileOutput(hash, Context.MODE_PRIVATE);
            Bitmap bitmap = Picasso.with(context)
                    .load(url)
                    .resize(1024, 0) // Height 0 to ensure the image is scaled with respect to width - http://stackoverflow.com/a/26782046/1360853
                    .onlyScaleDown()
                    .memoryPolicy(MemoryPolicy.NO_CACHE)
                    .get();

            // Writing the bitmap to the output stream
            bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
            fos.close();
            bitmap.recycle();
        } catch (IOException e) {
            Timber.e(e, "For url %s", url);
        } catch (OutOfMemoryError e) {
            Timber.e(e, "out of memory for url %s", url);
        }
    }
}

但这会创建一个 Bitmap 对象,它不仅会消耗大量内存,而且速度也相当慢且不必要。

我已将此 Runnable 修改为使用 okhttp3

class DownloadImage implements Runnable {

    String url;
    Context context;

    public DownloadImage(String url, Context context) {
        this.url = url;
        this.context = context;
    }

    @Override
    public void run() {
        try {
            String hash = Utilities.getSha1Hex(url);
            final FileOutputStream fos = context.openFileOutput(hash, Context.MODE_PRIVATE);
            Request request = new Request.Builder().url(url).build();
            okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    try {
                        fos.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    Sink sink = null;
                    BufferedSource source = null;
                    try {
                        source = response.body().source();
                        sink = Okio.sink(fos);
                        source.readAll(sink);
                    } catch (Exception e) {
                        Timber.e(e, "Downloading an image went wrong");
                    } finally {
                        if (source != null) {
                            source.close();
                        }
                        if (sink != null) {
                            sink.close();
                        }
                        fos.close();
                        okHttpClient.connectionPool().evictAll(); // For testing
                    }
                }
            });
        } catch (IOException e) {
            Timber.e(e, "For url %s", url);
        }
    }
}

虽然这种方法比以前快很多,但对于大量图像,我得到A/libc: FORTIFY_SOURCE: FD_SET: file descriptor >= FD_SETSIZE. Calling abort(). 后跟一个微转储,这意味着我打开了太多文件描述符。

为了测试,我添加了okHttpClient.connectionPool().evictAll(); // For testing 行,但这不起作用。 我还尝试在构建okHttpClient 时设置builder.connectionPool(new ConnectionPool(4, 500, TimeUnit.MILLISECONDS));,但这也没有任何作用。 我也知道https://github.com/square/okhttp/issues/2636

我似乎关闭了所有流/接收器/源,所以这里发生了什么?

使用 execute 函数将可运行对象添加到 ThreadPoolExecutor,该函数的创建方式如下:

// Sets the amount of time an idle thread waits before terminating
private static final int KEEP_ALIVE_TIME = 500;
// Sets the Time Unit to milliseconds
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.MILLISECONDS;
private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

// A queue of Runnables
private final BlockingQueue<Runnable> mDecodeWorkQueue;
private OkHttpClient okHttpClient;

ThreadPoolExecutor mDecodeThreadPool;

public ImageManager() {
    // Instantiates the queue of Runnables as a LinkedBlockingQueue
    mDecodeWorkQueue = new LinkedBlockingQueue<Runnable>();

    // Creates a thread pool manager
    mDecodeThreadPool = new ThreadPoolExecutor(
            NUMBER_OF_CORES,       // Initial pool size
            NUMBER_OF_CORES,       // Max pool size
            KEEP_ALIVE_TIME,
            KEEP_ALIVE_TIME_UNIT,
            mDecodeWorkQueue);

}

【问题讨论】:

  • 我们在进行多线程网络时遇到了这个问题,并且在此期间遇到了很多 400 响应(有意)。修复它的方法是确保response 在遇到不成功的请求时被关闭,方法是执行response.close()。这必须立即释放套接字,而不是等待某种可能太长的超时并最终在它们有机会自动释放之前使用所有文件描述符。在这里阅读更多:square.github.io/okhttp/4.x/okhttp/okhttp3/-response-body

标签: android okhttp file-descriptor okhttp3


【解决方案1】:

通过在 OnResponse 正文中创建和使用 FileOutputStream 解决了这个问题,以便在完成请求时它不会打开。

【讨论】:

  • 您能否分享解决问题的代码更改的差异,以使上述解决方案更清晰?
猜你喜欢
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
  • 2018-08-15
  • 1970-01-01
  • 2010-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多