Android AWS s3 SDK下载抛出“Socket is closed”异常或提前终止。

4
我将尝试使用AWS Android SDK 1.0.4或1.0.3从S3下载对象。
以下是我的代码:
AmazonS3Client client = getConnection(userCredentials);
S3Object obj = client.getObject(workspaceName, objectName);
ObjectMetadata meta = obj.getObjectMetadata();
long size = meta.getContentLength();
logger.info("S3 object length: "+size);
InputStream is = new BufferedInputStream(obj.getObjectContent());
byte[] fragmentBytes = IOUtils.toByteArray(is);
logger.info("Read bytes: "+ fragmentBytes.length);

有时候这个方法可以正常工作,但大多数情况下会抛出"java.net.SocketException: Socket is closed"异常或仅读取对象的部分内容而没有报错。

请注意,对于IOUtils.toByteArray(...)BufferedInputStream应该不是必要的,无论是否存在都没有影响。

在调试器中逐行执行代码时,似乎不会出现此问题。

这在我的Android 2.3.3设备和3.2设备上发生。它在WiFi和3G上都会出现。

有任何想法吗?

附:对象大小约为250k

4个回答

5
问题在于客户端正在被垃圾回收。要解决这个问题,您需要保留对 S3Client 的显式引用。
该错误的流程如下:
1.程序获取 S3 客户端 2.程序从客户端获取 S3 对象。 3.程序从 s3Object 获取 inputStream,该对象通过客户端获取。 4.垃圾回收运行,并且由于没有对客户端的引用,它被垃圾回收,随机关闭 inputStream。
在我看来,这是亚马逊的一个非常糟糕的 bug。为什么 S3 对象输入流没有对客户端的引用对我来说是一个谜。
以下是 AWS 论坛上的线程:https://forums.aws.amazon.com/thread.jspa?threadID=83326

从上面链接的线程中不清楚我们是否需要在适当的s3Object上有引用。 - om-nom-nom
已经有一段时间了,但是从记忆中我认为最终确实是这个问题。 - Carsten

2
您可能已经解决了这个问题,但我找不到正确的答案,最终还是自己解决了。以下是解决方案:
getObjectContent方法的文档非常清楚地说明了关闭InputStream的方法:
S3ObjectInputStream com.amazonaws.services.s3.model.S3Object.getObjectContent()
获取包含此对象内容的输入流。调用者应尽快关闭此输入流,因为对象内容未缓存在内存中,并直接从Amazon S3流式传输。
您和我一样都犯了一个错误,即将InputStream(S3Object)用作关闭透明的InputStream。
我猜您需要从S3获取图像以在Android上使用,因此这里有一个返回Bitmap的示例方法。它适用于任何类型的输入。
private Bitmap getFromStore(String fileName) {
    AmazonS3Client client = new AmazonS3Client(new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY));

    S3Object o = client.getObject(BUCKET, fileName);
    InputStream is = o.getObjectContent();

    Bitmap image = null;

    try {   
        // Use the inputStream and close it after.
        image = BitmapFactory.decodeStream(is);
    } finally {
        try {
            is.close();
        } catch (IOException e) {
            Log.w(TAG, "Error trying to close image stream. ", e);
        }
    }

    return image;
}

为了避免意外情况,请记得在与UI不同的线程上执行此操作。

祝你好运!


谢谢,但我不认为这是我的问题所在。看着我的代码,它正确地关闭了输入流,错误甚至可能发生在第一次下载(在任何流被关闭之前)。我有一个解决方法,但它非常粗糙:当它发生异常时捕获并重新开始下载文件。最终它会成功。 - Carsten

1

看起来你并不孤单。 其中一个回答提到了你的问题。

如何将S3对象写入文件?

我会尝试像上面问题中的答案那样循环内容。缓冲是好的,但是...

你也可以尝试不使用IOUtil,在Java中还有其他选择。


谢谢,但是链接的帖子是关于一个稍微不同的问题,它的解决方案在我的情况下没有帮助。IOUtil或多或少地做了与答案中的代码相同的事情。唯一的区别是缓冲区大小和线程中断处理。线程中断在我的情况下不是问题,缓冲区大小(1k vs 4k)也不应该有任何影响。 - Carsten

0

无需不断重新初始化s3。

在您的onCreate中,调用初始化s3Objects3Client的函数。

然后在您的asynctask中使用该调用。这样,当进行while读取时,您的s3Client将保持相同的数据并且永远不会关闭与s3的套接字连接。

S3Client s3Client;
S3Object s3Object;

onCreate() {
     s3Client = new AmazonS3Client(new BasicSessionCredentials(
                  Constants.ACCESS_KEY_ID, Constants.SECRET_KEY, Constants.TOKEN
                ));
     object = new S3Object();
}

doinbackground() {
     object = s3Client.getObject(new GetObjectRequest(Constants.getBucket(), id +".png"));
}

/****** add this in your onCreate ******/
AmazonS3Client client = getConnection(userCredentials);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接