2014-05-20 18 views
9

Sto usando la libreria universal-image-loader per caricare immagini, ma quando chiamo copy() su un file bitmap caricato in alcuni casi ottengo OutOfMemoryError. Ecco il mio codice:bitmap.copy() elimina l'errore di memoria

ImageLoader.getInstance().loadImage(path, new ImageLoadingListener() { 

     @Override 
     public void onLoadingStarted(String arg0, View arg1) { 
      // TODO Auto-generated method stub 

     } 

     @Override 
     public void onLoadingFailed(String arg0, View arg1, FailReason arg2) { 
      // TODO Auto-generated method stub 

     } 

     @Override 
     public void onLoadingComplete(String arg0, View arg1, Bitmap arg2) { 
      bm = arg2; 
     } 

     @Override 
     public void onLoadingCancelled(String arg0, View arg1) { 
      // TODO Auto-generated method stub 

     } 
    }); 
Bitmap bm2= bm.copy(Bitmap.Config.ARGB_8888, true); //where the crash happens 

mi serve il secondo Bitmap non essere mutabile in modo da poter disegnare su di esso.

+0

Questi casi potrebbero verificarsi quando le immagini sono grandi. Quanto sono grandi le dimensioni delle tue immagini? – Emmanuel

+0

Inoltre, sei sicuro di non conservare altri bitmap in memoria mentre questo non è utile? (questo spesso accade) - Questo documento può anche essere utile: http://developer.android.com/training/displaying-bitmaps/manage-memory.html – Shlublu

+0

beh sto usando il caricatore di immagini per caricare le immagini che non ho mai avuto problemi con esso. il problema si verifica solo quando si chiama copy() sulla bitmap. – user1940676

risposta

13

Prima di tutto cercare di trovare un po 'di tempo per leggere una buona documentazione ufficiale circa le bitmap: Displaying Bitmaps Efficiently

vi darà capire perché e quando java.lang.OutofMemoryError accade. E come evitarlo.

E la tua domanda: vedi questo articolo: Android: convert Immutable Bitmap into Mutable

But from API Level 11 only options.inMutable available to load the file into a mutable bitmap.

So, if we are building application with API level less than 11, then we have to find some other alternatives.

One alternative is creating another bitmap by copying the source

bitmap. mBitmap = mBitmap.copy(ARGB_8888 ,true);

But the will throw OutOfMemoryException if the source file is big. Actually incase if we want to edit an original file, then we will face this issue. We should be able to load at-least image into memory, but most we can not allocate another copy into memory.

So, we have to save the decoded bytes into some where and clear existing bitmap, then create a new mutable bitmap and load back the saved bytes into bitmap again. Even to copy bytes we cannot create another ByteBuffer inside the memory. In that case need to use MappedByteBuffer that will allocate bytes inside a disk file.

Following code would explain clearly:

//this is the file going to use temporally to save the bytes. 

File file = new File("/mnt/sdcard/sample/temp.txt"); 
file.getParentFile().mkdirs(); 

//Open an RandomAccessFile 
/*Make sure you have added uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" 
into AndroidManifest.xml file*/ 
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); 

// get the width and height of the source bitmap. 
int width = bitmap.getWidth(); 
int height = bitmap.getHeight(); 

//Copy the byte to the file 
//Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888; 
FileChannel channel = randomAccessFile.getChannel(); 
MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, width*height*4); 
bitmap.copyPixelsToBuffer(map); 
//recycle the source bitmap, this will be no longer used. 
bitmap.recycle(); 
//Create a new bitmap to load the bitmap again. 
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); 
map.position(0); 
//load it back from temporary 
bitmap.copyPixelsFromBuffer(map); 
//close the temporary file and channel , then delete that also 
channel.close(); 
randomAccessFile.close(); 

E here è il codice di esempio.

+1

Ricevo ancora un OutOfMemoryError usando questa soluzione. Succede su questa riga: 'bitmap = Bitmap.createBitmap (larghezza, altezza, Config.ARGB_8888);' – Andy

+0

bitmap = Bitmap.createScaledBitmap (originalBitmap, width, height, true); – Faakhir

+1

il collegamento del codice di esempio è rotto – lxknvlk

3

