2013-03-15 11 views
13

Ho riscontrato una situazione in cui devo visualizzare le immagini in una presentazione che cambia l'immagine molto velocemente. Il semplice numero di immagini mi fa desiderare di memorizzare i dati JPEG in memoria e decodificarli quando voglio visualizzarli. Per facilitare il Garbage Collector, sto usando BitmapFactory.Options.inBitmap per riutilizzare bitmap.BitmapFactory.Options.inBitmap causa la rottura quando si cambia la bitmap ImageView spesso

Sfortunatamente, questo causa tearing piuttosto grave, ho provato diverse soluzioni come sincronizzazione, semafori, alternando tra 2-3 bitmap, tuttavia, nessuno sembra risolvere il problema.

Ho creato un progetto di esempio che dimostra questo problema su GitHub; https://github.com/Berglund/android-tearing-example

Ho un thread che decodifica il bitmap, mette sul thread dell'interfaccia utente, e dorme per 5 ms:

Runnable runnable = new Runnable() { 
@Override 
public void run() { 
     while(true) { 
      BitmapFactory.Options options = new BitmapFactory.Options(); 
      options.inSampleSize = 1; 

      if(bitmap != null) { 
       options.inBitmap = bitmap; 
      } 

      bitmap = BitmapFactory.decodeResource(getResources(), images.get(position), options); 

      runOnUiThread(new Runnable() { 
       @Override 
       public void run() { 
        imageView.setImageBitmap(bitmap); 
       } 
      }); 

      try { 
       Thread.sleep(5); 
      } catch (InterruptedException e) {} 

      position++; 
      if(position >= images.size()) 
       position = 0; 
     } 
    } 
}; 
Thread t = new Thread(runnable); 
t.start(); 

La mia idea è che ImageView.setImageBitmap (bitmap) disegna il bitmap il prossimo vsync, tuttavia, probabilmente stiamo già decodificando la bitmap successiva quando questo accade, e come tale, abbiamo iniziato a modificare i pixel bitmap. Sto pensando nella giusta direzione?

Qualcuno ha qualche consiglio su dove andare da qui?

+0

'ImageView' è un requisito necessario? Suggerirei 'SurfaceView' o' TextureView' per alte percentuali di draw. –

+0

Sto affrontando la stessa lacrimazione ma in uno scenario diverso. Non posso fare domande a causa di questa politica di stackoverflow del culo. Per favore aiutami qualcuno. Sarà di grande aiuto per me. – therealprashant

risposta

4

In alternativa all'approccio attuale, è possibile considerare di mantenere i dati JPEG come si sta facendo, ma anche creare un Bitmap separato per ciascuna delle immagini e utilizzare i flag inPurgeable e inInputShareable. Questi flag allocano la memoria di supporto per i tuoi bitmap su un heap separato che non è gestito direttamente dal garbage collector Java e consente a Android stesso di scartare i dati bitmap quando non ha spazio per esso e di decodificare i tuoi JPEG su richiesta quando richiesto . Android ha tutto questo codice per scopi speciali per gestire i dati bitmap, quindi perché non usarlo?

+0

Ora, questo. Se avessi visto prima questa risposta, probabilmente la assegnerei la taglia. Questo risolve il problema perfettamente (e molto facilmente) sul mio Nexus 4 finora. Dovrò solo controllare anche i dispositivi di fascia più bassa. –

+0

felice di essere di aiuto =) –

0

