图像下载:使用LazyList进行内存泄漏

时间:2011-04-07 16:16:22

标签: android

我使用LazyList进行了内存泄漏。 我在整个应用程序中使用ImageLoader的一个实例,我在Application.onCreate()中创建它,因为我需要在几个活动中下载图像:列表活动,一个带有图库窗口小部件的活动和全屏图库活动(所有这些都使用相同的缓存) 我修改了图像加载器,因此它使用基于SoftReference的HashMap。这是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类:

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类,不是最好的方法,但是:

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异常。 我会感激你的帮助。感谢。

编辑我已经摆脱了硬缓存,但我仍然得到了这个OOM异常。在我看来,我做的很有趣。我甚至不知道我应该提供哪些额外信息...... 我从服务器下载的图像非常大。并且app无法分配appr。 1.5 MB,这就是我在LogCat中看到的。但我无法弄清楚为什么在需要内存时不清除我的SoftHashMap ...

4 个答案:

答案 0 :(得分:1)

  1. onLowMemory对您没有任何帮助,因为当您的应用程序内存不足时,它不会生成,当Android系统在关闭进程之前需要内存用于其他应用程序或自身时,会调用它
  2. 我认为不需要硬缓存 - 这会阻止可绘制的回收。只需将drawables保留在软缓存中 - 当drawables的引用不是软时,GC不会收集内存,因此您不必担心当前在ImageView中设置的drawable被回收。
  3. 您一次在屏幕上显示多少张图片?它们有多大?

答案 1 :(得分:1)

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

  2. 你绝对确定你的SoftHashMap实现工作正常吗?看起来相当复杂。您可以使用调试器来确保SoftHashMap永远不会保留超过15个位图。 MAT还可以帮助您识别内存中有多少位图 您还可以评论cache.put(photoToLoad.url,bmp)电话。这样,您将禁用内存中缓存,以确定它是否是导致问题的原因。

  3. 是的,它可能是活动泄漏。你可以识别出来。如果你只是在同一个活动中滚动并获得OOM,则意味着其他东西正在泄漏而不是活动。如果多次停止/启动活动并获得OOM,则意味着活动正在泄漏。 如果你看一下MAT直方图,你也可以肯定地说是活动是否泄漏。

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

  5. 您可以尝试仅使用HashMap&lt; String,SoftReference&lt; Bitmap&gt;&gt;替换SoftHashMap实现。阅读SoftReference。它非常适合内存缓存的非常简单的实现。如果有足够的内存,它会将对象保存在内存中。如果内存太少,SoftReference会释放对象。

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

      

    这种地图非常适合构建LRU缓存。

  7. 您知道我的实现设计时考虑了小图像,例如50 * 50。如果您有更大的图像,您应该考虑它们消耗了多少内存。如果它们占用太多,你可以将它们缓存到SD卡而不是内存。性能可能会慢一些,但OOM不再是问题。

  8. 与OOM无关。我可以看到你在onLowMemory()中调用clearCache()。不好,因为clearCache()也从SD中删除了缓存。您应该只清除内存缓存而不是SD缓存。

答案 2 :(得分:0)

看起来你正在创建位图,但从不调用Bitmap.recycle()。

编辑:详细说明,Bitmap.recycle()释放当前用于存储位图的内存。由于BitmapFactory创建的位图是不可变的,(您可以在新创建的位图上使用Bitmap.isMutable()进行检查),只需从哈希表中删除对它们的引用并等待垃圾收集器就不足以释放它存储器中。

调用位图在完成特定位图时(例如在photoQueue的“clean”方法或clearCache()中)回收。

答案 3 :(得分:0)

我看到下面的行在您的代码中被注释,First of All取消注释该行plz。

//o2.inPurgeable=true;

在NonPurgeable情况下,编码比特流一遍又一遍地被解码为不同的Bitmap,直到出现内存不足。在可清除的情况下,一个图像分配的内存将在任何需要时由任何新图像共享,以后随时如果激活旧的图像引用,则OS将通过使用另一个图像的空间来管理自身的内存引用。副verce和这种方式它总是避免内存不足错误。

如果你有可清除的情况并仍然面临内存泄漏,那么现在使用try catch blog来跟踪错误并告诉我堆栈跟踪详细信息。

    try{
    //your code to download or decode image
    }catch(Error e){
    //Print the stack trace
}
相关问题