图像下载:使用LazyList的内存泄漏 [英] image download: memory leak using LazyList

查看:161
本文介绍了图像下载:使用LazyList的内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用

I've got memory leak using LazyList. I use one instance of ImageLoader in whole app, I create it in Application.onCreate(), because I need image downloading in several activities: list activity, one activity with gallery widget and full-screen gallery activitiy(all of them use same cache) I modified image loader so it uses SoftReference-based HashMap. Here's code for SoftHashMap:

public class SoftHashMap extends AbstractMap {

    private final Map hash=new HashMap();
    private final int HARD_SIZE;
    private final LinkedList hardCache=new LinkedList();
    private final ReferenceQueue queue=new ReferenceQueue();
    
    public SoftHashMap(){
        this(100);
    }
    
    public SoftHashMap(int hardSize){
        HARD_SIZE=hardSize;
    }
    
    public Object get(Object key){
        Object result=null;
        SoftReference soft_ref=(SoftReference)hash.get(key);
        if(soft_ref!=null){
            result=soft_ref.get();
            if(result==null){
                hash.remove(key);
            }else{
                hardCache.addFirst(result);
                if(hardCache.size()>HARD_SIZE){
                    hardCache.removeLast();
                }
            }
        }
        return result;
    }
    private static class SoftValue extends SoftReference{
        private final Object key;
        public SoftValue(Object k, Object key, ReferenceQueue q) {
            super(k, q);
            this.key=key;
        }
    }
    
    private void processQueue(){
        SoftValue sv;
        while((sv=(SoftValue)queue.poll())!=null){
            hash.remove(sv.key);
       }
    }
    
    public Object put(Object key, Object value){
        processQueue();
        return hash.put(key, new SoftValue(value, key, queue));
    }
    
    public void clear(){
        hardCache.clear();
        processQueue();
        hash.clear();
    }
    
    public int size(){
        processQueue();
        return hash.size();
    }

    public Set entrySet() {
        throw new UnsupportedOperationException();
    }

}

ImageLoader类:

ImageLoader class:

public class ImageLoader {
    

     private SoftHashMap cache=new SoftHashMap(15);
     
     private File cacheDir;
     final int stub_id=R.drawable.stub;
     private int mWidth, mHeight;
     
