2009-08-27 11 views
8

Also füttere ich Dateidaten zu einer API, die eine Reader nimmt, und ich möchte eine Möglichkeit, den Fortschritt zu melden.InputStream oder Reader Wrapper für die Fortschrittsberichterstattung

Es scheint, wie es einfach sein sollte, eine FilterInputStream Implementierung zu schreiben, die die FileInputStream Wraps, verfolgt die Anzahl von Bytes gegenüber der Gesamtgröße der Datei gelesen, und feuert ein Ereignis (oder ruft einige update()-Methode) zu melden fraktioneller Fortschritt.

(Alternativ könnten sie berichten absolute Bytes zu lesen, und jemand anderes die Mathematik tun könnte -. Vielleicht ganz allgemein nützlich im Fall von anderen Streaming-Situationen)

Ich weiß, ich habe dies vor, und ich gesehen Vielleicht habe ich es schon einmal gemacht, aber ich kann den Code nicht finden und bin faul. Hat es jemand herumliegen? Oder kann jemand einen besseren Ansatz vorschlagen?


Ein Jahr (und ein bisschen) später ...

implementiert ich eine Lösung basierend auf Adamskis Antwort unten, und es funktionierte, aber nach einigen Monaten der Nutzung Ich würde es nicht empfehlen . Wenn Sie viele Updates haben, wird das Abfeuern/Behandeln unnötiger Fortschrittsereignisse zu hohen Kosten. Der grundlegende Zählmechanismus ist in Ordnung, aber viel besser, wer sich um die Fortschrittsumfrage kümmert, anstatt ihn ihnen zu übergeben.

(Wenn Sie die Gesamtgröße kennen, können Sie versuchen, nur ein Ereignis feuern jede> 1% Veränderung oder was auch immer, aber es ist nicht wirklich die Mühe wert. Und oft nicht.)

Antwort

11

Hier ist eine ziemlich grundlegende Implementierung, die PropertyChangeEvent s auslöst, wenn zusätzliche Bytes gelesen werden. Einige Einschränkungen:

  • Die Klasse unterstützt keine mark oder reset Operationen, auch wenn diese leicht wäre hinzuzufügen.
  • Die Klasse überprüft nicht, ob die Gesamtzahl der gelesenen Bytes die maximale Anzahl der erwarteten Bytes überschreitet, obwohl dies beim Anzeigen des Fortschritts immer vom Client-Code verarbeitet werden kann.
  • Ich habe den Code nicht getestet.

Code:

public class ProgressInputStream extends FilterInputStream { 
    private final PropertyChangeSupport propertyChangeSupport; 
    private final long maxNumBytes; 
    private volatile long totalNumBytesRead; 

    public ProgressInputStream(InputStream in, long maxNumBytes) { 
     super(in); 
     this.propertyChangeSupport = new PropertyChangeSupport(this); 
     this.maxNumBytes = maxNumBytes; 
    } 

    public long getMaxNumBytes() { 
     return maxNumBytes; 
    } 

    public long getTotalNumBytesRead() { 
     return totalNumBytesRead; 
    } 

    public void addPropertyChangeListener(PropertyChangeListener l) { 
     propertyChangeSupport.addPropertyChangeListener(l); 
    } 

    public void removePropertyChangeListener(PropertyChangeListener l) { 
     propertyChangeSupport.removePropertyChangeListener(l); 
    } 

    @Override 
    public int read() throws IOException { 
     int b = super.read(); 
     updateProgress(1); 
     return b; 
    } 

    @Override 
    public int read(byte[] b) throws IOException { 
     return (int)updateProgress(super.read(b)); 
    } 

    @Override 
    public int read(byte[] b, int off, int len) throws IOException { 
     return (int)updateProgress(super.read(b, off, len)); 
    } 

    @Override 
    public long skip(long n) throws IOException { 
     return updateProgress(super.skip(n)); 
    } 

