开发者

Android - I want to show file upload progress to the user

I upload photo to the server via the default HttpClient in Android SDK. I want to show progress in the user interface, is there a 开发者_运维知识库way to find out how much has been uploaded? Is it possible with HttpUrlConnection?


For me HTTPClient didn't work. The bytes where buffered in parts and sent as total after the flush call. What worked was to sent it on socket level.

You can use the HttpMultipartClient for this (updated link on 30-10-2011): http://code.google.com/p/rainbowlibs/source/browse/android/trunk/rainbowlibs/src/it/rainbowbreeze/libs/data/HttpMultipartClient.java?spec=svn94&r=94

Specify the amount of bytes for each part and update the progressbar in the while loop:

while (( line = reader.readLine()) != null && !headersEnd)

Call the HttpMultipartClient as folow:

HttpMultipartClient httpMultipartClient = new HttpMultipartClient("bluppr.com", "/api/order/create", 80);

FileInputStream fis = new FileInputStream(path + fileName);
httpMultipartClient.addFile(fileName, fis, fis.available());
httpMultipartClient.setRequestMethod("POST");
httpMultipartClient.send();

At the server side use:

<?php

$target_path = "uploads/";

$target_path = $target_path . basename( $_FILES['uploadedfile']['name']);

if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
    echo "The file ".  basename( $_FILES['uploadedfile']['name'])." has been uploaded " .$_POST["order"]. " post";
} else{
    echo "There was an error uploading the file, please try again!";
}

?>

I used this for Bluppr Postcards, worked like a charm. If you need more info let me know.


1) Be sure to perform your upload in a Service with its own thread.

2) To get the progress: Wrap your InputStream in this class, and use a a httpmime.jar library which has MultiPart support for HttpClient. I used a thread which checks the progress and updates the progressbar in the notification.

package com.hyves.android.service.upload;

import java.io.IOException;
import java.io.InputStream;

/**
 * This outputstream wraps an existing outputstream and provides 
 * callbacks after certain amount of bytes to a HttpCallback
 * 
 * @author tjerk
 */
public class ProgressNotifyingInputStream extends InputStream {
    private InputStream wrappedStream;
    private int count = 0;
    private int totalSize;

    /**
     * Creates a new notifying outputstream which wraps an existing one.
     * When you write to this stream the callback will be notified each time when
     * updateAfterNumberBytes is written.
     * 
     * @param stream the outputstream to be wrapped
     * @param totalSize the totalsize that will get written to the stream
     */
    public ProgressNotifyingInputStream(InputStream stream, int totalSize) {
        if(stream==null) {
            throw new NullPointerException();
        }
        if(totalSize == 0) {
            throw new IllegalArgumentException("totalSize argument cannot be zero");
        }
        this.wrappedStream = stream;
        this.totalSize = totalSize;
    }


    @Override
    public int read() throws IOException {
        count++;
        return wrappedStream.read();
    }

    /**
     * Get progress from 0 to 100
     * @return
     */
    public int getProgress() {
        return count * 100 / totalSize;
    }

}


I needed the upload progress for an image and was not using the HttpMultipartClient because of implementation issues (trouble getting the package through gradle and dependency errors). Another issue I was running into was getting the actual file size of the image I wanted to upload.

My requirements also included having the upload in the notification area. Here is my solution:

Getting the image size

protected int sizeOf(Bitmap data) {
    /*
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        return data.getAllocationByteCount();
    } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
        return data.getRowBytes() * data.getHeight();
    } else {
        return data.getByteCount();
    }
    // NONE OF THE ABOVE RETURN ACCURATE RESULTS!
    // A Bitmap, when stored as a file takes up more room because it represents
    // full pixel data and is not compressed on disk.
    */
    byte[] bitmapdata = getBmpAsByteArray(data);
    return (bitmapdata == null) ? 0 : bitmapdata.length;
}

AsyncHttpPostTask extends AsyncTask<UploadableImage, Integer, String>

AsyncHttpPostTask#onProgressUpdate

This function is called from within AsyncHttpPostTask#doInBackground which calls the callback to alert the activity of the status change.

@Override
protected void onProgressUpdate(Integer... progress) {
    ((ImageUploadActivity) activity).updateProgress(progress[0]);
}

AsyncHttpPostTask#doInBackground

As I mentioned before, I did not use the HttpMultipartClient, so I had to sort-of implement my own. Most of this comes from http://www.androidsnippets.com/multipart-http-requests

