Autore Topic: Gestire Bitmap per non avere Out of Memory  (Letto 1049 volte)

Offline Ilgard

  • Utente junior
  • **
  • Post: 64
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    Xperia M
  • Sistema operativo:
    Ubuntu 14.04
Gestire Bitmap per non avere Out of Memory
« il: 20 Novembre 2014, 17:17:28 CET »
0
Salve a tutti,
sto realizzando una app che carica delle immagini da un server e le visualizza.
Volevo dei consigli su come gestire le immagini: per ora le tengo in degli array di Bitmap, ma è una scelta pessima perché già solo 3 immagini mi danno Out of Memory. A me serve di averne almeno 3 per volta caricate (quella corrente, la successiva e la precedente).
Mi farebbe inoltre comodo mantenere quelle già scaricate (perché così se scorro indietro non devo riscaricarle di nuovo).
Qualche consiglio?

Offline Ohmnibus

  • Utente senior
  • ****
  • Post: 712
  • Respect: +150
    • Github
    • Google+
    • @ohmnibus
    • Mostra profilo
    • Lords of Knowledge GdR
  • Dispositivo Android:
    Huawei P9 Lite
  • Play Store ID:
    Ohmnibus
  • Sistema operativo:
    Windows 7 x64
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #1 il: 20 Novembre 2014, 17:45:38 CET »
0
Puoi salvarle nella memoria interna con un nome che stabilisci tu (es. "001.jpg", "002.jpg", ecc.), in modo che in RAM devi mantenere solo in nomi dei file.

Vedi Storage Options | Android Developers

Quest'area di memoria è privata e ci può accedere solo la tua app.

Mi raccomando ricorda di cancellare le immagini che non ti servono più ;)
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 Ilgard

  • Utente junior
  • **
  • Post: 64
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    Xperia M
  • Sistema operativo:
    Ubuntu 14.04
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #2 il: 21 Novembre 2014, 15:10:07 CET »
0
Grazie, mi serviva proprio qualcosa del genere. Farò un po' di prove e farò sapere com'è andata :D

Offline Ilgard

  • Utente junior
  • **
  • Post: 64
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    Xperia M
  • Sistema operativo:
    Ubuntu 14.04
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #3 il: 25 Novembre 2014, 17:58:56 CET »
0
Allora, sto un po' provando a continuo ad avere problemi, probabilmente perché ho sbagliato qualcosa.
Gestisco un'activity con più fragment, ognuno con una immagine.
Le immagini le scarico tutte (per semplicità per ora le prendo da file di risorsa), le salvo in memoria e poi mi tengo i riferimenti dei nomi, in questo modo:
Codice (Java): [Seleziona]
for (int i = 0; i < 10; i++){                
                String FILENAME = Integer.toString(i) + ".jpg";

                FileOutputStream fos;
                try {
                    fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
                    Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(riferimenti[i])).getBitmap();
                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
                    byte[] bitmapdata = stream.toByteArray();                
                    fos.write(bitmapdata);

                    fos.close();
                   
                    bitmap.recycle();
                    stream.close();
                }
                catch (FileNotFoundException e) {e.printStackTrace();}
                catch (IOException e) {e.printStackTrace();    }                

                immagini[i] = FILENAME;
            }

Nei fragment poi, nella onCreate, prendo i file e li trasformo in bitmap