    @Override 
    public void mark(int readlimit) { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public void reset() throws IOException { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public boolean markSupported() { 
     return false; 
    } 

    private long updateProgress(long numBytesRead) { 
     if (numBytesRead > 0) { 
      long oldTotalNumBytesRead = this.totalNumBytesRead; 
      this.totalNumBytesRead += numBytesRead; 
      propertyChangeSupport.firePropertyChange("totalNumBytesRead", oldTotalNumBytesRead, this.totalNumBytesRead); 
     } 

     return numBytesRead; 
    } 
} 
+0

Nicht schlecht. Wahrscheinlich hätte ich 'skip()' vergessen. :) –

+0

@David: Um ehrlich zu sein, das Implementieren von Überspringen bedeutete, böse (int) Umwandlungen einzuführen, wenn Sie also wissen, dass Sie es nicht brauchen, würde ich auch hier eine UnsupportedOperationException werfen. – Adamski

+0

Sie müssen auch überprüfen, dass 'super.read()' kein negatives Ergebnis liefert (Ende der Daten). – PhoneixS

1

Wenn Sie Erstellen Sie eine GUI-Anwendung gibt es immer ProgressMonitorInputStream. Wenn es kein GUI gibt, das ein InputStream in der Weise einschließt, die Sie beschreiben, ist ein Kinderspiel und nimmt weniger Zeit als das Posten einer Frage hier.

+0

Yeah, ich sah mir das an. Es ist eine GUI-Anwendung, aber es ist ziemlich komplex und Fortschrittsbericht ist nicht nur eine Frage des Auftauchens eines Standard-'ProgressMonitor'. Das Einpacken des 'InputStream' würde weniger Zeit in Anspruch nehmen, als eine Frage zu stellen, aber dann würde ich nie wissen, ob jemand da draußen eine bessere Idee hat. –

5

Guava ‚s com.google.common.io Paket können Sie ein wenig helfen. Das Folgende ist unkompiliert und ungetestet, sollte Sie aber auf den richtigen Weg bringen.

Das mag nach einer Menge Code aussehen, aber ich glaube, es ist das Mindeste, womit Sie davonkommen. (Beachten Sie andere Antworten auf diese Frage geben vollständige Codebeispiele, so ist es schwer, sie so zu vergleichen.)

+0

Kennen Sie eine zuverlässige Methode, dies mit einer URL und nicht mit einer Datei zu tun? Was mich nervt, ist der Weg, die Inhaltslänge der URL zu finden. Wie hier angedeutet, scheint es keine Möglichkeit zu geben, die Größe des Streams zu kennen, wenn wir die gzip-Komprimierung verwenden. http://android-developers.blogspot.fr/2011/09/androids-http-clients.html – Snicolas

+0

Die Antwort von Adamski funktioniert, aber es gibt einen kleinen Fehler. Die überschriebene Methode read (byte [] b) ruft die Methode read (byte [] b, int off, int len) über die Superklasse auf. So wird updateProgress (long numBytesRead) zweimal für jede Leseaktion aufgerufen, und Sie erhalten ein numBytesRead, das doppelt so groß ist wie die Datei, nachdem die gesamte Datei gelesen wurde. Nicht überschreiben Lese (Byte [] b) -Methode löst das Problem. – WillamS

+0

(Sollte dieser Kommentar dann nicht an diese Antwort angehängt werden?) –

3

Die Antwort von Adamski funktioniert, aber es gibt einen kleinen Fehler. Die überschriebene read(byte[] b) Methode ruft die read(byte[] b, int off, int len) Methode durch die Superklasse auf.
So wird updateProgress(long numBytesRead) zweimal für jede Leseaktion aufgerufen, und Sie enden mit einer numBytesRead, die die doppelte Größe der Datei ist, nachdem die gesamte Datei gelesen wurde.

Nicht überschreiben read(byte[] b) Methode löst das Problem.

+3

Dies muss ein Kommentar der Antwort von @Adamski sein, zumindest dann nicht, wenn Sie den korrigierten Code nicht in Ihre Antwort eingeben. – PhoneixS

0

die Antwort von @ Kevin Bourillion gegeben abzuschließen, kann es auch mit dieser Technik zu einem Netzwerk Inhalt angewandt werden (das verhindert zweimal den Strom zu lesen: ein für die Größe und einen für den Inhalt):

 final HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(url).openConnection(); 
     InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() { 

      public InputStream getInput() throws IOException { 
       return httpURLConnection.getInputStream(); 
      } 
     }; 
     long total = httpURLConnection.getContentLength(); 
     final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
     ByteStreams.readBytes(supplier, new ProgressByteProcessor(bos, total)); 

Wobei ProgressByteProcessor eine innere Klasse ist:

+0

PS: Dies sollte nicht mit gzip komprimierten URL-Verbindungen funktionieren. – Snicolas