@Override
protected String doInBackground(InputStream... inStream) {
    if (MainActivity.isDebugMode) {
        Log.d(TAG, "doInBackground");
    }

    HttpURLConnection connection;
    DataOutputStream outputStream;
    InputStream inputStream;

    String twoHyphens = "--";
    String boundary = "----------MobileFormData";
    String lineEnd = "\r\n";

    String result;

    int bytesRead, bytesAvailable, bufferSize;
    byte[] buffer;
    int maxBufferSize = 32768; // 2^15 = 32k -- http://stackoverflow.com/a/11221907/940217

    try {
        InputStream is = inStream[0];
        totalSize = curUpImage.getFileSize();
        Log.e(TAG, "Determined the file size to be " + totalSize + " bytes");

        URL url = new URL(this.server);
        connection = (HttpURLConnection) url.openConnection();

        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setUseCaches(false);
        connection.setChunkedStreamingMode(maxBufferSize);

        connection.setRequestMethod("POST");
        connection.setRequestProperty("Connection", "Keep-Alive");
        connection.setRequestProperty("User-Agent", "Android Multipart HTTP Client 1.0");
        connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

        outputStream = new DataOutputStream(connection.getOutputStream());
        // Upload POST Data
        Log.e(TAG, "Args: "+this.postArgs);
        String[] posts = this.postArgs.split("&");
        for (String post : posts) {
            outputStream.writeBytes(twoHyphens + boundary + lineEnd);
            String[] kv = post.split("=");
            outputStream.writeBytes(String.format("Content-Disposition: form-data; name=\"%s\"", kv[0]));
            outputStream.writeBytes(lineEnd);
            outputStream.writeBytes(lineEnd);
            outputStream.writeBytes(String.format("%s", kv[1]));
            outputStream.writeBytes(lineEnd);
        }

        outputStream.writeBytes(twoHyphens + boundary + lineEnd);
        outputStream.writeBytes("Content-Disposition: form-data; name=\"" + this.fileParamConst + "\"; filename=\"image.jpg\"" + lineEnd);
        outputStream.writeBytes("Content-Type: image/jpeg" + lineEnd);
        outputStream.writeBytes(lineEnd);

        bytesAvailable = is.available();
        bufferSize = Math.min(bytesAvailable, maxBufferSize);
        buffer = new byte[bufferSize];

        int totalByteRead = 0;
        bytesRead = is.read(buffer, 0, bufferSize);
        while (bytesRead > 0) {
            totalByteRead += bytesRead;
            Log.w(TAG, "totalByteRead: "+totalByteRead+", totalSize: "+totalSize);
            publishProgress((int) ((totalByteRead / (float) totalSize) * 100));
            outputStream.write(buffer, 0, bufferSize);
            bytesAvailable = is.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = is.read(buffer, 0, bufferSize);
        }

        if (totalByteRead == 0){
            Log.e(TAG, "Total bytes read from image file: "+totalByteRead);
        } else {
            Log.d(TAG, "Total bytes read from image file: "+totalByteRead);
        }

        outputStream.writeBytes(lineEnd);
        outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

        inputStream = connection.getInputStream();
        result = this.convertStreamToString(inputStream);

        is.close();
        inputStream.close();
        outputStream.flush();
        outputStream.close();

        return result;
    } catch (MalformedURLException e) {
        result = "Error - Malformed URL";
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        result = "Error - Image file not found.";
        e.printStackTrace();
    } catch (IOException e) {
        result = "Error - IO Exception.";
        e.printStackTrace();
    }
    return result;
}

AsyncHttpPostTask#onPostExecute

Here I parse my server's JSON response to see if the upload was able to be processed successfully, then return a message to the Activity which controls the notification.

