9

I'm working on a project which download a file by using a http connection. I display a horizontal progress bar with the progress bar status during the downloading. My function looks like this:

.......
try {           
        InputStream myInput = urlconnect.getInputStream();
        BufferedInputStream buffinput = new BufferedInputStream(myInput);

        ByteArrayBuffer baf = new ByteArrayBuffer(capacity);
        int current = 0;
        while((current = buffinput.read()) != -1) {
            baf.append((byte) current);
        }
File outputfile = new File(createRepertory(app, 0), Filename);
        FileOutputStream myOutPut = new FileOutputStream(outputfile);
        myOutPut.write(baf.toByteArray());
...
}

I know in advance the size of my file so I need to retrieve the size during the downloading (in my while block). Thus I'll be able to determinate the status of the progress bar.

progressBarStatus = ((int) downloadFileHttp(url, app) * 100)/sizefile;

long downloadFileHttp(.., ..) is the name of my function.

I already try to retrieve it by using outputfile.length but his value is "1" maybe it's the number of file that I'm trying to download.

Is there any way to figure it out?

UPDATE 1

I haven't got any thread which allow me to figure this out. Currently I have got a horizontal progress bar whch displays only 0 and 100% whitout intermediate values. I think about another approach. If I know the rate of my wifi and the size of the file I can determinate the time of downloading.

I know that I can retrieve the piece of information of my Wifi connection and the size of my file to download.

Is anybody already have worked or have thread on it?

13KZ
  • 1,295
  • 5
  • 24
  • 43
  • 1
    Since you are manually reading bytes from the stream, you could just keep a counter in the while loop, which adds up the amount of bytes copied. Make it volatile and you can read it from another thread. You could also use baf.length(), but this wouldn't be thread safe (I believe). – Alexander Torstling Mar 05 '13 at 14:20
  • Thanks @AlexanderTorstling. I already try to retrieve the length of baf but when I try to return his value it catch an Exception – 13KZ Mar 05 '13 at 14:40
  • what is that use ? for updating the UI status ? – Chintan Khetiya Mar 19 '13 at 09:55
  • @chintankhetiya yes I'm trying to display a progress bar with the status. – 13KZ Mar 19 '13 at 13:19

8 Answers8

10

I'll assume that you're using HttpURLConnection. In which case you need to call the getContentLength() method on urlconnect.

However, the server is not required to send a valid content length, so you should be prepared for it to be -1.

parsifal
  • 121
  • 2
  • 5
    Any server worth it's weight in bits will send a Content-Length header. A great deal of clients will hang if it is not present. – Kylar Mar 05 '13 at 15:22
  • 2
    @Kylar "worth it's weight in bits" - this will stick with me for a long time. Couldn't stop laughing. – Tom 'Blue' Piddock Mar 20 '13 at 09:44
6

AsyncTask might be the perfect solution for you:

