2010-07-09 53 views
17

Je développe une application Android qui permet à l'utilisateur de télécharger un fichier à des services comme Twitpic et d'autres. Le téléchargement POST est effectué sans bibliothèques externes et fonctionne correctement. Mon seul problème est, que je ne peux pas saisir aucun progrès parce que tout le téléchargement est fait quand je recevoir la réponse, pas pendant que écrit les octets dans le flux de sortie. Voici ce que je fais:Impossible de progresser sur le téléchargement de fichiers POST http (Android)

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

J'écrire les données de formulaire à dos, ce qui est pas si important ici, maintenant. Après que j'écrire les données de fichier lui-même (la lecture de « in », qui est le InputStream des données que je veux envoyer):

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

Après cela, j'envoie les données du formulaire multipart pour indiquer la fin de la fichier. Ensuite, je ferme les flux:

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

Tout cela fonctionne parfaitement bien, pas de problème jusqu'à présent. Mon problème est, cependant, que tout le processus jusqu'à ce point prend environ une ou deux secondes, peu importe la taille du fichier. Le téléchargement lui-même semble se produire quand je lis la réponse:

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

Cela prend plusieurs secondes ou minutes, selon la taille du fichier est et à quelle vitesse la connexion Internet. Mes questions sont maintenant: 1) Pourquoi l'uplaod ne se passe-t-il pas lorsque j'écris les octets à "dos"? 2) Comment puis-je obtenir une progression afin d'afficher une boîte de dialogue de progression pendant le téléchargement lorsque tout se produit en même temps?

/EDIT: 1) Je mets le Content-Length dans l'en-tête qui change le problème un peu, mais ne saurait en aucun façon résous: Maintenant tout le contenu est téléchargé après le dernier octet est écrit dans le flux. Donc, cela ne change pas la situation que vous ne pouvez pas saisir la progression, car encore une fois, les données sont écrites à la fois. 2) J'ai essayé MultipartEntity dans Apache HttpClient v4. Là, vous n'avez pas du tout un OutputStream, car toutes les données sont écrites lorsque vous effectuez la requête. Encore une fois, il n'y a aucun moyen de progresser.

Y a-t-il quelqu'un là-bas qui a une autre idée de la façon d'obtenir un processus dans un téléchargement multipart/formulaire?

+0

Peut-être que cette réponse vous donnera quelques conseils: http://stackoverflow.com/questions/254719/file-upload-with-java-with-progress-bar/470047#470047 – pableu

+0

@pableu : J'ai examiné cela. Le problème est: Il n'y a pas RequestEntity dans le SDK Android. Il fait partie d'Apache HttpClient 3.1, qui n'est plus utilisé par Android. Maintenant, j'ai même essayé de charger les nouvelles librairies 4.0.1, mais je ne vois aucune possibilité de progresser ici. D'autres idées? – Manuel

Répondre

20

J'ai essayé pas mal dans les derniers jours et je pense avoir la réponse à la question initiale:

Il est impossible de saisir un progrès en utilisant HttpURLConnection parce qu'il ya un bug/comportement inhabituel dans Android: http://code.google.com/p/android/issues/detail?id=3164#c6

Il sera fixé Froyo post, ce qui est quelque chose qu'on peut attendre pour ...

Ainsi, la seule autre option que je trouve est d'utiliser Apache HttpClient. Le answer linked by papleu est correct, mais il se réfère à Java général. Les classes qui y sont utilisées ne sont plus disponibles dans Android (HttpClient 3.1 faisait partie du SDK, une fois). Donc, ce que vous pouvez faire est d'ajouter les bibliothèques de HttpClient 4 (spécifiquement apache-mime4j-0.6.jar et httpmime-4.0.1.pot) et utiliser une combinaison de la première réponse (par Tuler) et la dernière réponse (par 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); 
     } 
    } 
} 

Cela fonctionne avec HttpClient 4, mais l'inconvénient est que mon apk a maintenant une taille de 235 kb (c'était 90 kb quand j'ai utilisé le téléchargement multipart décrit dans ma question) et, pire encore, une taille d'application installée de 735 ko (environ 170 ko auparavant). C'est vraiment horrible. Seulement pour obtenir une progression pendant le téléchargement, la taille de l'application est maintenant plus de 4 fois plus grande qu'auparavant.

+1

A propos de la taille .apk et de l'installation, avez-vous vérifié ProGuard sur SourceForge? Il va réduire votre application à l'endroit où vous étiez ou même plus petit :) – Tobias

