views:

220

answers:

2

I am developing an Android app which enables the user to upload a file to services like Twitpic and others. The POST upload is done without any external libraries and works just fine. My only problem is, that I can't grab any progress because all the uploading is done when I receive the response, not while writing the bytes into the outputstream. Here is what I do:

URL url = new URL(urlString); 
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 
conn.setDoInput(true); 
conn.setDoOutput(true); 
conn.setUseCaches(false); 
conn.setRequestMethod("POST"); 
conn.setRequestProperty("Connection", "Keep-Alive"); 
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); 
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());

Then I write the form data to dos, which is not so important here, now. After that I write the file data itself (reading from "in", which is the InputStream of the data I want to send):

while ((bytesAvailable = in.available()) > 0)
{ 
 bufferSize = Math.min(bytesAvailable, maxBufferSize);
 byte[] buffer = new byte[bufferSize];
 bytesRead = in.read(buffer, 0, bufferSize);
 dos.write(buffer, 0, bytesRead);
} 

After that, I send the multipart form data to indicate the end of the file. Then, I close the streams:

in.close(); 
dos.flush(); 
dos.close(); 

This all works perfectly fine, no problem so far. My problem is, however, that the whole process up to this point takes about one or two seconds no matter how large the file is. The upload itself seems to happen when I read the response:

DataInputStream inStream = new DataInputStream(conn.getInputStream());

This takes several seconds or minutes, depending on how large the file is and how fast the internet connection. My questions now are: 1) Why doesn't the uplaod happen when I write the bytes to "dos"? 2) How can I grab a progress in order to show a progress dialog during the upload when it all happens at once?

/EDIT: 1) I set the Content-Length in the header which changes the problem a bit, but does not in any way solve it: Now the whole content is uploaded after the last byte is written into the stream. So, this doesn't change the situation that you can't grab the progress, because again, the data is written at once. 2) I tried MultipartEntity in Apache HttpClient v4. There you don't have an OutputStream at all, because all the data is written when you perform the request. So again, there is no way to grab a progress.

Is there anyone out there who has any other idea how to grab a process in a multipart/form upload?

A: 

have you tried using the apache DefaultHttpClient instead of the URLConnection?

Ben
You mean with the external libraries apache-mime4j-0.6.jar and httpmime-4.0.1.jar?I tried to avoid that. My whole APK is only 90kb and it already has quite alot of functions. I'd rather not blow that to 300kb only to implement something that already works.Is there no way I can grab a progress using URLConnection?
Manuel
no, its built into the android platform. org.apache.http.impl.client.DefaultHttpClient
Ben
All the examples I found used FileBody and MultipartEntity which are not part of the Android platform. Do you have any idea how to do a multipart upload with DefaultHttpClient, using only classes included in the platform?
Manuel
I tried to use DefaultHttpClient now, but there is no way grabbing a progress here. At least I didn't find one. Could you give an example of how to do that?
Manuel
+5  A: 

I have tried quite a lot in the last days and I think I have the answer to the initial question:

It's not possible to grab a progress using HttpURLConnection because there is a bug / unusual behavior in Android: http://code.google.com/p/android/issues/detail?id=3164#c6

It will be fixed post Froyo, which is not something one can wait for...

So, the only other option I found is to use the Apache HttpClient. The answer linked by papleu is correct, but it refers to general Java. The classes that are used there are not available in Android anymore (HttpClient 3.1 was part of the SDK, once). So, what you can do is add the libraries from HttpClient 4 (specifically apache-mime4j-0.6.jar and httpmime-4.0.1.jar) and use a combination of the first answer (by Tuler) and the last answer (by Hamy):

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;

public class CountingMultipartEntity extends MultipartEntity {

    private final ProgressListener listener;

    public CountingMultipartEntity(final ProgressListener listener) {
        super();
        this.listener = listener;
    }

    public CountingMultipartEntity(final HttpMultipartMode mode, final ProgressListener listener) {
        super(mode);
        this.listener = listener;
    }

    public CountingMultipartEntity(HttpMultipartMode mode, final String boundary,
            final Charset charset, final ProgressListener listener) {
        super(mode, boundary, charset);
        this.listener = listener;
    }

    @Override
    public void writeTo(final OutputStream outstream) throws IOException {
        super.writeTo(new CountingOutputStream(outstream, this.listener));
    }

    public static interface ProgressListener {
        void transferred(long num);
    }

    public static class CountingOutputStream extends FilterOutputStream {

        private final ProgressListener listener;
        private long transferred;

        public CountingOutputStream(final OutputStream out,
                final ProgressListener listener) {
            super(out);
            this.listener = listener;
            this.transferred = 0;
        }


        public void write(byte[] b, int off, int len) throws IOException {
            out.write(b, off, len);
            this.transferred += len;
            this.listener.transferred(this.transferred);
        }

        public void write(int b) throws IOException {
            out.write(b);
            this.transferred++;
            this.listener.transferred(this.transferred);
        }
    }
}

This works with HttpClient 4, but the downside is that my apk now has a size of 235 kb (it was 90 kb when I used the multipart upload described in my question) and, even worse, an installed app size of 735 kb (about 170 kb before). That's really awful. Only to get a progress during upload the app size is now more than 4 times as big as it was before.

Manuel