9

我有以下操作,每 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管道(2)失败:打开的文件太多 06-04 19:40:40.892:E/EventService(6320):下载错误 = javax.net.ssl.SSLException:无法创建应用程序数据

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

4

3 回答 3

9

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

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

于 2013-06-05T01:20:19.527 回答
1

尝试使用OkHttp代替。

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

正如评论中提到的@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 立即修复了我们泄露的文件描述符问题,因此如果您遇到困难,值得尝试,即使以添加另一个库依赖项为代价。

于 2019-10-05T16:27:06.020 回答
0

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

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

adb shell ps

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

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

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

唯一可行的解​​决方案是使用自定义 ThreadPool 来限制并发性。这是 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)
            }
        }
    }
}
于 2021-12-10T13:17:57.423 回答