Non si può fare molto a proposito dell'errore di memoria dei bitmap tranne che per garantire che la bitmap che si sta copiando o visualizzando non sia molto grande. Fortunatamente imageloader universale ha una funzione per comprimere bitmap cambiando la configurazione. Quindi dai una prova a Bitmap.Config.RGG_565. Si suppone che metà dell'impronta della memoria della bitmap. È anche possibile richiedere una dimensione heap elevata. Un'altra cosa che puoi fare è copiare la versione ridimensionata della bitmap.

2

Come argomentazione di Illegel, è necessario assicurarsi che Bitmap non sia TROPPO grande. Inoltre, assicurati di caricare solo una bitmap alla volta nella memoria.

è possibile scalare dinamicamente il bitmap utilizzando BitmapFactory

Bitmap b = BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.length) 
image.setImageBitmap(Bitmap.createScaledBitmap(b, 300, 300, false)); 
2

essere grati che accade sul vostro dispositivo, e non solo sui dispositivi dell'utente.

1) Questo è qualcosa che devi affrontare e reagire in modo appropriato. Visualizza il messaggio di errore o carica la risoluzione inferiore della bitmap. La tua app funzionerà su molti tipi di dispositivi, ognuno con una diversa quantità di memoria.

2) Utilizzare l'importante funzione Bitmap.recycle dopo ogni operazione che rende ridondante la vecchia bitmap. Ciò libererà immediatamente la memoria per il prossimo lavoro senza attendere l'esecuzione di GC e possibili errori di memoria esaurita.

+0

Sì, questo è un buon consiglio, ma [ricorda] (http://developer.android.com/training/displaying-bitmaps/manage-memory.html#recycle): dovresti usare 'recycle()' solo quando sei sicuro che la bitmap non viene più utilizzata. Se si chiama 'recycle()' e in seguito si tenta di disegnare la bitmap, si otterrà l'errore: '" Canvas: provare ad usare una bitmap riciclata "'. –

+0

Certo, i documenti lo scrivono chiaramente. –

1

utilizzare questo codice a fullfill il vostro scopo

Effettuare le seguenti classi nel codice e in ultimo utilizzo ImageLoader per caricare url passarlo url, ImageView e dra è possibile visualizzare l'url in caso non restituisca alcuna immagine

FileCache.java

public class FileCache { 

private File cacheDir; 

public FileCache(Context context){ 
    //Find the dir to save cached images 
    if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) 
     cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"LazyList"); 
    else 
     cacheDir=context.getCacheDir(); 
    if(!cacheDir.exists()) 
     cacheDir.mkdirs(); 
} 

public File getFile(String url){ 
    //I identify images by hashcode. Not a perfect solution, good for the demo. 
    String filename=String.valueOf(url.hashCode()); 
    //Another possible solution (thanks to grantland) 
    //String filename = URLEncoder.encode(url); 
    File f = new File(cacheDir, filename); 
    return f; 

} 

public void clear(){ 
    File[] files=cacheDir.listFiles(); 
    if(files==null) 
     return; 
    for(File f:files) 
     f.delete(); 
} 

} 

MemoryCache.java

public class MemoryCache { 
private Map<String, SoftReference<Bitmap>> cache=Collections.synchronizedMap(new HashMap<String, SoftReference<Bitmap>>()); 

public Bitmap get(String id){ 
    if(!cache.containsKey(id)) 
     return null; 
    SoftReference<Bitmap> ref=cache.get(id); 
    return ref.get(); 
} 

public void put(String id, Bitmap bitmap){ 
    cache.put(id, new SoftReference<Bitmap>(bitmap)); 
} 

public void clear() { 
    cache.clear(); 
} 
} 

Utils.java

public class Utils { 
public static void CopyStream(InputStream is, OutputStream os) 
{ 
    final int buffer_size=1024; 
    try 
    { 
     byte[] bytes=new byte[buffer_size]; 
     for(;;) 
     { 
      int count=is.read(bytes, 0, buffer_size); 
      if(count==-1) 
       break; 
      os.write(bytes, 0, count); 
     } 
    } 
    catch(Exception ex){} 
} 
} 

ImageLoader.java

public class ImageLoader { 

MemoryCache memoryCache=new MemoryCache(); 
FileCache fileCache; 
private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>()); 
ExecutorService executorService; 

public ImageLoader(Context context){ 
    fileCache=new FileCache(context); 
    executorService=Executors.newFixedThreadPool(5); 
} 