+1

Ceci est une solution fantastique! Merci beaucoup de poster –

+0

Vous n'avez plus besoin d'apache-mime4j-0.6.jar, car dans la version 4.1 alpha 2, les dépendances pour apache-mime4j-0.6.jar sont supprimées. – CSchulz

0

Il est possible de récupérer la progression à partir de DefaultHttpClient. Le fait qu'il reste dans la ligne

response = defaultHttpClient.execute(httpput, context); 

jusqu'à ce que des données complètes est envoyé, ne signifie pas nécessairement que vous ne pouvez pas saisir les progrès. A savoir, HttpContext change lors de l'exécution de cette ligne, donc si vous l'approchez via Thread différent, vous pouvez observer des changements dans cela. Ce dont vous avez besoin, c'est ConnAdapter. Il a HttpConnectionMetrics intégré

final AbstractClientConnAdapter connAdapter = (AbstractClientConnAdapter) context.getAttribute(ExecutionContext.HTTP_CONNECTION); 
final HttpConnectionMetrics metrics = connAdapter.getMetrics(); 
int bytesSent = (int)metrics.getSentBytesCount(); 

Si vous vérifiez cela périodiquement, vous pourrez observer la progression. Assurez-vous de gérer correctement les threads, et tous essayer/attraper gardé. En particulier, gérer le cas où Context n'est plus valide (IllegalStateException), ce qui se produit lorsque Put est annulé ou terminé.

0

Utilisez plutôt WatchedInputStream. C'est agréable, génial et facile à utiliser.

  1. utilisez WatchedInputStream pour envelopper votre flux d'entrée.
  2. watchedStream.setListener

    watchedStream.setListener (nouveau WatchedInputStream.Listener() {

    public void notify (int lecture) {// faire quelque chose pour mettre à jour l'interface utilisateur.
    }}

1

Essayez ceci. Il fonctionne.

connection = (HttpURLConnection) url_stripped.openConnection(); 
    connection.setRequestMethod("PUT"); 
    String boundary = "---------------------------boundary"; 
    String tail = "\r\n--" + boundary + "--\r\n"; 
    connection.addRequestProperty("Content-Type", "image/jpeg"); 
    connection.setRequestProperty("Connection", "Keep-Alive"); 
    connection.setRequestProperty("Content-Length", "" 
      + file.length()); 
    connection.setDoOutput(true); 

    String metadataPart = "--" 
      + boundary 
      + "\r\n" 
      + "Content-Disposition: form-data; name=\"metadata\"\r\n\r\n" 
      + "" + "\r\n"; 

    String fileHeader1 = "--" 
      + boundary 
      + "\r\n" 
      + "Content-Disposition: form-data; name=\"uploadfile\"; filename=\"" 
      + fileName + "\"\r\n" 
      + "Content-Type: application/octet-stream\r\n" 
      + "Content-Transfer-Encoding: binary\r\n"; 

    long fileLength = file.length() + tail.length(); 
    String fileHeader2 = "Content-length: " + fileLength + "\r\n"; 
    String fileHeader = fileHeader1 + fileHeader2 + "\r\n"; 
    String stringData = metadataPart + fileHeader; 

    long requestLength = stringData.length() + fileLength; 
    connection.setRequestProperty("Content-length", "" 
      + requestLength); 
    connection.setFixedLengthStreamingMode((int) requestLength); 
    connection.connect(); 

    DataOutputStream out = new DataOutputStream(
      connection.getOutputStream()); 
    out.writeBytes(stringData); 
    out.flush(); 

    int progress = 0; 
    int bytesRead = 0; 
    byte buf[] = new byte[1024]; 
    BufferedInputStream bufInput = new BufferedInputStream(
      new FileInputStream(file)); 
    while ((bytesRead = bufInput.read(buf)) != -1) { 
     // write output 
     out.write(buf, 0, bytesRead); 
     out.flush(); 
     progress += bytesRead; 
     // update progress bar 
     publishProgress(progress); 
    } 

    // Write closing boundary and close stream 
    out.writeBytes(tail); 
    out.flush(); 
    out.close(); 

    // Get server response 
    BufferedReader reader = new BufferedReader(
      new InputStreamReader(connection.getInputStream())); 
    String line = ""; 
    StringBuilder builder = new StringBuilder(); 
    while ((line = reader.readLine()) != null) { 
     builder.append(line); 
    } 

Référence: http://delimitry.blogspot.in/2011/08/android-upload-progress.html