@Override
protected void onPostExecute(String result) {

    String resultString = null;
    if (MainActivity.isDebugMode){
        Log.d(TAG, "Async result: "+result);
    }

    boolean successful = false;
    String[] errorMessages = null;
    try {
        JSONObject mainObject = new JSONObject(result);
        String resultJsonString = mainObject.getString("result");
        JSONArray messagesJsonArray = mainObject.getJSONArray("messages");
        if (resultJsonString != null){
            if (resultJsonString.equalsIgnoreCase("success")){
                successful = true;
            } else {
                Log.e(TAG, "result was: "+resultJsonString);
            }
        }
        errorMessages = new String[messagesJsonArray.length()];
        for (int i = 0; i < messagesJsonArray.length(); i++){
            errorMessages[i]= (String)messagesJsonArray.get(i);
        }
    } catch (JSONException e){
        Log.e(TAG, "JSON Exception -- The string that I tried to parse was:\n"+result);
        e.printStackTrace();
    }

    if (successful) {
        Toast.makeText(this.activity, "Upload completed successfully!", Toast.LENGTH_SHORT).show();
        resultString = "Upload complete.";
    } else {
        String eMessages;
        if (errorMessages != null && errorMessages.length > 0){
            eMessages = TextUtils.join(", ", errorMessages);
            resultString = "Image upload failed:\n"+eMessages;
        } else {
            resultString = "Image upload failed!";
        }
    }
    ((ImageUploadActivity) activity).updateProgress(null);
    ((ImageUploadActivity) activity).setPostResult(resultString);
}

Displaying the progress

In the Activity which is responsible for the notification, I have this callback function which is called from the async task. Displaying the progress here can also be done using one of the solutions discussed on John Russell's blog post. This Activity is launched with mode singleTop so that when it is brought to the front with the notification, the state is preserved.

ImageUploadActivity#buildNotify

private void buildNotify(){
    Intent resultIntent = new Intent(this, ImageUploadActivity.class);
    // Because clicking the notification opens a new ("special") activity, there's
    // no need to create an artificial back stack.
    PendingIntent resultPendingIntent =
            PendingIntent.getActivity(
                    this,
                    0,
                    resultIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT
            );

    mNotifyManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
    mBuilder = new NotificationCompat.Builder(this);
    mBuilder.setContentIntent(resultPendingIntent);
    mBuilder.setContentTitle("Image Upload")
            .setContentText("Image upload in progress")
            .setSmallIcon(android.R.drawable.ic_menu_upload);

}

ImageUploadActivity#updateProgress

This method flushes out the progress to the notification and also to the UI contained within the Activity.

public void updateProgress(Integer progress){
    this.currentProgress = progress;
    if (uploadStatusTV != null && this.currentProgress != null){
        currentStatus = "uploading image: "+this.currentProgress+"%";
        uploadStatusTV.setText("uploading image: "+this.currentProgress+"%");

        if (mBuilder == null){
            buildNotify();
        }
        // Sets the progress indicator to a max value, the
        // current completion percentage, and "determinate" state
        mBuilder.setProgress(100, currentProgress, false);
        // Displays the progress bar for the first time.
        mNotifyManager.notify(notify_id, mBuilder.build());

    } else if (uploadStatusTV != null){
        return;
    } else {
        Log.e(TAG, "You should never see this message.");
        finish();
    }
}


Or you should use AsyncTask to do the actual process of file upload and use ProcessDialog to start and stop the process.

You can see this code, http://github.com/narup/mymobile/blob/master/pbdroid/src/com/example/android/skeletonapp/StoreListActivity.java i wrote to load the JSON data over HTTP and i use process dialog.

Main part of the code is :

 private class LoadStoresTask extends AsyncTask<String, Void, List<Store>> {

@Override
protected List<Store> doInBackground(String... params) {
return WsiStoresClient.connect(params[0]);
}

@Override
protected void onPostExecute(List<Store> result) {
dismissDialog(BUSY_DIALOG_KEY);
}

}


I did not work with that API, but notice that HttpClient is not android specific:

org.apache.http.client.HttpClient

So if you google for "HttpClient progress" there is a number of posts that may be usefull.

Also, consider that post Android Download Progress


I wrote up an example of exactly how to do this -> http://toolongdidntread.com/android/android-multipart-post-with-progress-bar/


I haven't used httpclient but I have done something like you want using AsyncTask.

    private class DownloadImageTask extends AsyncTask<String, Void,Bitmap>{
            protected Bitmap doInBackground(String... urls) {

              while (myProgress<length){
                       myProgress=myProgress+1;  
                       myProgressBar.setProgress(myProgress);

                }
                 return decodeImage(urls[0]);
            }


           protected void onPostExecute(Bitmap result) {
                //dialog.dismiss();
                imView.setImageBitmap(result);
            }   

            protected void onPreExecute() {
                /* Things to be done while execution of long running operation is 
                 in progress. For example updating ProgressDialog */

               dialog = ProgressDialog.show(BusinessCardActivity.this,
                      "Loading.........","Wait For Few Second", true);          
                }
             }

See in background process I'm incrementing the progressbar and decoding image and in post execution I'm setting the image.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