-2

Yes, this question has been asked a millions times, and I believe I've looked at them all. They are very "sometimesy", slow, or not what I need.

On one project, I use the following code to use the InputStream received from a GET to turn that into a PDF. This works PERFECTLY, every time, on my physical device and my emulator (Genymotion 2.1.1, Emulator API 18 4.3). Note that some things are edited out, and the PDFs are generally small, less than 1 MB.

public abstract class MyPDFFile extends File implements ApiModel{

public MyPDFFile(InputStream inputStream){
    super(context.getExternalFilesDir(
            Environment.DIRECTORY_DOWNLOADS), "my_pdf.pdf");

    if (externalStorageIsWritable()) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            FileOutputStream fileInputStream = new FileOutputStream(this);
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileInputStream));

            int readLine;
            char[] cbuf = new char[1];

            do {
                readLine = bufferedReader.read(cbuf);
                bufferedWriter.write(cbuf);
            } while (readLine != -1);
            bufferedWriter.close();
        }
        catch(IOException e){
            // Didn't work
        }
    }
    else{
        // Cant write
    }
} 

I figured on this new project, I could use the same code to download an APK from the internet to the device. Nope, definitely not the case. I eventually tried this for Inputstream to File:

FileOutputStream fileOutputStream = new FileOutputStream(file);

byte[] buffer = new byte[1];
while ( (read(buffer)) > 0 ) {

    fileOutputStream.write(buffer);
}
fileOutputStream.close();
close();

That works on my emulator, and works fine. I moved to testing on my device... not so much, which is weird, because the working PDF code works on both my emulator and device. I've tried adjusting the size of my buffer to various multiples of 512 (which results in the file being EXTREMELY small, like a few KB, to being EXTREMELY large, about double the apk size, which is about 5.6 MB).

Also, another weird thing: I can NEVER get it to successfully save outside of the constructor. When I do the saving there, the InputStream is fine, my file gets created, yadayada, and when I use successful code, I just rename the file afterwards since all I have access to in the constructor is the InputStream. If I decide "No, I want to name it when I have the proper things" and simply save the InputStream to my object, it NEVER works properly. Can never get above 4KB for the downloaded file. I've tried extends InputStream and extends BufferedInputStream to no avail.

I can post more code if needed. All I would have access to is the InputStream from my GET request; I'm using the browep Android HTTP library and that's all I can get without trying to mess with the library itself (or overriding methods in it).

gatlingxyz
  • 781
  • 6
  • 12
  • why 1 in `new byte[1];` ? – njzk2 Apr 11 '14 at 20:16
  • why do you extend `File` at all ? – njzk2 Apr 11 '14 at 20:17
  • I used an answer here from S/O which had that. No other reason. Though, I've tried multiple sized arrays and none of them worked (they actually made it worse). I'm extending file so I don't have to have an actual "File" object in it. – gatlingxyz Apr 11 '14 at 20:18
  • also, why do you use this library? I does not seem very widespread. (also, there is a 1-line file download that I use quite often : http://stackoverflow.com/questions/576513/android-download-binary-file-problems/5980089#5980089 ) – njzk2 Apr 11 '14 at 20:20
  • I'm using this library one because I'm familiar with it. Secondly, I'm not only using it for file downloading. I'm doing many POSTs and GETs and in a previous project that used it it seemed very easy to create objects from the JSON objects we got back. – gatlingxyz Apr 11 '14 at 20:21
  • 1
    ok. I just asked as, since it's not very much used, it could have tons of defect no one knows about. android team issued a library named `Volley` a few months ago, which deals quite nicely with json. – njzk2 Apr 11 '14 at 20:24
  • Definitely up for learning; I remember hearing about Volley. I'll look into it when I've exhausted all other options. – gatlingxyz Apr 11 '14 at 20:29
  • to make it work with buffers of more that 1 byte, you must use `bufferedWriter.write(cbuf, readline);` – njzk2 Apr 11 '14 at 20:33
  • Do you mean `bufferedWriter.write(cbuf, 0, readline)`? I tried that right after Luiggi's suggestion and it crashed this time (because of the -1). Then I added an if statement in the while to stop it incase it was -1, and my original problem happened. =( – gatlingxyz Apr 11 '14 at 20:40
  • what -1 ? luiggi's suggestion is complete, there is no `if` to add – njzk2 Apr 11 '14 at 20:46
  • The -1 returned from `read` when it's done, and that code you gave me was referring to the code from the working PDF sample. Luiggi's solution gives me the same problem I had before. – gatlingxyz Apr 11 '14 at 20:49
  • weird. tons of people have been downloading tons of file, mostly using the code Luiggi posted or some close equivalent, and I've never seen the issue you mention. But I only now noticed that you discard any IOException without even looking at it. – njzk2 Apr 11 '14 at 20:51
  • Which is why it's weird to me, too. All the code I found is some variant of his. But I get no IOException, or any Exception. In the code I'm using now, I have something that runs directly after the file is supposedly downloaded and that runs fine (I created a download notification and after the download, i update the notification, which happens every time without fail). I'll ultimately try Volley, but I'm confused because I've used similar code to download a PDF with no problems. – gatlingxyz Apr 11 '14 at 20:53
  • `no IOException, or any Exception` are you sure ? is there a log in the catch block ? – njzk2 Apr 11 '14 at 21:02
  • also, what is `read` in `while ( (read(buffer)) > 0 ) {` ? – njzk2 Apr 11 '14 at 21:02
  • Your brain is not working either. – Adam Arold Apr 11 '14 at 21:05
  • There is logging done (showing a toast for the mean time) and no exception is thrown. My object extends BufferedInputStream, so it's the read from that – gatlingxyz Apr 11 '14 at 21:08
  • @njzk2 I was about to invest in using Volley, but I see that it's not good for large payloads, and while the specific item I'm testing with isn't very big, I have no guarantee that file sizes will be small. – gatlingxyz Apr 11 '14 at 22:04
  • @njzk2 Actually, I think I'll still go with Volley. It looks incredibly easy and amazing, and I can use it throughout the rest of the app. I'll return to downloading larger files later. Thanks for the suggestion. – gatlingxyz Apr 11 '14 at 22:41

1 Answers1

0

The problem is that you're reading the file byte by byte. This can take ton of time. Instead, read the file in bigger piece of chunks, like 4 or 8 KBs:

int file_chunk_size = 1024 * 4; //4KBs, written like this to easily change it to 8
byte[] buffer = new byte[file_chunk_size];
int bytesRead = 0;
while ( (bytesRead = read(buffer)) > 0 ) {
    fileOutputStream.write(buffer, 0, bytesRead);
}
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • This makes sense for when it takes too long, but this doesn't work (in my scenario). I tried 4kb and 8kb. I will say that using a 4kb buffer did give me a slightly bigger file, but only up to about 8kb in total. Just to make sure it's said, I'm closing my `fileOutputStream` and the Inputstream directly after this chunk of code. – gatlingxyz Apr 11 '14 at 20:27
  • In general? A 5.6MB file is being downloaded, but only ending up at either: 1-8kb total, the correct size and file but only in an emulator, or bigger than the actual size, which means extra information in the file and it crashes. Your suggestion? The very first problem: it still ends up being too small. – gatlingxyz Apr 11 '14 at 20:38
  • @kentarosu then the problem seems to be on the stream passed as argument to `MyPDFFile` class. Instead doing this oddly design extending `File` class, move the logic in the method where you receive the real `InputStream` from your service and download the file from there using the piece of code I've provided here. I faced a similar problem when passing the `InputStream` between methods. – Luiggi Mendoza Apr 11 '14 at 21:19