     public ImageLoader(Context context, int h, int w){
         mWidth=w;
         mHeight=h;
         
         photoLoaderThread.setPriority(Thread.NORM_PRIORITY);
         if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
                cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"CacheDir");
            else
                cacheDir=context.getCacheDir();
            if(!cacheDir.exists())
                cacheDir.mkdirs();
     }
     public void DisplayImage(String url, Activity activity, ImageView imageView)
        {
           
           
           Log.d("IMAGE LOADER", "getNativeHeapSize()-"+String.valueOf(Debug.getNativeHeapSize()/1024)+" kb");
           Log.d("IMAGE LOADER", "getNativeHeapAllocatedSize()-"+String.valueOf(Debug.getNativeHeapAllocatedSize()/1024)+" kb");
           Log.d("IMAGE LOADER", "getNativeHeapFreeSize()-"+String.valueOf(Debug.getNativeHeapFreeSize()/1024)+" kb");
           if(cache.get(url)!=null){
               imageView.setImageBitmap((Bitmap)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){
            Bitmap b=null;
            try {
                //decode image size
                
                BitmapFactory.Options o = new BitmapFactory.Options();
                o.inJustDecodeBounds = true;
                FileInputStream fis=new FileInputStream(f);
                BitmapFactory.decodeStream(fis,null,o);
                try {
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                //Find the correct scale value. It should be the power of 2.
                //final int REQUIRED_SIZE=mWidth;
                int width_tmp=o.outWidth, height_tmp=o.outHeight;
                int scale=1;
                
                while(true){
                    if(width_tmp/2<=mWidth || height_tmp/2<=mHeight)
                        break;
                    width_tmp/=2;
                    height_tmp/=2;
                    scale*=2;
                }
                
                //decode with inSampleSize
                BitmapFactory.Options o2 = new BitmapFactory.Options();
                o2.inSampleSize=scale;
                //o2.inPurgeable=true;
                fis=new FileInputStream(f);
                b=BitmapFactory.decodeStream(fis, null, o2);
                try {
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                return b;
            } catch (FileNotFoundException e) {}
            return null;
        }
     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();
        }
     class PhotosQueue{
         private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>(); 
         
         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();
     
     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();
        }
}

还有我的Application类,但这并不是最好的方法:

And my Application class, not the best way to do it, though:

public class MyApplication extends Application {
    
    ImageLoader mImageLoader;

    
    @Override 
    public void onCreate(){
    
        int h =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getHeight();
        
        int w =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getWidth();
        mImageLoader=new ImageLoader(getApplicationContext(), h, w);
        super.onCreate();
  
    public ImageLoader getImageLoader(){
        return mImageLoader;
    }
    
    @Override
    public void onLowMemory(){
        mImageLoader.clearCache();
        Log.d("MY APP", "ON LOW MEMORY");
        super.onLowMemory();
    }
}

最糟糕的是:一段时间后,当ImageLoader尝试解码另一个位图时,我收到OOM异常.我会很感激您的帮助.谢谢.

And the worst part: after some time I receive OOM exception when ImageLoader tries to decode another bitmap. I'll appreciate any your help. Thanks.

编辑我已经摆脱了硬缓存,但是我仍然遇到这个OOM异常.在我看来,我做的很有趣.我什至不知道我应该提供什么额外的信息...我从服务器下载的图像很大.并且应用无法分配appr.1.5 mb,这就是我在LogCat中看到的内容.但是我只是想不出为什么如果需要内存,vm不会清除我的SoftHashMap ...

EDIT I've got rid of hard cache, but i still get this OOM exception. It seems to me that I'm doing smth funny. I don't even know what extra information should I provide... The images which i download from server are pretty big, though. And app fails to allocate appr. 1.5 mb, that's what I see in LogCat. But I just can't figure out why doesn't vm clear my SoftHashMap if there is need for memory...

推荐答案

  1. 这是一篇有关分析内存泄漏的精彩文章.它绝对可以帮助您. http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html .

您绝对确定您的SoftHashMap实现工作正常吗?看起来比较复杂.您可以使用调试器来确保SoftHashMap永远不会保存超过15个位图.MAT还可以帮助您确定内存中有多少位图.
您还可以注释cache.put(photoToLoad.url,bmp)调用.这样,您将禁用内存中的缓存来确定是否是问题的原因.

Are you absolutely sure your SoftHashMap implementation works fine? Looks rather complicated. You can use debugger to ensure SoftHashMap never holds more than 15 bitmaps. MAT can also help you to identify how many bitmaps are there in memory.
You can also comment cache.put(photoToLoad.url, bmp) call. This way you'll disable in-memory caching to identify if it's a cause of a problem or not.

是的,这可能是活动泄漏.您可以识别出这一点.如果只是在同一活动中滚动并获得OOM,则意味着其他活动不在泄漏.如果您多次停止/启动活动并获得OOM,则表示活动正在泄漏.如果您查看MAT直方图,您也可以肯定地说活动是否泄漏.

Yes it may be an Activity leak. You can identify that. If you just scroll around in the same activity and get OOM it means that something else is leaking not activity. If you stop/start activity several times and get OOM it means activity is leaking. Also you can definitely say is activity leaking or not if you take a look at MAT histogram.

使用inSampleSize时,图像大小无关紧要.即使使用5mpx图像,它也应该可以正常工作.

As you use inSampleSize the image size doesn't matter. It should work fine even with 5mpx images.

您可以尝试仅用HashMap< String,SoftReference< Bitmap>>替换您的SoftHashMap实现.了解有关SoftReference的信息.这对于内存缓存的非常简单的实现很有用.如果有足够的内存,它将对象保存在内存中.如果内存不足,SoftReference会释放对象.

You could try to replace your SoftHashMap implementation with just HashMap<String, SoftReference<Bitmap>>. Read about SoftReference. It is good for very simple implementation of in-memory cache. It holds objects in memory if there's enough memory. If there's too little memory SoftReference releases objects.

我也建议您使用LinkedHashMap进行内存中缓存.它有一个特殊的构造函数,可以按上次访问其条目的顺序来迭代项目.因此,当缓存中有15个以上的项目时,您可以删除最近访问的项目.正如文档所述:

I can also recommend you to use LinkedHashMap for in-memory cache. It has a special constuctor to iterate items in the order in which its entries were last accessed. So when you have more than 15 items in cache you can remove least-recently accessed items. As documentation says:

这种映射非常适合构建LRU缓存.

This kind of map is well-suited to building LRU caches.

  • 您知道我的实现在设计时就考虑了小图像,例如50 * 50.如果您有较大的图像,则应考虑它们消耗了多少内存.如果它们占用太多空间,则可以将它们缓存到SD卡中,而不缓存到内存中.性能可能会变慢,但OOM不再是问题.

  • You know my implementation was designed with small images in mind, something like 50*50. If you have larger images you should think how much memory they consume. If they take too much you could just cache them to SD card but not to memory. The performance may be slower but OOM would not be a problem any more.

    与OOM不相关.我可以看到您在onLowMemory()中调用clearCache().不好,因为clearCache()还会从SD中删除缓存.您应该只清除内存中的缓存,而不是SD缓存.

    Not related to OOM. I can see you call clearCache() in onLowMemory(). Not good because clearCache() also removes cache from SD. You should only clear in-memory cache not SD cache.

    这篇关于图像下载:使用LazyList的内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