Nel BM.decode (resource ... è la rete coinvolta?

Se sì allora u necessità di ottimizzare la connessione look-ahead e il trasporto dati attraverso la connessione di rete così come il vostro lavoro ottimizzando le bitmap e memoria.Questo può significare diventare esperti a bassa latenza o trasporto asincrono usando il tuo protocollo di connessione (suppongo http) .Assicurati di non trasportare più dati di quelli che ti servono? La decodifica bitmap può spesso scartare l'80% dei pixel nella creazione di un oggetto per riempire una vista locale

Se i dati destinati alle bitmap sono già locali e non ci sono preoccupazioni sulla latenza della rete, concentrarsi solo su rese rving un tipo di raccolta DStructure (listArray) per contenere i frammenti che l'interfaccia utente scambierà sugli eventi page-forward, page-back.

Se i tuoi jpeg (png senza perdita con le operazioni bitmap IMO) sono intorno a 100k ciascuno, puoi semplicemente utilizzare un adattatore std per caricarli su frammenti. Se sono molto più grandi, allora dovrai capire l'opzione 'comprimi' bitmap da usare con la decodifica per non sprecare un sacco di memoria sulla struttura dei dati dei frammenti.

se è necessario un theadpool per ottimizzare la creazione della bitmap, eseguire tale operazione per rimuovere qualsiasi latenza coinvolta in quel passaggio.

Non sono sicuro che funzioni, ma se vuoi diventare più complicato, potresti cercare di mettere un buffer circolare o qualcosa sotto il listArray che collabora con l'adattatore ??

IMO - una volta che si dispone della struttura, la commutazione della transazione tra i frammenti come pagina deve essere molto veloce. Ho esperienza diretta con circa 6 foto in memoria ciascuna con dimensioni di circa 200k e veloce a pagina-in avanti, pagina indietro.

Ho utilizzato this app come un framework, concentrandomi sull'esempio di 'visualizzatore di pagine'.

+0

Alcuni buoni punti qui. Tuttavia, non capisco completamente cosa intendi con i frammenti. Mi stai suggerendo di passare da un frammento all'altro, ciascuno dei quali precarica una singola immagine? Penso che darebbe troppo sovraccarico per creare frammenti e passare da uno all'altro. –

+0

http://developer.android.com/training/displaying-bitmaps/display-bitmap.html#gridview. è possibile memorizzare bitmap in una raccolta per visualizzazione di pagina e utilizzare un adattatore per gestire il paging. questo esempio memorizza imgRefId nella raccolta. –

4

È necessario eseguire le seguenti operazioni per risolvere questo problema.

  1. Aggiungere una bitmap aggiuntiva per evitare situazioni in cui il thread ui disegna un bitmap mentre un altro thread lo sta modificando.
  2. Implementare la sincronizzazione dei thread per evitare situazioni in cui il thread in background tenta di decodificare una nuova bitmap, ma quella precedente non è stata mostrata dal thread ui.

Ho modificato il codice un po 'e ora funziona correttamente per me.

package com.example.TearingExample; 

import android.app.Activity; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.os.Bundle; 
import android.view.View; 
import android.widget.Button; 
import android.widget.ImageView; 

import java.util.ArrayList; 

public class MainActivity extends Activity { 
    ArrayList<Integer> images = new ArrayList<Integer>(); 

    private Bitmap[] buffers = new Bitmap[2]; 
    private volatile Bitmap current; 

    private final Object lock = new Object(); 
    private volatile boolean ready = true; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     images.add(R.drawable.black); 
     images.add(R.drawable.blue); 
     images.add(R.drawable.green); 
     images.add(R.drawable.grey); 
     images.add(R.drawable.orange); 
     images.add(R.drawable.pink); 
     images.add(R.drawable.red); 
     images.add(R.drawable.white); 
     images.add(R.drawable.yellow); 

     final ImageView imageView = (ImageView) findViewById(R.id.image); 
     final Button goButton = (Button) findViewById(R.id.button); 

     goButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       Runnable runnable = new Runnable() { 
        @Override 
        public void run() { 
         int position = 0; 
         int index = 0; 

         while (true) { 
          try { 
           synchronized (lock) { 
            while (!ready) { 
             lock.wait(); 
            } 
            ready = false; 
           } 

           BitmapFactory.Options options = new BitmapFactory.Options(); 

           options.inSampleSize = 1; 
           options.inBitmap = buffers[index]; 

           buffers[index] = BitmapFactory.decodeResource(getResources(), images.get(position), options); 
           current = buffers[index]; 

           runOnUiThread(new Runnable() { 
            @Override 
            public void run() { 
             imageView.setImageBitmap(current); 
             synchronized (lock) { 
              ready = true; 
              lock.notifyAll(); 
             } 
            } 
           }); 

           position = (position + 1) % images.size(); 
           index = (index + 1) % buffers.length; 

           Thread.sleep(5); 
          } catch (InterruptedException ignore) { 
          } 
         } 
        } 
       }; 
       Thread t = new Thread(runnable); 
       t.start(); 
      } 
     }); 
    } 
} 
+0

Infatti, il metodo setImageBitmap() passa solo l'oggetto bitmap a ImageView e attiva la vista per invalidare, a questo punto non è stato avviato alcun disegno. Quindi può portare a bypassare alcuni frame se la decodifica viene eseguita più velocemente della visualizzazione. –

+0

Ottimo tentativo, vedo miglioramenti - ma sfortunatamente, non risolve il problema al 100% a causa dei motivi che concede Binh Tran. –

7

Si dovrebbe utilizzare il metodo OnDraw() della ImageView da quel metodo viene chiamato quando la vista ha bisogno di disegnare il suo contenuto sullo schermo.

ho creare una nuova classe chiamata MyImageView che estende l'ImageView e l'override del metodo OnDraw(), che attiverà un callback per lasciare l'ascoltatore sa che questo punto di vista ha terminato il suo disegno

public class MyImageView extends ImageView { 

    private OnDrawFinishedListener mDrawFinishedListener; 

    public MyImageView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    @Override 
    protected void onDraw(Canvas canvas) { 
     super.onDraw(canvas); 
     if (mDrawFinishedListener != null) { 
      mDrawFinishedListener.onOnDrawFinish(); 
     } 
    } 

    public void setOnDrawFinishedListener(OnDrawFinishedListener listener) { 
     mDrawFinishedListener = listener; 
    } 

    public interface OnDrawFinishedListener { 
     public void onOnDrawFinish(); 
    } 

} 

