Android de l'erreur de Mémoire avec un Chargement différé des images
J'ai trouvé Fedor code ici et mis en œuvre dans mon projet. La seule différence, c'est que mon application ne dispose pas d'une vue de liste, je suis plutôt accès à 1 image à la fois à partir du serveur. Lorsque l'activité se lance, je l'appelle "DisplayImage(...)" pour afficher la première image. Ensuite, il y a 2 boutons (précédent/suivant) que lorsque l'utilisateur clique dessus, ils l'appellent "DisplayImage(...)".
Il fonctionne très bien pour un peu de temps, mais puis-je obtenir une erreur de Mémoire insuffisante. Au sommet de son code, il a des commentaires que vous pouvez utiliser SoftReference. Je suis en supposant que cela peut résoudre mon problème, non? J'ai joué un peu avec elle un peu, mais quand j'ai essayé de le modifier pour utiliser SoftReference, les images ne jamais charger. Je n'ai jamais utilisé SoftReference avant, donc je me dis que je suis juste en manque de quelque chose. Comment puis-je modifier ce code (ImageLoader) à fixer mon OOM erreur? Est-il une meilleure façon de mettre en cache les images que vous parcourez?
Mise à JOUR:
Voici le code au cas où vous ne souhaitez pas afficher les autres fichiers de la source.
package com.fedorvlasov.lazylist;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Stack;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
public class ImageLoader {
//the simplest in-memory cache implementation. This should be replaced with something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
private HashMap<String, Bitmap> cache=new HashMap<String, Bitmap>();
private File cacheDir;
public ImageLoader(Context context){
//Make the background thead low priority. This way it will not affect the UI performance
photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
//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();
}
final int stub_id=R.drawable.stub;
public void DisplayImage(String url, Activity activity, ImageView imageView)
{
if(cache.containsKey(url))
imageView.setImageBitmap(cache.get(url));
else
{
queuePhoto(url, activity, imageView);
imageView.setImageResource(stub_id);
}
}
private void queuePhoto(String url, Activity activity, ImageView imageView)
{
//This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them.
photosQueue.Clean(imageView);
PhotoToLoad p=new PhotoToLoad(url, imageView);
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
}
//start thread if it's not started yet
if(photoLoaderThread.getState()==Thread.State.NEW)
photoLoaderThread.start();
}
private Bitmap getBitmap(String url)
{
//I identify images by hashcode. Not a perfect solution, good for the demo.
String filename=String.valueOf(url.hashCode());
File f=new File(cacheDir, filename);
//from SD cache
Bitmap b = decodeFile(f);
if(b!=null)
return b;
//from web
try {
Bitmap bitmap=null;
InputStream is=new URL(url).openStream();
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;
}
}
PhotosQueue photosQueue=new PhotosQueue();
public void stopThread()
{
photoLoaderThread.interrupt();
}
//stores list of photos to download
class PhotosQueue
{
private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>();
//removes all instances of this ImageView
public void Clean(ImageView image)
{
for(int j=0 ;j<photosToLoad.size();){
if(photosToLoad.get(j).imageView==image)
photosToLoad.remove(j);
else
++j;
}
}
}
class PhotosLoader extends Thread {
public void run() {
try {
while(true)
{
//thread waits until there are any images to load in the queue
if(photosQueue.photosToLoad.size()==0)
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.wait();
}
if(photosQueue.photosToLoad.size()!=0)
{
PhotoToLoad photoToLoad;
synchronized(photosQueue.photosToLoad){
photoToLoad=photosQueue.photosToLoad.pop();
}
Bitmap bmp=getBitmap(photoToLoad.url);
cache.put(photoToLoad.url, bmp);
Object tag=photoToLoad.imageView.getTag();
if(tag!=null && ((String)tag).equals(photoToLoad.url)){
BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
Activity a=(Activity)photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
}
}
if(Thread.interrupted())
break;
}
} catch (InterruptedException e) {
//allow thread to exit
}
}
}
PhotosLoader photoLoaderThread=new PhotosLoader();
//Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable
{
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i){bitmap=b;imageView=i;}
public void run()
{
if(bitmap!=null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(stub_id);
}
}
public void clearCache() {
//clear memory cache
cache.clear();
//clear SD cache
File[] files=cacheDir.listFiles();
for(File f:files)
f.delete();
}
}
Ici est la même classe après j'ai essayé de mettre en œuvre SoftReference. Je ne pense pas que je l'ai fait, parce que ce n'affiche pas l'image sur l'écran après le chargement.
package com.mycompany.myapp;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Stack;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
public class ImageLoader {
//the simplest in-memory cache implementation. This should be replaced with something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
private HashMap<String, SoftReference<Bitmap>> cache=new HashMap<String, SoftReference<Bitmap>>();
private File cacheDir;
public ImageLoader(Context context){
//Make the background thread low priority. This way it will not affect the UI performance
photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
//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(),"MyApp/Temp");
else
cacheDir=context.getCacheDir();
if(!cacheDir.exists())
cacheDir.mkdirs();
}
final int stub_id = R.drawable.loading;
public void DisplayImage(String url, Activity activity, ImageView imageView)
{
if(cache.containsKey(url)){
imageView.setImageBitmap(null);
System.gc();
imageView.setImageBitmap(cache.get(url).get());
} else {
queuePhoto(url, activity, imageView);
imageView.setImageBitmap(null);
System.gc();
imageView.setImageResource(stub_id);
}
}
private void queuePhoto(String url, Activity activity, ImageView imageView)
{
//This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them.
photosQueue.Clean(imageView);
PhotoToLoad p=new PhotoToLoad(url, imageView);
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
}
//start thread if it's not started yet
if(photoLoaderThread.getState()==Thread.State.NEW)
photoLoaderThread.start();
}
private SoftReference<Bitmap> getBitmap(String url)
{
//I identify images by hashcode. Not a perfect solution, good for the demo.
String filename=String.valueOf(url.hashCode());
File f=new File(cacheDir, filename);
//from SD cache
SoftReference<Bitmap> b = decodeFile(f);
if(b!=null)
return b;
//from web
try {
SoftReference<Bitmap> bitmap=null;
InputStream is=new URL(url).openStream();
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 SoftReference<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=1024;
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 cache.get(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;
}
}
PhotosQueue photosQueue=new PhotosQueue();
public void stopThread()
{
photoLoaderThread.interrupt();
}
//stores list of photos to download
class PhotosQueue
{
private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>();
//removes all instances of this ImageView
public void Clean(ImageView image)
{
for(int j=0 ;j<photosToLoad.size();){
if(photosToLoad.get(j).imageView==image)
photosToLoad.remove(j);
else
++j;
}
}
}
class PhotosLoader extends Thread {
public void run() {
try {
while(true)
{
//thread waits until there are any images to load in the queue
if(photosQueue.photosToLoad.size()==0)
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.wait();
}
if(photosQueue.photosToLoad.size()!=0)
{
PhotoToLoad photoToLoad;
synchronized(photosQueue.photosToLoad){
photoToLoad=photosQueue.photosToLoad.pop();
}
SoftReference<Bitmap> bmp=getBitmap(photoToLoad.url);
cache.put(photoToLoad.url, bmp);
Object tag=photoToLoad.imageView.getTag();
if(tag!=null && ((String)tag).equals(photoToLoad.url)){
BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
Activity a=(Activity)photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
}
}
if(Thread.interrupted())
break;
}
} catch (InterruptedException e) {
//allow thread to exit
}
}
}
PhotosLoader photoLoaderThread=new PhotosLoader();
//Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable
{
SoftReference<Bitmap> bitmap;
ImageView imageView;
public BitmapDisplayer(SoftReference<Bitmap> bmp, ImageView i){bitmap=bmp;imageView=i;}
public void run()
{
if(bitmap!=null){
imageView.setImageBitmap(null);
System.gc();
imageView.setImageBitmap(bitmap.get());
} else {
imageView.setImageBitmap(null);
System.gc();
imageView.setImageResource(stub_id);
}
}
}
public void clearCache() {
//clear memory cache
cache.clear();
//clear SD cache
File[] files=cacheDir.listFiles();
for(File f:files)
f.delete();
}
}
J'ai mis à jour mon post avec la classe ImageLoader. Il a créé d'autres classes qui sont référencés ici, mais je pense que vous pouvez comprendre ce qu'ils font. J'ai juste besoin de modifier ce code pour corriger mes OOM erreur. Merci.
pourriez-vous m'aider sur ce même problème. de son urgente.
OriginalL'auteur Brian | 2011-02-22
Vous devez vous connecter pour publier un commentaire.
Généralement, votre image en mémoire cache déclaration devrait ressembler à quelque chose comme:
Note de votre OOM problèmes ne peuvent pas nécessairement être lié à votre cache Bitmap. Assurez-vous de toujours arrêter/interrompre le thread généré par votre chargeur d'image dans votre Activité de la méthode onDestroy ou dans le finaliser de toute autre classe qui gère.
Utiliser l'Éclipse de la Mémoire de l'Analyseur Outil en conjonction avec DDMS pour analyser votre application est l'utilisation de la mémoire: http://www.eclipse.org/mat/
Aussi loin que l'arrêt du thread dans la méthode onDestroy, je fais appel "imageLoader.stopThread();". Mais ça n'aide pas le problème de mémoire quand je suis dans ma Visionneuse d'Image de l'Activité. C'est la seule classe où j'ai tout de threads ou de traitement en arrière-plan en cours.
En règle générale, vous rendra la vie beaucoup plus facile pour vous-même si vous prenez le temps de comprendre le code à copier-coller dans vos propres projets. Tout d'abord, le cache doit être déclarée statique, il n'existe qu'une seule instance. Deuxièmement, decodeFile et getBitmap méthodes doivent retourner Bitmap, pas SoftReference. Déclarer le soft de référence au point de vous en cache à l'intérieur de PhotoLoader.run():
Bitmap bmp=getBitmap(photoToLoad.url); cache.put(photoToLoad.url, new SoftReference<Bitmap>(bmp));
OK, donc, étant donné la grande taille de vos images, vous devez commencer sérieusement à regarder à l'optimisation de la taille de l'échantillon des options pour votre bitmap comme vous le décoder (dans votre decodeFile méthode), et appeler explicitement le contenu de la (des) méthode lorsque vous avez terminé avec chacun d'eux. L'allocation de mémoire pour les bitmaps dans Android est un sujet délicat, et que je suis probablement pas qualifié pour donner des conseils. De recherche de ce site pour les mêmes questions, cela devrait vous donner quelques bonnes informations.
Lors de l'utilisation SoftReference fixe le OOM erreur pour moi, elle avait encore quelques bugs que je ne pouvais pas travailler. Après le système de nettoyer la SoftReference, il n'a pas nettoyer le cache. Il était de compensation de l'image bitmap, mais pas l'URL. J'ai donc enlevé mon SoftReference puis en a fait alors il stocke uniquement quelques images dans le cache à la fois. Cela semble bien fonctionner et n'obtiens pas de OOM les erreurs de cette façon. Maintenant que je suis en train de taper ceci, je me rends compte que je devrais probablement avoir utilisé SoftReference sur la Chaîne. Qui peuvent avoir résolu le problème que j'ai. Oh bien. Merci à tous pour votre aide Jeff.
OriginalL'auteur Jeff Gilfelt
À fixer mon OOM de l'utilisation de Lazy Loader, j'ai manuellement appelé garbage collection ( Système d'.gc(); ) à chaque fois que je créer ou détruire une page (probablement faire l'un ou l'autre serait bien) parce qu'Android n'a pas toujours l'appeler aussi souvent qu'il le devrait, et utilisé Fedor est clearCache méthode:
La fonction est là, mais n'est jamais appelé par quoi que ce soit, sauf si vous faites usage de. Je le fais dans mes onCreate de la deuxième activité qui utilise le Lazy Load. Vous pouvez également le faire:
Cela ne semble jamais appelée pour moi, mais avec la taille de vos images, vous pourriez avoir besoin d'elle. Aussi pensez à rendre les images, sauf si vous avez une raison de les garder ainsi large.
Ici, c'est pourquoi onLowMemory n'ya pas appelé: "onLowMemory() est appelée lorsque l'ensemble du système est en cours d'exécution hors de la mémoire, non pas lors de votre processus est en cours d'exécution hors de la mémoire. Chaque application est limitée à un montant fixe de RAM (24 MO sur un Nexus One par exemple).Si vous utilisez ces 24 MO, mais le système a encore plus de RAM disponible, vous recevrez un OutOfMemoryError mais pas onLowMemory()." - Romain Guy. Donc, il n'y a pas de point de substitution de cette méthode pour l'application spécifique.
Je ne le savais pas, merci pour l'info
Aussi, vous ne devriez vraiment pas besoin de compter sur
System.gc();
, et, en réalité, il n'a probablement pas beaucoup vous aider. Les Bitmaps ne sont pas stockées sur le Dalvik (Java), elles sont stockées dans le C indigènes de la zone de votre code, et donc du Système.gc() ne peuvent pas les toucher.OriginalL'auteur Cameron
je suis face au même problème et j'utilise le
SoftReference
pour l'image et l'url, mais il semble que le Dalvik VM récupérer SoftReference très rapidement et mon ListView images de garder le scintillement ensuite, j'ai essayéinPurgeable
= true
et je reçois plus deOutOfMemoryException
et monlistview
images ne clignote pas.OriginalL'auteur confucius
envisager le recyclage de la mémoire allouée à l'image bitmap
bitmap.faire du neuf avec();
bitmap=null;
appeler cette fonction lorsque vous n'avez plus besoin d'une image bitmap cela permettra de libérer de la mémoire
OriginalL'auteur Munawwar Hussain Shelia
Vous devez modifier la ligne suivante dans android 2.1 ou ci-dessous.
à ce
OriginalL'auteur Chrishan