final int stub_id = R.drawable.no_image; 
public void DisplayImage(String url, ImageView imageView) 
{ 
    imageViews.put(imageView, url); 
    Bitmap bitmap=memoryCache.get(url); 
    if(bitmap!=null) 
     imageView.setImageBitmap(bitmap); 
    else 
    { 
     queuePhoto(url, imageView); 
     imageView.setImageResource(stub_id); 
    } 
} 

private void queuePhoto(String url, ImageView imageView) 
{ 
    PhotoToLoad p=new PhotoToLoad(url, imageView); 
    executorService.submit(new PhotosLoader(p)); 
} 

private Bitmap getBitmap(String url) 
{ 
    File f=fileCache.getFile(url); 

    //from SD cache 
    Bitmap b = decodeFile(f); 
    if(b!=null) 
     return b; 

    //from web 
    try { 
     Bitmap bitmap=null; 
     URL imageUrl = new URL(url); 
     HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection(); 
     conn.setConnectTimeout(30000); 
     conn.setReadTimeout(30000); 
     conn.setInstanceFollowRedirects(true); 
     InputStream is=conn.getInputStream(); 
     OutputStream os = new FileOutputStream(f); 
     Utils.CopyStream(is, os); 
     os.close(); 
     bitmap = decodeFile(f); 
     return bitmap; 
    } catch (Exception ex){ 
     ex.printStackTrace(); 
     return null; 
    } 
} 

//decodes image and scales it to reduce memory consumption 
private Bitmap decodeFile(File f){ 
    try { 
     //decode image size 
     BitmapFactory.Options o = new BitmapFactory.Options(); 
     o.inJustDecodeBounds = true; 
     BitmapFactory.decodeStream(new FileInputStream(f),null,o); 

     //Find the correct scale value. It should be the power of 2. 
     final int REQUIRED_SIZE=70; 
     int width_tmp=o.outWidth, height_tmp=o.outHeight; 
     int scale=1; 
     while(true){ 
      if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE) 
       break; 
      width_tmp/=2; 
      height_tmp/=2; 
      scale*=2; 
     } 

     //decode with inSampleSize 
     BitmapFactory.Options o2 = new BitmapFactory.Options(); 
     o2.inSampleSize=scale; 
     return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 
    } catch (FileNotFoundException e) {} 
    return null; 
} 

//Task for the queue 
private class PhotoToLoad 
{ 
    public String url; 
    public ImageView imageView; 
    public PhotoToLoad(String u, ImageView i){ 
     url=u; 
     imageView=i; 
    } 
} 

class PhotosLoader implements Runnable { 
    PhotoToLoad photoToLoad; 
    PhotosLoader(PhotoToLoad photoToLoad){ 
     this.photoToLoad=photoToLoad; 
    } 

    @Override 
    public void run() { 
     if(imageViewReused(photoToLoad)) 
      return; 
     Bitmap bmp=getBitmap(photoToLoad.url); 
     memoryCache.put(photoToLoad.url, bmp); 
     if(imageViewReused(photoToLoad)) 
      return; 
     BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad); 
     Activity a=(Activity)photoToLoad.imageView.getContext(); 
     a.runOnUiThread(bd); 
    } 
} 

boolean imageViewReused(PhotoToLoad photoToLoad){ 
    String tag=imageViews.get(photoToLoad.imageView); 
    if(tag==null || !tag.equals(photoToLoad.url)) 
     return true; 
    return false; 
} 

//Used to display bitmap in the UI thread 
class BitmapDisplayer implements Runnable 
{ 
    Bitmap bitmap; 
    PhotoToLoad photoToLoad; 
    public BitmapDisplayer(Bitmap b, PhotoToLoad p){bitmap=b;photoToLoad=p;} 
    public void run() 
    { 
     if(imageViewReused(photoToLoad)) 
      return; 
     if(bitmap!=null) 
      photoToLoad.imageView.setImageBitmap(bitmap); 
     else 
      photoToLoad.imageView.setImageResource(stub_id); 
    } 
} 

public void clearCache() { 
    memoryCache.clear(); 
    fileCache.clear(); 
} 

} 

chiamata questo codice in cui si desidera memorizzare nella cache o il download o il gestore immagini

imageLoader.DisplayImage(song.get(CustomizedListView.KEY_THUMB_URL), thumb_image);