【问题标题】:Android: Too many open files errorAndroid:打开文件过多错误
【发布时间】:2013-06-05 01:08:41
【问题描述】:

我有以下每 3 秒运行一次的操作。
基本上,它每 3 秒从服务器下载一个文件并将其保存到本地文件中。
下面的代码完成了一段时间的工作。

public class DownloadTask extends AsyncTask<String, Void, String>{

    @Override
    protected String doInBackground(String... params) {
        downloadCommandFile( eventUrl);
        return null;
    }


}

private void downloadCommandFile(String dlUrl){
    int count;
    try {
        URL url = new URL( dlUrl );
        NetUtils.trustAllHosts();
        HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
        con.setDoInput(true);
        con.setDoOutput(true);
        con.connect();
        int fileSize = con.getContentLength();
        Log.d(TAG, "Download file size = " + fileSize );
        InputStream is = url.openStream();
        String dir = Environment.getExternalStorageDirectory() + Utils.DL_DIRECTORY;
        File file = new File( dir );
        if( !file.exists() ){
            file.mkdir();
        }

        FileOutputStream fos = new FileOutputStream(file + Utils.DL_FILE);
        byte data[] = new byte[1024];
        long total = 0;

        while( (count = is.read(data)) != -1 ){
            total += count;
            fos.write(data, 0, count);
        }

        is.close();
        fos.close();
        con.disconnect(); // close connection


    } catch (Exception e) {
        Log.e(TAG, "DOWNLOAD ERROR = " + e.toString() );
    }

}

一切正常,但如果我让它运行 5 到 10 分钟,我会收到以下错误。

06-04 19:40:40.872: E/NativeCrypto(6320): AppData::create pipe(2) 失败:打开的文件太多 06-04 19:40:40.892: E/NativeCrypto(6320): AppData::create pipe(2) 失败:打开的文件太多 06-04 19:40:40.892:E/EventService(6320):下载错误 = javax.net.ssl.SSLException: 无法创建应用程序数据

过去两天我一直在做一些研究。
有人建议他们打开了许多连接,例如https://stackoverflow.com/a/13990490/1503155,但我仍然无法弄清楚问题所在。
有什么想法可能导致问题吗?
提前致谢。

【问题讨论】:

  • 您最终找到问题所在了吗?

标签: android httpurlconnection android-file


【解决方案1】:

我认为您收到此错误是因为您同时打开了太多文件,这意味着您有太多异步任务同时运行(每个异步任务打开一个文件),如果您这么说是有道理的你每 3 秒运行一个新的。

您应该尝试使用线程池执行器来限制同时运行的异步任务的数量。

【讨论】:

  • 感谢您提供宝贵的信息。知道如何限制同时运行的异步任务的数量吗?
  • 如果你不支持Android 2.x,你可以通过提供你自己的线程池执行器来执行你的异步任务:Executor e = Executors.newFixedThreadPool(3),然后运行你的异步任务:executeOnExecutor(e);
  • 作为替代选项,我希望您在一定程度上管理您的异步任务进度(通过检查通过 onPostExecute() 完成的任务)并手动控制当前正在运行的异步任务的数量。
  • 谢谢!现在我通过添加 StrictMode.ThreadPolicy 来允许所有测试,而不使用 Async 任务。如果可行,我会尝试您的建议。顺便说一下应用只支持4.xxx
  • AsyncTask 已经在线程数有限的作业队列上运行。所以导致这个错误似乎不是很相关。
【解决方案2】:

尝试改用OkHttp

您的问题不在于线程过多,尽管这正是导致您的问题浮出水面的原因。

正如 cmets 中提到的 @stdout,AsyncTask 已经在一个线程池中运行,该线程池在所有 AsyncTask 之间是通用的和共享的,除非您另外指定。这里的问题是文件描述符没有正确和及时地关闭。

问题是您的文件描述符没有足够快地关闭。

我为此苦苦挣扎了几个小时/几天/几周,做你想做的一切设置小的读取/连接超时并使用finally 块来关闭连接、输入流、输出流等。但我们从未找到工作解决方案。 HttpsUrlConnection 似乎在某些方面存在缺陷。

所以我们尝试将OkHttp 作为HttpsUrlConnection 的替代品,瞧!它开箱即用。

所以,如果您正在为此苦苦挣扎并且很难修复它,我建议您也尝试使用 OkHttp。

以下是基础知识:

添加 Maven 依赖项后,您可以执行以下操作来下载文件:

OkHttpClient okHttpClient = new OkHttpClient.Builder().build();

OutputStream output = null;

try {
  Request request   = new Request.Builder().url( download_url ).build();
  Response response = okHttpClient.newCall( request ).execute();

  if ( !response.isSuccessful() ) {
    throw new FileNotFoundException();
  }

  output = new FileOutputStream( output_path );

  output.write( response.body().bytes() );
}
finally {
  // Ensure streams are closed, even if there's an exception.
  if ( output != null ) output.flush();
  if ( output != null ) output.close();
}

切换到 OkHttp 立即修复了我们泄露的文件描述符问题,因此如果您遇到困难,值得尝试,即使以添加另一个库依赖项为代价。

【讨论】:

  • 这没有帮助。看起来Android会在一段时间后从连接中释放管道和套接字。唯一对我有用的解决方案是使用 8 个线程的 ThreadPool。
【解决方案3】:

我不得不一次下载数百个文件并遇到错误。

您可以使用以下命令检查打开的描述符:

adb shell ps

在列表中找到您的应用程序 PID 并使用另一个命令:

adb shell run-as YOUR_PACKAGE_NAME ls -l  /proc/YOUR_PID/fd

我在通常的启动中看到大约 150 个打开的描述符。下载文件时有 700 多个。他们的数量只会在几分钟后减少,看起来 Android 在后台释放了他们,而不是当你可以在流中 close 时。

唯一可行的解​​决方案是使用自定义线程池来限制并发。这是 Kotlin 代码:

private val downloadDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher()

private suspend fun downloadFile(sourceUrl: String, destPath: String, progressBlock: suspend (Long) -> Unit) = withContext(downloadDispatcher) {
    val url = URL(sourceUrl)
    url.openConnection().apply { connect() }
    url.openStream().use { input ->
        FileOutputStream(File(destPath)).use { output ->
            val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
            var bytesRead = input.read(buffer)
            var bytesCopied = 0L
            while (bytesRead >= 0) {
                if (!coroutineContext.isActive) break
                output.write(buffer, 0, bytesRead)
                bytesCopied += bytesRead
                progressBlock(bytesCopied)
                bytesRead = input.read(buffer)
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 2016-02-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多