private class DownloadFileTask extends AsyncTask<URL, Integer, Long> {
 protected Long doInBackground(URL... urls) {
    Url url = urls[0];
    //connect to url here
    .......
    try {           
        InputStream myInput = urlconnect.getInputStream();
        BufferedInputStream buffinput = new BufferedInputStream(myInput);

        ByteArrayBuffer baf = new ByteArrayBuffer(capacity);
        int current = 0;
        while((current = buffinput.read()) != -1) {
            baf.append((byte) current);
            //here you can send data to onProgressUpdate
            publishProgress((int) (((float)baf.length()/ (float)sizefile) * 100));
        }
    File outputfile = new File(createRepertory(app, 0), Filename);
    FileOutputStream myOutPut = new FileOutputStream(outputfile);
    myOutPut.write(baf.toByteArray());
    ...
 }

 protected void onProgressUpdate(Integer... progress) {
     //here you can set progress bar in UI thread
     progressBarStatus = progress;
 }

}

to start AsyncTask call here within your method

new DownloadFileTask().execute(url);
Alex
  • 688
  • 6
  • 8
  • 2
    I concur with @Alex answer. It's the way to go. You can get the 'sizeFile' from the Content-Length header of your response (URLConnection.getContentLength()). But instead of reading one byte at a time (buffinput.read()), read a bunch of them at the same time (buffinput.read(byteArray)), unless you want to risk getting hundreds-of-thousands of calls to your onProgressUpdate. If the content-length is unknown, then there is just no way to have a progress bar. – Streets Of Boston Mar 19 '13 at 21:49
  • If you don't know the size of file, the only option would be to set progress bar as [indeterminate](http://developer.android.com/reference/android/widget/ProgressBar.html#setIndeterminate(boolean)) – Alex Mar 26 '13 at 21:22
3

Simple. Below Code:

try {
    URL url = new URL(yourLinkofFile);
    URLConnection conn = url.openConnection();
    conn.connect();
    totalFileSize = conn.getContentLength();
} catch (Exception e) {
    Log.e(TAG, "ERROR: " + e.toString());
}
reiley
  • 3,759
  • 12
  • 58
  • 114
2

Check the Content-Length header on the response. It should be set. All major HTTP servers use this header.

Kylar
  • 8,876
  • 8
  • 41
  • 75
2

In HTTP 1.1 specs, the chunk response data is supposed to be pulled back across multiple rounds. Acutally, the content-length is -1 in chunk response, so we can't use availble method in Inputstream. BTW, Inputstream.availble method is only stable to get content length in ByteArrayInputStream.

If you just want to get the total length, you need to calc it by yourself in each read round. see the IOUtils class in apache commons-io project as below:

//-----------------------------------------------------------------------
/**
 * Copy bytes from an <code>InputStream</code> to an
 * <code>OutputStream</code>.
 * <p>
 * This method buffers the input internally, so there is no need to use a
 * <code>BufferedInputStream</code>.
 * <p>
 * Large streams (over 2GB) will return a bytes copied value of
 * <code>-1</code> after the copy has completed since the correct
 * number of bytes cannot be returned as an int. For large streams
 * use the <code>copyLarge(InputStream, OutputStream)</code> method.
 * 
 * @param input  the <code>InputStream</code> to read from
 * @param output  the <code>OutputStream</code> to write to
 * @return the number of bytes copied
 * @throws NullPointerException if the input or output is null
 * @throws IOException if an I/O error occurs
 * @throws ArithmeticException if the byte count is too large
 * @since Commons IO 1.1
 */
public static int copy(InputStream input, OutputStream output) throws IOException {
    long count = copyLarge(input, output);
    if (count > Integer.MAX_VALUE) {
        return -1;
    }
    return (int) count;
}

/**
 * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
 * <code>OutputStream</code>.
 * <p>
 * This method buffers the input internally, so there is no need to use a
 * <code>BufferedInputStream</code>.
 * 
 * @param input  the <code>InputStream</code> to read from
 * @param output  the <code>OutputStream</code> to write to
 * @return the number of bytes copied
 * @throws NullPointerException if the input or output is null
 * @throws IOException if an I/O error occurs
 * @since Commons IO 1.3
 */
public static long copyLarge(InputStream input, OutputStream output)
        throws IOException {
    byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
    long count = 0;
    int n = 0;
    while (-1 != (n = input.read(buffer))) {
        output.write(buffer, 0, n);
        count += n;
    }
    return count;
}

If you want to check download progress, you need to callback on each read round from InputStream input to OutputStream output in the course of copping to disk. In the callback, you can copy a piece of data and add the amount to the counter which is designed to be available for your progressbar functionality. it is a little bit complex

Henry Leu
  • 2,184
  • 4
  • 22
  • 34
1

It sounds like your main problem is not getting the length of the file or figuring out the actual value, but rather how to access the current value from another thread so that you can update the status bar appropriately.

You have a few approaches to solve this:

1.) In your progress bar item have a callback that will let you set the value and call that method each time you update your count in your download thread. 2.) Put the value in some field that is accessible to both threads (potentially not-thread-safe).