Codice (Java): [Seleziona]
FileInputStream fis = null;
               
        if (defaultWP == null) {
            new BitmapFactory();
            bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default);
        } else{
            try {
                fis = context.openFileInput(defaultWP);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            bitmap = BitmapFactory.decodeStream(fis);
        }
       
        ((ImageView) view.findViewById(R.id.wallpaper)).setBackground(new BitmapDrawable(getResources(), bitmap));

Così e basta, a rigor di logica, tutte le bitmap mi rimangono nello heap, quindi la mia idea è stata quella di modificare la onPause così:
Codice (Java): [Seleziona]
@Override
    public void onPause (){
        super.onPause();
        bitmap.recycle();
        bitmap = null;
    }

Di sicuro me la chiama, solo che o non è sufficiente o non funziona (sembra non influenzare minimamente la cosa).
Qualche consiglio?

ps: uso un ViewPager per i fragments.

Offline arlabs

  • Utente normale
  • ***
  • Post: 434
  • Respect: +49
    • Mostra profilo
  • Dispositivo Android:
    GalaxyS6, Nexus5
  • Play Store ID:
    AR Labs
  • Sistema operativo:
    Windows 10
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #4 il: 26 Novembre 2014, 10:53:49 CET »
0

Offline Ilgard

  • Utente junior
  • **
  • Post: 64
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    Xperia M
  • Sistema operativo:
    Ubuntu 14.04
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #5 il: 26 Novembre 2014, 17:13:59 CET »
0
Proverò con la cache ma il problema non dovrebbe risiedere lì: salvare in cache invece che in RAM renderà l'app più veloce, ma la gestione della memoria heap resta la stessa quindi penso che avrei comunque OutOfMemory.

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #6 il: 26 Novembre 2014, 17:54:34 CET »
0
Suggerimenti sparsi:
1- non selezionare 100 quando comprimi, prova con 85-90. Questo non risolve di certo il problema della memoria (che il file sia 100kb o 100mb non cambia niente se la risoluzione è la stessa) ma è sempre meglio ottimizzare quando possibile.
2- fai un detect della risoluzione del device e metti un limite così da impostare la risoluzione prima di caricarla in memoria. Se lo schermo è 480x800 è inutile caricare una immagine di 1080x1920.
3- assicurati che la onpause venga chiamata. Piazza un log.e nella onpause e verifica se ci passa. Non ne sono convinto. Per esempio in un viewpager si tengono attivi 3 fragment.
4- Il problema è che tu hai due reference all'immagine. Uno è quello che hai settato come background, l'altro è l'oggetto bitmap. Secondo me stai riciclando l'oggetto bitmap ma non il nuovo bitmapdrawable che hai settato come background. Dovresti non solo fare il recycle ma anche resettare il background passando null, così da perdere il reference al drawable. Altrimenti che tu passi o meno dalla onpause, il background della imageview rimane settato. Il metodo non è fail-proof perché quando dereferenzi il background devi attendere che il garbage collector faccia il suo lavoro.

 :-)

Offline arlabs

  • Utente normale
  • ***
  • Post: 434
  • Respect: +49
    • Mostra profilo
  • Dispositivo Android:
    GalaxyS6, Nexus5
  • Play Store ID:
    AR Labs
  • Sistema operativo:
    Windows 10
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #7 il: 26 Novembre 2014, 19:06:13 CET »
0
Proverò con la cache ma il problema non dovrebbe risiedere lì: salvare in cache invece che in RAM renderà l'app più veloce, ma la gestione della memoria heap resta la stessa quindi penso che avrei comunque OutOfMemory.

Non me lo sono riletto tutto. Ma mi pareva che lì, o nella pagina dopo, fornissero un esempio per riciclare Bitmap ed evitare di caricarsene 1000 in memoria... sarebbe da rileggere con calma...

Offline Ilgard

  • Utente junior
  • **
  • Post: 64
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    Xperia M
  • Sistema operativo:
    Ubuntu 14.04
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #8 il: 27 Novembre 2014, 10:56:47 CET »
0
Spulciando un po' sul tutorial ho modificato il codice non sfruttando più bitmap ma file di risorsa (nel complesso mi cambia poco, tanto le bitmap scaricate dovrei comunque metterle da qualche parte) ed il problema si presenta lo stesso.
L'idea di fare lo scaling delle immagini è buona (lo consigliano anche sul tutorial) e, in questo specifico caso, potrebbe anche risolvermi il problema, ma con display a risoluzione più alta non mi cambierebbe nulla.

