【问题标题】:How do I prevent OutOfMemory Error when allocating new byte[] for File transfer?为文件传输分配新字节 [] 时,如何防止 OutOfMemory 错误?
【发布时间】:2015-08-18 00:03:08
【问题描述】:

我正在尝试读取文件以便通过 Wear 应用程序发送它,但我得到了 OutOfMemory 异常。

File file = new File(filePath);
final FileInputStream fileInputStream = new FileInputStream(file);
byte fileContent[] = new byte[(int) file.length()]; //***BOMBS HERE***
fileInputStream.read(fileContent);
fileInputStream.close();
Asset programDataAsset = Asset.createFromBytes(fileContent);

异常说明如下:

java.lang.OutOfMemoryError: Failed to allocate a 31150467 byte allocation with 2097152 free bytes and 16MB until OOM
       at com.rithmio.coach.wear.TransferService.sendAssetToMobile(TransferService.java:110)
       at com.rithmio.coach.wear.TransferService.onHandleIntent(TransferService.java:84)
       at com.rithmio.coach.wear.TransferService$1.run(TransferService.java:60)
       at java.lang.Thread.run(Thread.java:818)

【问题讨论】:

  • 解决方法是按固定大小的块进行读写。
  • 如果他只是增加堆呢?他并不是要发送 1 GB 的数据,30 MB 也不算多。
  • 我通常发现,如果您的程序需要增加 jvm 内存分配,您可能最好使用其他解决方案。毕竟,这是一个便携性问题——尤其是对于可穿戴设备。谁知道消费者真正拥有多少内存?
  • 更不用说这可能在堆空间有限的移动设备上

标签: java out-of-memory wear-os android-wear-data-api


【解决方案1】:

让我们使用ChannelApi 解决这个问题。我们也不必像我在评论中所说的那样担心块。谷歌提前考虑并为发送文件提供了一种方便的方法。 public abstract PendingResult sendFile (GoogleApiClient client, Uri uri)

private String pickBestNodeId(List<Node> nodes) {
    String bestNodeId = null;
    // Find a nearby node or pick one arbitrarily
    for (Node node : nodes) {
        if (node.isNearby()) {
            return node.getId();
         }
         bestNodeId = node.getId();
    }
    return bestNodeId;
}

public boolean send(File f) {
    GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
    // Request access only to the Wearable API
        .addApi(Wearable.API)
        .build();
    mGoogleApiClient.blockingConnect();
    Channel channel = openChannel(mGoogleApiClient, pickBestNodeId(Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await()), "/your/arbitrary/application/specific/path/").await(); //NOTE THE PATH IS ARBITRARY, IT CAN BE WHATEVER YOU WANT. IT DOES NOT POINT TO ANYTHING, AND CAN EVEN BE LEFT WITH WHAT I HAVE.
    boolean didSend = channel.sendFile(mGoogleApiClient, f.toURI()).await().isSuccess();
    channel.close(mGoogleApiClient);
    mGoogleApiClient.disconnect();
    return didSend;
}

注意:这使用阻塞方法,不应在 ui 线程中运行。

如果您希望调用是非阻塞的,您应该省略我对PendingResult.await() 的使用,并将结果回调设置为PendingResult。回调可以通过setResultCallback(ResultCallback callback)设置。

【讨论】:

    【解决方案2】:

    我建议您使用ByteArrayOutputStream。然后读取InputStream,使用以下代码:

    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    int b;
    while((b = input_stream.read()) != -1) {
        stream.write(b);
    }
    

    然后,您可以使用 for(byte b : stream.toByteArray()) {...} 遍历字节

    【讨论】:

      【解决方案3】:

      您应该使用缓冲区而不是尝试将整个文件存储为数组。将文件的某些部分读入字节数组,然后将它们写出到流中。这也将防止内存较少的设备上出现 OOM 错误。

      int fileLength = (int) file.length();
      while(fileLength > 0){
          byte[] arr;
          if(fileLength > 1024){
              arr = new byte[1024];
          }else{
              arr = new byte[fileLength];
          fileInputStream.read(arr);
          // write to outputStream/file transfer
          fileLength -= arr.length;
      }
      

      【讨论】:

      • 你有例子吗?
      • 我已经编辑了我的答案。更具体地说,您需要提供有关文件传输的更多详细信息
      • 此代码不是正确的读取循环。您不应该尝试猜测文件长度。它可能会在您阅读时发生变化。您必须在返回值为正时读取,并且必须将该返回值存储到变量中。否则无法使用正确读取的数据。目前,您假设每次读取都返回 1024 个字节。它没有指定这样做,除非文件长度是 1024 的倍数,否则它肯定不会在最后一次这样做。
      • 处理程序将在文件上打开,因此不应更改。但如果这样做,它会导致与 OP 的原始代码相同的问题,因此没有理由假设文件会更改。
      • 重点是当前编写的复制循环不会遇到这个问题。您的新代码仍然受此影响,它仍然受我提到的第二个问题的影响,并在整个地方喷洒字节数组。正确的方法已经在这里发布了数百次。它是四行代码加上两个声明。
      猜你喜欢
      • 1970-01-01
      • 2016-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多