2009-08-27 16 views
8

Por lo tanto, estoy alimentando datos de archivo a una API que toma Reader, y me gustaría una forma de informar el progreso.Contenedor InputStream o Reader para informe de progreso

Parece que debe ser sencillo para escribir una aplicación FilterInputStream que envuelve el FileInputStream, no pierde de vista el número de bytes leídos vs el tamaño total del archivo, y dispara un evento (o, llama a algunos update() método) que informe progreso fraccionario.

(Alternativamente, podría reportar bytes leídos absolutos, y alguien más podría hacer los cálculos. - quizás más generalmente útil en el caso de otras situaciones de streaming)

Sé que he visto esto antes y lo Puede que incluso lo haya hecho antes, pero no puedo encontrar el código y soy flojo. ¿Alguien lo tiene por ahí? ¿O alguien puede sugerir un mejor enfoque?


durante un año (y un poco) más tarde ...

que implementa una solución basada en la respuesta de Adamski a continuación, y funcionó, pero después de algunos meses de uso no se lo recomendaría . Cuando tiene muchas actualizaciones, disparar/manejar eventos de progreso innecesarios se convierte en un gran costo. El mecanismo de conteo básico está bien, pero es mucho mejor tener a quien le importe el sondeo de progreso, en lugar de presionarlo.

(Si conoce el tamaño total, se puede tratar solamente disparando un evento cada> cambio de un 1% o lo que sea, pero no es realmente vale la pena. Y, a menudo, no lo hace.)

Respuesta

11

Aquí hay una implementación bastante básica que dispara PropertyChangeEvent s cuando se leen bytes adicionales. Algunas advertencias:

  • La clase no admite mark o reset operaciones, aunque estos serían fáciles de agregar.
  • La clase no verifica si el número total de bytes leídos alguna vez excede el número máximo de bytes anticipados, aunque esto siempre podría tratarse con el código del cliente cuando se muestre el progreso.
  • No he probado el código.

Código:

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

No está mal. Probablemente habría olvidado 'skip()'. :) –

+0

@David: para ser sincero, la implementación del salto significó la introducción de moldes desagradables (int), así que si sabes que no lo necesitas, arrojaré una excepción UnsupportedOperationException también aquí. – Adamski

+0

También debe verificar que 'super.read()' no arroje un resultado negativo (final de datos). – PhoneixS

1

Si Estamos construyendo una aplicación GUI siempre hay ProgressMonitorInputStream. Si no hay una GUI involucrada en envolver un InputStream en la forma en que usted describe, es obvio y toma menos tiempo que publicar una pregunta aquí.

+0

Sí, lo miré. Es una aplicación GUI pero es bastante compleja y el informe de progreso no es solo cuestión de mostrar un 'ProgressMonitor' estándar. Envolver el 'InputStream' tomaría menos tiempo que publicar una pregunta, pero nunca sabría si alguien tiene una mejor idea. –

5

Guava 's com.google.common.io paquete puede ayudarle un poco. Lo siguiente no está compilado ni probado, pero debería ponerlo en el camino correcto.

long total = file1.length(); 
long progress = 0; 
final OutputStream out = new FileOutputStream(file2); 
boolean success = false; 
try { 
    ByteStreams.readBytes(Files.newInputStreamSupplier(file1), 
     new ByteProcessor<Void>() { 
     public boolean processBytes(byte[] buffer, int offset, int length) 
      throws IOException { 
      out.write(buffer, offset, length); 
      progress += length; 
      updateProgressBar((double) progress/total); 
      // or only update it periodically, if you prefer 
     } 
     public Void getResult() { 
      return null; 
     } 
     }); 
    success = true; 
} finally { 
    Closeables.close(out, !success); 
} 

Esto puede parecer un montón de código, pero creo que es lo menos que te saldrá con la tuya. (Tenga en cuenta que otras respuestas a esta pregunta no dan completos ejemplos de código, por lo que es difícil compararlos de esa manera.)

+0

¿Conoces alguna forma confiable de hacer esto con una URL y no con un archivo? Lo que me molesta es la forma de encontrar la longitud del contenido de la URL. Como se indica aquí, parece que no hay forma de saber el tamaño de la transmisión si usamos la compresión gzip. http://android-developers.blogspot.fr/2011/09/androids-http-clients.html – Snicolas

+0

La respuesta de Adamski funciona pero hay un pequeño error. El método de lectura reemplazada (byte [] b) llama al método de lectura (byte [] b, int off, int len) a través de la superclase. Así que updateProgress (long numBytesRead) se llama dos veces para cada acción de lectura y se termina con un numBytesRead que es dos veces el tamaño del archivo después de que se haya leído el archivo total. No reemplazando el método de lectura (byte [] b) se resuelve el problema. – WillamS

+0

(¿No debería adjuntarse este comentario a esa respuesta?) –

3

La respuesta de Adamski funciona pero hay un pequeño error. El método reemplazado read(byte[] b) llama al método read(byte[] b, int off, int len) a través de la superclase.
Así que updateProgress(long numBytesRead) se llama dos veces por cada acción de lectura y termina con un numBytesRead que es dos veces el tamaño del archivo después de que se haya leído el archivo total.

No anula read(byte[] b) método resuelve el problema.

+3

Esto debe ser un comentario de la respuesta @Adamski no una respuesta en sí misma al menos si no se pone el código corregido en su respuesta. – PhoneixS

0

Para completar la respuesta dada por @ Kevin Bourillion, se puede aplicar a un contenido de red, así utilizando esta técnica (que impide la lectura de la corriente de dos veces: una para tamaño y una para el contenido):

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

Donde ProgressByteProcessor es una clase interna:

public class ProgressByteProcessor implements ByteProcessor<Void> { 

    private OutputStream bos; 
    private long progress; 
    private long total; 

    public ProgressByteProcessor(OutputStream bos, long total) { 
     this.bos = bos; 
     this.total = total; 
    } 

    public boolean processBytes(byte[] buffer, int offset, int length) throws IOException { 
     bos.write(buffer, offset, length); 
     progress += length - offset; 
     publishProgress((float) progress/total); 
     return true; 
    } 

    public Void getResult() { 
     return null; 
    } 
} 
+0

PD: Esto no debería funcionar con urlconnections comprimidos comprimidos. – Snicolas

Cuestiones relacionadas