Nel MainActivity, definire 3 bitmap: un riferimento alla bitmap utilizzata da ImageView per disegnare, uno per la decodifica e un riferimento alla bitmap che viene riciclato per la successiva decodifica. Ho riutilizzare il blocco sincronizzato dalla risposta di vminorov, ma messo in luoghi diversi con la spiegazione del codice commento

public class MainActivity extends Activity { 

    private Bitmap mDecodingBitmap; 
    private Bitmap mShowingBitmap; 
    private Bitmap mRecycledBitmap; 

    private final Object lock = new Object(); 

    private volatile boolean ready = true; 

    ArrayList<Integer> images = new ArrayList<Integer>(); 
    int position = 0; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     images.add(R.drawable.black); 
     images.add(R.drawable.blue); 
     images.add(R.drawable.green); 
     images.add(R.drawable.grey); 
     images.add(R.drawable.orange); 
     images.add(R.drawable.pink); 
     images.add(R.drawable.red); 
     images.add(R.drawable.white); 
     images.add(R.drawable.yellow); 

     final MyImageView imageView = (MyImageView) findViewById(R.id.image); 
     imageView.setOnDrawFinishedListener(new OnDrawFinishedListener() { 

      @Override 
      public void onOnDrawFinish() { 
       /* 
       * The ImageView has finished its drawing, now we can recycle 
       * the bitmap and use the new one for the next drawing 
       */ 
       mRecycledBitmap = mShowingBitmap; 
       mShowingBitmap = null; 
       synchronized (lock) { 
        ready = true; 
        lock.notifyAll(); 
       } 
      } 
     }); 

     final Button goButton = (Button) findViewById(R.id.button); 

     goButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       Runnable runnable = new Runnable() { 
        @Override 
        public void run() { 
         while (true) { 
          BitmapFactory.Options options = new BitmapFactory.Options(); 
          options.inSampleSize = 1; 

          if (mDecodingBitmap != null) { 
           options.inBitmap = mDecodingBitmap; 
          } 

          mDecodingBitmap = BitmapFactory.decodeResource(
            getResources(), images.get(position), 
            options); 

          /* 
          * If you want the images display in order and none 
          * of them is bypassed then you should stay here and 
          * wait until the ImageView finishes displaying the 
          * last bitmap, if not, remove synchronized block. 
          * 
          * It's better if we put the lock here (after the 
          * decoding is done) so that the image is ready to 
          * pass to the ImageView when this thread resume. 
          */ 
          synchronized (lock) { 
           while (!ready) { 
            try { 
             lock.wait(); 
            } catch (InterruptedException e) { 
             e.printStackTrace(); 
            } 
           } 
           ready = false; 
          } 

          if (mShowingBitmap == null) { 
           mShowingBitmap = mDecodingBitmap; 
           mDecodingBitmap = mRecycledBitmap; 
          } 

          runOnUiThread(new Runnable() { 
           @Override 
           public void run() { 
            if (mShowingBitmap != null) { 
             imageView 
               .setImageBitmap(mShowingBitmap); 
             /* 
             * At this point, nothing has been drawn 
             * yet, only passing the data to the 
             * ImageView and trigger the view to 
             * invalidate 
             */ 
            } 
           } 
          }); 

          try { 
           Thread.sleep(5); 
          } catch (InterruptedException e) { 
          } 

          position++; 
          if (position >= images.size()) 
           position = 0; 
         } 
        } 
       }; 
       Thread t = new Thread(runnable); 
       t.start(); 
      } 
     }); 

    } 
} 
+0

Questo sembra risolvere il problema, ma la risposta di j__m ha risolto il problema più facilmente, quindi assegnerò la risposta corretta. Tuttavia, poiché il premio era vicino alla fine, l'ho premiato perché anche la soluzione sembra funzionare.Se avessi visto la risposta di j__m prima che la taglia si esaurisse, probabilmente la darei a j__m. –

+2

In questo caso, j__m merita la taglia. Ho appena raccolto una taglia con 200 reputazione e la assegnerò a j__m dopo 24 ore. –

+0

@Binh Tran, e tu meriti il ​​mio +1 per questo! –

0

E 'legato alla memorizzazione nella cache delle immagini, l'elaborazione asycTask, sfondo scaricare dalla rete ecc Si prega di leggere questa pagina: http://developer.android.com/training/displaying-bitmaps/index.html

Se si scarica e si analizza il progetto di esempio bitmapfun in quella pagina, sono sicuro che risolverà tutti i problemi. Questo è un campione perfetto.

+0

Puoi inserire le parti rilevanti di quell'articolo collegato nella tua risposta? I collegamenti tendono a spostarsi/scomparire nel tempo, rendendo le risposte inutili. –

+0

Siamo spiacenti, ma il progetto bitmapfun ha ben poco a che fare con la visualizzazione rapida di bitmap e il mantenimento delle prestazioni. –

Problemi correlati