Ho alterato il codice semplificandolo parecchio: nell'activity principale creo il ViewPager
Codice (Java): [Seleziona]
PageFragment fragment = new PageFragment();
    private PagerAdapter mPagerAdapter;
    private ViewPager mPager;
    static int indice = 0;
    int l = riferimenti.length;

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

        Display display = getWindowManager().getDefaultDisplay();
                Point size = new Point();
                display.getSize(size);
                int width = size.x;
                int height = size.y;
                fragment = new PageFragment(width, height);

        this.mPagerAdapter = new PagerAdapter(super.getSupportFragmentManager(), l);
        mPager = (ViewPager) super.findViewById(R.id.pager);
        mPager.setAdapter(this.mPagerAdapter);
        mPager.setCurrentItem(0);
    }

Nella classe PageFragment gestisco il Fragment:
Codice (Java): [Seleziona]
public class PageFragment extends Fragment{
        public String defaultWP = null;
        public int mImageNum;
        ImageView mImageView;
        ViewGroup mRootView;
        static int larghezza;
        static int altezza;
        static final BitmapFactory.Options options = new BitmapFactory.Options();

        static PageFragment newInstance(int imageNum) {
                final PageFragment f = new PageFragment(larghezza, altezza);
                final Bundle args = new Bundle();
                args.putInt("IMAGE_DATA_EXTRA", imageNum);
                f.setArguments(args);
                return f;
        }

        public PageFragment(int width, int height) {
                larghezza = width;
                altezza = height;
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                mImageNum = getArguments() != null ? getArguments().getInt("IMAGE_DATA_EXTRA") : -1;
        }

        @Override
        public void onPause(){
                super.onPause();
                mImageView.setImageBitmap(null);
        }

        @Override
        public void onDestroy() {
                super.onDestroy();
                mImageView.setImageBitmap(null);
        };


        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
                final View v = inflater.inflate(R.layout.frag_layout, container, false);
                mImageView = (ImageView) v.findViewById(R.id.wallpaper);               
                return v;
        }

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
                super.onActivityCreated(savedInstanceState);
                final int resId = MercuryActivity.riferimenti[mImageNum];              

                mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), resId, larghezza, altezza));
        }

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

        public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {

                // First decode with inJustDecodeBounds=true to check dimensions
                options.inJustDecodeBounds = true;
               
                // Calculate inSampleSize
                options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

                // Decode bitmap with inSampleSize set
                options.inJustDecodeBounds = false;
                return BitmapFactory.decodeResource(res, resId, options);
        }

}

Ed il PageAdapter è un FragmentStatePagerAdapter:
Codice (Java): [Seleziona]
public class PagerAdapter extends FragmentStatePagerAdapter {

    private final int mSize;

    public PagerAdapter(FragmentManager fm, int size) {
        super(fm);
        mSize = size;
    }

    @Override
    public int getCount() {
        return mSize;
    }

    @Override
    public Fragment getItem(int position) {
        return PageFragment.newInstance(position);
    }
}

La onPause viene chiamata subito prima che si generi l'eccezione (la memoria heap regge 3 immagini al più, a patto di non scalarle).
Ne viene chiamata una sola, quindi quando sono alla terza pagina la prima chiama la onPause (presumo).
Nella onPause ho provato ad inserire
Codice (Java): [Seleziona]
mImageView.setImageDrawable(null);ma non ci sono state differenze.
Altri riferimenti da rilasciare non li trovo.

EDIT: ho provato a fare il resize delle immagini (il codice sopra è aggiornato) ma non cambia nulla. I dati sono corretti, ma già con il display dell'emulatore (480x800) ottengo OutOfMemory.
« Ultima modifica: 27 Novembre 2014, 12:23:15 CET da Ilgard »

Offline Ilgard

  • Utente junior
  • **
  • Post: 64
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    Xperia M
  • Sistema operativo:
    Ubuntu 14.04
Re:Gestire Bitmap per non avere Out of Memory
« Risposta #9 il: 02 Dicembre 2014, 17:35:08 CET »
0
Allora, tramite qualche rimaneggiamento del codice ho risolto il problema. Non ho ben capito come, comunque non faceva correttamente il ridimensionamento.
Tra l'altro ho deciso di salvare le bitmap scaricate direttamente su database (tanto dopo avrei comunque dovuto farlo) e poi accederle da lì.
Secondo voi, ai fini della velocità, è sensato tenersele anche in RAM oppure non avrei vantaggi sensibili?