If it were me, in my progress bar item I would have a method that would allow updating the progress with some value. Then I would call that method from my thread that is downloading the file.

So basically User --> Clicks some download button --> Handler calls the method to start the download, passing a callback to the update progress bar method --> Downloading thread calls the method on each iterative cycle with the updated percentage complete.

Blaine Mucklow
  • 592
  • 7
  • 18
1

I think you're making your life too complicated :)

First: since progressBarStatus = ((int) downloadFileHttp(url, app) * 100)/sizefile; is always either 0, either 100, maybe you're not computing the value correctly. You didn't post the whole method there, but don't forget you're dealing with int so sizefile is always int, and making divisions to a higher or equal to sizefile is always going to return 0 or 1. I suspect that is the direction you need to look into ... Also, I don't see in your code where you're updating the progressbar after an intermediate byte read.

Second: I think it would be more efficient if you would read in chunks. The read is more efficient and you don't need to notify the UI thread for each downloaded byte. The answer from Adamski from this thread might help you out. Just use a smaller byte array. I am usually using 256 (3G) or 512 (Wi-Fi) - but maybe you don't need to go that much into detail. So once you got a new array read, count the total number of bytes read, notify the UI and continue reading until the end of stream.

Third: Set the progressBar.setMax() before downloading to sizeFile, compute the downloaded bytes number properly based on the comment from "First" and then call setProgress with that computed number. Just don't forget to update the progressbar on an UI thread. AsyncTask has a great mechanism to help you with that.

Good luck!

Community
  • 1
  • 1
gunar
  • 14,660
  • 7
  • 56
  • 87
0

Well this should help you out

URLConnection connection = servletURL.openConnection();
BufferedInputStream buff = new BufferedInputStream(connection .getInputStream());
ObjectInputStream input = new ObjectInputStream(buff );
int avail = buff .available();

System.out.println("Response content size = " + avail);
Sudhakar
  • 4,823
  • 2
  • 35
  • 42
  • Thanks @Sudhakar for your answer!! I read that this method returns the number of bytes available in the buffer plus those available in the source stream. But it returns sometimes an value which is less than the precedent one. Seems weird – 13KZ Mar 05 '13 at 15:58
  • Well i had tried various ways in projects i have worked , this fit the bill closest :) – Sudhakar Mar 05 '13 at 18:40
  • No this is wrong. As the Java documentation itself states, the `available()` method is rarely useful as it has no real relation to the actual amount of data that could be read from the stream. – adelphus Mar 16 '13 at 10:42
  • Can you point where in the doc , it says that http://docs.oracle.com/javase/6/docs/api/java/io/BufferedInputStream.html#available() – Sudhakar Mar 16 '13 at 15:06
  • http://developer.android.com/reference/java/io/InputStream.html#available() Quote: 'Note that this method provides such a weak guarantee that it is not very useful in practice.' – adelphus Mar 16 '13 at 16:45
  • In HTTP 1.1 specs, the `chunk` response data is supposed to be pulled back across multiple rounds. Acutally, the content-length is -1 in chunk response, so we can't use `availble` method in Inputstream. – Henry Leu Mar 18 '13 at 11:41
  • BTW, `Inputstream.availble` method is only stable to get content length in ***ByteArrayInputStream***. – Henry Leu Mar 18 '13 at 11:55
  • Henry: Could you provide links ? – Sudhakar Mar 18 '13 at 12:11
  • @Sudhakar , look at the source-code of the various InputStream subclasses. You'll see that 'available' is not a good measure, since input can be buffered and the server may not yet have sent all the contents. Only the http-response would now the actual content-length. Use URLConnection's method getContentLength instead. If the response is not chunked, this should return the proper value. – Streets Of Boston Mar 19 '13 at 21:54