Autore Topic: Popolare listView/greedView con molte bitmpa  (Letto 450 volte)

Offline tonno16

  • Utente storico
  • *****
  • Post: 1197
  • Respect: +58
    • Mostra profilo
  • Dispositivo Android:
    moto g
  • Play Store ID:
    Diego Tonini
  • Sistema operativo:
    OpenSuse
Popolare listView/greedView con molte bitmpa
« il: 26 Dicembre 2014, 11:14:40 CET »
0
Save a tutti. Ho la necessità di popolare una listView/greedView con una serie di bitmpa. Precisamente la cartella /SD/DCIM/Camera.

Ottengo una lista di File, List<File> files, che passo direttamente al mio CustomAdapter.
L'adapter funziona bene, in quanto in presenza di pochi bitmap, funziona perfettamente. Nella mia cartella ce ne sono 350 circa. I problemi iniziano.
Ho seguito dettaggliatamente tutta la parte relativa ai bitmap come sul sito android developer.

http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

Ho implementato tuttii i metodi come dal sito. Unica differenza è che li il tutto fa affidamento a "decodeBitmapFromResource()", mentre per le mie esigenze io devo usare "decodeBitmapFromFile()".
Inoltre ho anche implementato l'uso della LruCache<> come appunto suggerito nel sito
Per cui vi posto il mio Adater:

Codice (Java): [Seleziona]
public class FileAdapter extends BaseAdapter {

    private int resource;
    private LayoutInflater inflater;
    private Context mContext;
    private List<MyFile> mFileList;
    private LruCache<String, Bitmap> mMemoryCache;

    public FileAdapter(Context context, int resourceId, List<MyFile> objects) {
        //super(context, resourceId, objects);
        mContext = context;
        mFileList = objects;
        resource = resourceId;
        inflater = LayoutInflater.from(context);

        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = maxMemory / 8;

        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in bytes rather than number
                // of items.
                return bitmap.getByteCount();
            }
        };

    }

    @Override
    public int getCount() {
        return mFileList.size();
    }

    @Override
    public MyFile getItem(int i) {
        return mFileList.get(i);
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int position,View convertView, ViewGroup parent) {

        final MyFile file = getItem(position);
        ViewHolder holder;

        if (convertView == null) {
            holder = new ViewHolder();
            convertView = inflater.inflate(resource, parent, false);
            holder.setImgRowIcon((ImageView) convertView.findViewById(R.id.imgRowIcon));
            //holder.tvRowName = (AutoResizeTextView) convertView.findViewById(R.id.tvRowName);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        //holder.tvRowName.setText(file.getName()+file.getNumberChild());

        /*if(file.isHidden()){
            holder.getImgRowIcon().setAlpha(0.4f);
        } else {
            holder.getImgRowIcon().setAlpha(1f);
        }*/


        loadBitmap(holder.imgRowIcon,file.getFile());

        return convertView;
    }


    private static class ViewHolder {
        //AutoResizeTextView tvRowName;
        private ImageView imgRowIcon;

        public void setImgRowIcon(ImageView imgRowIcon) {
            this.imgRowIcon = imgRowIcon;
        }
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

    private static Bitmap decodeSampledBitmapFromResource(File file, int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getPath(), options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(file.getPath(), options);
    }

    class BitmapWorkerTask extends AsyncTask<File, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;
        private File data;

        public BitmapWorkerTask(ImageView imageView) {
            // Use a WeakReference to ensure the ImageView can be garbage collected
            imageViewReference = new WeakReference<ImageView>(imageView);
        }

        // Decode image in background.
        @Override
        protected Bitmap doInBackground(File... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(data, 80, 80);
        }

        // Once complete, see if ImageView is still around and set bitmap.
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (isCancelled()) {
                bitmap = null;
            }

            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                final BitmapWorkerTask bitmapWorkerTask =
                        getBitmapWorkerTask(imageView);
                if (this == bitmapWorkerTask && imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }

    public void loadBitmap(ImageView imageView, File file) {
        if (cancelPotentialWork(file, imageView)) {
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(decodeSampledBitmapFromResource(file,80,80), task);
            imageView.setImageDrawable(asyncDrawable);
            task.execute(file);
        }
    }

    static class AsyncDrawable extends BitmapDrawable {
        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

        public AsyncDrawable(Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
            super(bitmap);
            bitmapWorkerTaskReference =
                    new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
        }

        public BitmapWorkerTask getBitmapWorkerTask() {
            return bitmapWorkerTaskReference.get();
        }
    }

    public static boolean cancelPotentialWork(File data, ImageView imageView) {
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

        if (bitmapWorkerTask != null) {
            final File bitmapData = bitmapWorkerTask.data;
            // If bitmapData is not yet set or it differs from the new data
            if (bitmapData != data) {
                // Cancel previous task
                bitmapWorkerTask.cancel(true);
            } else {
                // The same work is already in progress
                return false;
            }
        }
        // No task associated with the ImageView, or an existing task was cancelled
        return true;
    }

    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
        if (imageView != null) {
            final Drawable drawable = imageView.getDrawable();
            if (drawable instanceof AsyncDrawable) {
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getBitmapWorkerTask();
            }
        }
        return null;
    }
}

Ora, il mio problema è il seguente: Nonostante la UI sia veramente fluida, nel momento del primo avvio dell' app adapter popola la griglia/lista correttamente, mettendoci circa 3 decimi di secondo per ogni imageView.  Ora, se scrollo in basso e poi scrollo in alto, le immagini vengono alterate, quindi cosa c'era in prima posizione ora non c'è più. Inoltre anche senza fare niente, le immagini si alterano e scambiano totalmente in autonomia senza che io interagisca col telefono.

