3

I am trying to read a file in order to send it via Wear App, but I get an OutOfMemory exception.

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);

The exception states the following:

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)
8
  • 1
    possible duplicate of Sending big file using FileInputStream/ObjectOutputStream Commented Aug 18, 2015 at 0:05
  • 4
    The solution is to read and write in chunks of a fixed size. Commented Aug 18, 2015 at 0:08
  • What if he just increases the heap? It's not like he's trying to send 1 GB of data, 30 MB isn't that much. Commented Aug 18, 2015 at 0:09
  • 2
    I usually find that if your program needs to increase the jvm memory allocation, you might be better off with another solution. After all, it's a portability issue — especially for wearables. Who knows how much memory the consumer will really have? Commented Aug 18, 2015 at 0:11
  • 1
    Not to mention this is likely on a mobile device with limited heap space Commented Aug 18, 2015 at 0:21

3 Answers 3

1

Let's solve this using the ChannelApi. We also do not have to worry about chunks like I said in my comment. Google thought ahead and made a convenience method for sending files. 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;
}

NOTE: this uses blocking methods, and should not be run in the ui thread.

If you would like the calls to be nonblocking, you should omit my usage of PendingResult.await(), and set the result callback of PendingResult. The callback can be set via setResultCallback(ResultCallback callback).

Sign up to request clarification or add additional context in comments.

Comments

0

I recommend that you use a ByteArrayOutputStream. Then to read from an InputStream, use this code:

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

Then, you can iterate through the bytes with for(byte b : stream.toByteArray()) {...}

Comments

-1

You should use a buffer rather than attempting to store the entire file as an array. Read in parts of the file into a byte array and then write them out to the stream. This also will prevent OOM errors on devices with less memory.

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;
}

8 Comments

Do you have an example perhaps?
Ive edited my answer. To be more specific you would need to give more details about the file transfer
This code is not a correct read loop. You shouldn't try to guess the file length. It might change while you're reading. You must read while the return value is positive, and you must store that return value into a variable. Otherwise it is impossible to use the data that was read correctly. At present you're assuming every read returned 1024 byes. It isn't specified to do that, and it certainly won't do that the last time unless the file length is a multiple of 1024.
A handler will be open on the file so it shouldnt change. But if it did it would cause an issue just the same as the OP's original code would so theres no reason to assume the file will change.
The point is that a currently written copy loop doesn't suffer from this problem. Your new code still suffers from this, and it still suffers from the second problem I mentioned, and sprays byte arrays all over the place. The correct way to do this has been posted here hundreds times. It's four lines of code plus two declarations.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.