Non capisco dove sbaglio.
MI viene in mente la classe AsyncDrawable. Essa guardando i possibili costruttore,  quasi tutti i costruttori sono deprecati. L'unico costruttore che accetta anche una String relativa alla path del file, necessita anche di un oggetto di tipo Resource. Quindi nel mio codice ho usato un costrutto deprecato in quanto io non ho alcuna immagine prelevabile da resource.

Idee?

Offline Ohmnibus

  • Utente senior
  • ****
  • Post: 616
  • Respect: +136
    • Google+
    • @ohmnibus
    • Mostra profilo
    • Lords of Knowledge GdR
  • Dispositivo Android:
    Samsung Galaxy Nexus
  • Play Store ID:
    Ohmnibus
  • Sistema operativo:
    Windows 7 x64
Re:Popolare listView/greedView con molte bitmpa
« Risposta #1 il: 28 Dicembre 2014, 15:12:33 CET »
+1
Non so se è quello il problema, ma intanto mi salta all'occhio un errore "di concetto" in loadBitmap(ImageView imageView, File file): il primo parametro nella chiamata new AsyncDrawable(decodeSampledBitmapFromResource(file,80,80), task); dovrebbe essere un placeholder (tipo uno spinner o qualcosa del genere) e non l'immagine che stai andando a caricare.

Ti suggerisco di cambiare la loadBitmap in:
Codice (Java): [Seleziona]
    public void loadBitmap(ImageView imageView, File file) {
        if (cancelPotentialWork(file, imageView)) {
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
            final Bitmap placeHolder = ((BitmapDrawable)imageView.getContext().getResource().getDrawable(R.drawable.ic_placeholder)).getBitmap();
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(placeHolder, task);
            imageView.setImageDrawable(asyncDrawable);
            task.execute(file);
        }
    }

Post unito: 29 Dicembre 2014, 01:18:52 CET
Un'altra cosa:

nel metodo onPostExecute, prova a cambiare

Codice (Java): [Seleziona]
imageView.setImageBitmap(bitmap);
con

Codice (Java): [Seleziona]
imageView.setImageDrawable(null); //Rimuove il riferimento all'eventuale AsyncDrawable
imageView.setImageBitmap(bitmap);

« Ultima modifica: 29 Dicembre 2014, 01:18:52 CET da Ohmnibus, Reason: Merged DoublePost »
Ohmnibus
Le mie app su Play Store

È stata trovata una soluzione al tuo problema? Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato.

Offline tonno16

  • Utente storico
  • *****
  • Post: 1197
  • Respect: +58
    • Mostra profilo
  • Dispositivo Android:
    moto g
  • Play Store ID:
    Diego Tonini
  • Sistema operativo:
    OpenSuse
Re:Popolare listView/greedView con molte bitmpa
« Risposta #2 il: 11 Gennaio 2015, 18:12:37 CET »
0
Ho avuto da fare. Grazie della risposta. Entro sta sera proverò. In ogni caso mi chiedo come facciano tutti i file manager (quasi tutti) a elaborare cosi tante immagine in maniera fluida.

Post unito: 11 Gennaio 2015, 19:24:11 CET
OK. Devo ammette che ora funziona molto meglio. Ho commesso un errorre madornale, grazie per averlo segnalato. Ora 9 immagini (sul mio schermo ci stanno 9 item) vengono caricate in 2 secondi. Potrei e vorrei fare molto meglio. Ora vedo se comprimendo ulteriormente ottengo di meglio. Inoltre ho notato una differenza fra la cartella DCIM 400 foto circa e quella di what app 380 circa. Quest' ultima ha un tempo di caricamento notevolmente minore.
« Ultima modifica: 11 Gennaio 2015, 19:24:11 CET da tonno16, Reason: Merged DoublePost »

Offline Ohmnibus

  • Utente senior
  • ****
  • Post: 616
  • Respect: +136
    • Google+
    • @ohmnibus
    • Mostra profilo
    • Lords of Knowledge GdR
  • Dispositivo Android:
    Samsung Galaxy Nexus
  • Play Store ID:
    Ohmnibus
  • Sistema operativo:
    Windows 7 x64
Re:Popolare listView/greedView con molte bitmpa
« Risposta #3 il: 12 Gennaio 2015, 09:37:40 CET »
0
Nota che il caricamento asincrono non serve per caricare le immagini più velocemente (anzi semmai il contrario), ma piuttosto per non bloccare la visualizzazione durante il caricamento.

Io ho adottato il caricamento asincrono in un listview con delle icone. La differenza è che ora lo scrolling del listview è molto più fluido (mentre prima si "inceppava"), ma le icone sono caricate con un leggero ritardo.
Ohmnibus
Le mie app su Play Store

È stata trovata una soluzione al tuo problema? Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato.

Offline tonno16

  • Utente storico
  • *****
  • Post: 1197
  • Respect: +58
    • Mostra profilo
  • Dispositivo Android:
    moto g
  • Play Store ID:
    Diego Tonini
  • Sistema operativo:
    OpenSuse
Re:Popolare listView/greedView con molte bitmpa
« Risposta #4 il: 12 Gennaio 2015, 09:58:02 CET »
0
Momento, momento. So perfettamente cosa comporta l'uso di un thread asincrono.
Forse mi sono spiegato male. Altri file manager caricano le stesse immagini con una velocità maggiore. Circa 2x 3x rispetto alla mia implementazione. Mi chiedevo quale fosse un possobile accorgimento per arrivare a tale risultato.

Ho comunque fatto uso della LruCache come suggerito sul sito developer (argomento dopo i bitmap). Il tutto si è ancora migliorato, difatti le immagini vengono caricate una sola volta.