然而,另一个图像下载问题 [英] Yet another image download question

查看:117
本文介绍了然而,另一个图像下载问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道,这之前disscused很多次,但我已经得到了使用内存泄漏<一href="http://stackoverflow.com/questions/541966/android-how-do-i-do-a-lazy-load-of-images-in-listview/3068012#3068012">LazyList和我有没有想法如何解决它。 我用ImageLoader的在整个应用程序的一个实例,我在Application.onCreate()创建它,因为我需要下载图像的几项活动:列表活动,与画廊的小部件和全屏画廊activitiy(所有的人都一个活动使用相同的缓存) 我修改过的图像加载器,它使用SoftReference为基础的HashMap中。下面是$ C $下SoftHashMap:

 公共类SoftHashMap扩展AbstractMap {

    私人最终地图哈希=新的HashMap();
    私人最终诠释HARD_SIZE;
    私人最终LinkedList的hardCache =新的LinkedList();
    私人最终的ReferenceQueue队列=新的ReferenceQueue();

    公共SoftHashMap(){
        本(100);
    }

    公共SoftHashMap(INT hardSize){
        HARD_SIZE = hardSize;
    }

    公共对象获取(对象键){
        对象result = NULL;
        SoftReference soft_ref =(SoftReference)hash.get(密钥);
        如果(soft_ref!= NULL){
            结果= soft_ref.get();
            如果(结果== NULL){
                hash.remove(键);
            }其他{
                hardCache.addFirst(结果);
                如果(hardCache.size()&GT; HARD_SIZE){
                    hardCache.removeLast();
                }
            }
        }
        返回结果;
    }
    私有静态类SoftValue扩展SoftReference {
        私人最终目标的关键;
        公共SoftValue(对象K,对象键,ReferenceQueue上Q){
            超级(K,Q);
            this.key =键;
        }
    }

    私人无效processQueue(){
        SoftValue SV;
        而((SV =(SoftValue)queue.poll())!= NULL){
            hash.remove(sv.key);
       }
    }

    公共对象的put(对象的关键,对象的值){
        processQueue();
        返回hash.put(重点,新SoftValue(值,键,队列));
    }

    公共无效清除(){
        hardCache.clear();
        processQueue();
        hash.clear();
    }

    公众诠释大小(){
        processQueue();
        返回hash.size();
    }

    公开组的entrySet(){
        抛出新UnsupportedOperationException异常();
    }

}
 

ImageLoader的类:

 公共类ImageLoader的{


     私人SoftHashMap缓存=新SoftHashMap(15);

     私人文件cacheDir;
     最终诠释stub_id = R.drawable.stub;
     私人诠释mWidth,mHeight;

     公共ImageLoader的(上下文的背景下,INT小时,INT W){
         mWidth = W;
         mHeight = H;

         photoLoaderThread.setPriority(Thread.NORM_PRIORITY);
         如果(android.os.Environment.getExternalStorageState()。等于(android.os.Environment.MEDIA_MOUNTED))
                cacheDir =新的文件(android.os.Environment.getExternalStorageDirectory(),CacheDir);
            其他
                cacheDir = context.getCacheDir();
            如果(!cacheDir.exists())
                cacheDir.mkdirs();
     }
     公共无效DisplayImage(字符串URL,活动活动,ImageView的ImageView的)
        {


           Log.d(图像加载器,getNativeHeapSize() - +将String.valueOf(Debug.getNativeHeapSize()/ 1024)+KB);
           Log.d(图像加载器,getNativeHeapAllocatedSize() - +将String.valueOf(Debug.getNativeHeapAllocatedSize()/ 1024)+KB);
           Log.d(图像加载器,getNativeHeapFreeSize() - +将String.valueOf(Debug.getNativeHeapFreeSize()/ 1024)+KB);
           如果(cache.get(URL)!= NULL){
               imageView.setImageBitmap((位图)cache.get(URL));
           }
            其他
            {
                queuePhoto(URL,活动,ImageView的);
                imageView.setImageResource(stub_id);
            }
        }

        私人无效queuePhoto(字符串URL,活动活动,ImageView的ImageView的)
        {
            //这个ImageView的可之前用于其它图像。所以有可能在队列一些旧的任务。我们需要将它们丢弃。
            photosQueue.Clean(ImageView的);
            PhotoToLoad P =新PhotoToLoad(URL,ImageView的);
            同步(photosQueue.photosToLoad){
                photosQueue.photosToLoad.push(对);
                photosQueue.photosToLoad.notifyAll();
            }

            //启动线程,如果它尚未开始
            如果(photoLoaderThread.getState()== Thread.State.NEW)
                photoLoaderThread.start();
        }
     私人位图getBitmap(字符串URL)
        {
            //我通过散列code识别图像。没有一个完美的解决方案,很好的演示。
            字符串文件名=将String.valueOf(url.hash code());
            文件F =新的文件(cacheDir,文件名);

            //从SD高速缓存
            位图B =去codeFILE(F);
            如果(B!= NULL)
                返回b;

            //从网页
            尝试 {
                点阵位图= NULL;
                InputStream的是=新的URL(网址).openStream();
                的OutputStream OS =新的FileOutputStream(F);
                Utils.CopyStream(是,OS);
                os.close();
                位=去codeFILE(F);
                返回的位图;
            }赶上(例外前){
               ex.printStackTrace();
               返回null;
            }
        }

        //德$ C $连拍影像和扩展它来减少内存消耗
        私人位图德codeFILE(文件f){
            位图B = NULL;
            尝试 {
                //德code图像尺寸

                BitmapFactory.Options O =新BitmapFactory.Options();
                o.inJustDe codeBounds = TRUE;
                的FileInputStream FIS =新的FileInputStream(F);
                BitmapFactory.de codeStream(FIS,空,O);
                尝试 {
                    fis.close();
                }赶上(IOException异常E){
                    // TODO自动生成的catch块
                    e.printStackTrace();
                }

                //找到正确的比例值。它应该是2的幂。
                //最终诠释REQUIRED_SIZE = mWidth;
                INT width_tmp = o.outWidth,height_tmp = o.outHeight;
                int标= 1;

                而(真){
                    如果(width_tmp / 2'= mWidth || height_tmp / 2'= mHeight)
                        打破;
                    width_tmp / = 2;
                    height_tmp / = 2;
                    规模* = 2;
                }

                //德code与inSampleSize
                BitmapFactory.Options O2 =新BitmapFactory.Options();
                o2.inSampleSize =规模;
                //o2.inPurgeable=true;
                FIS =新的FileInputStream(F);
                B = BitmapFactory.de codeStream(FIS,空,O2);
                尝试 {
                    fis.close();
                }赶上(IOException异常E){
                    // TODO自动生成的catch块
                    e.printStackTrace();
                }
                返回b;
            }赶上(FileNotFoundException异常E){}
            返回null;
        }
     类PhotoToLoad {
         公共字符串URL;
         公众的ImageView ImageView的;

         公共PhotoToLoad(字符串U,ImageView的我){
             URL = U;
             ImageView的=我;
         }
     }
     PhotosQueue photosQueue =新PhotosQueue();

        公共无效stopThread()
        {
            photoLoaderThread.interrupt();
        }
     类PhotosQueue {
         私人堆叠式和LT; PhotoToLoad&GT; photosToLoad =新的堆栈&LT; PhotoToLoad&GT;();

         公共无效清洁(ImageView的图像)
            {
                对于(INT J = 0; J&LT; photosToLoad.size()){
                    如果(photosToLoad.get(J).imageView ==图片)
                        photosToLoad.remove(J);
                    其他
                        + D];
                }
            }
     }
     类PhotosLoader继承Thread {
         公共无效的run(){
             尝试 {
                而(真)
                    {
                        //线程等待直到有在队列加载任何图像
                        如果(photosQueue.photosToLoad.size()== 0)
                            同步(photosQueue.photosToLoad){
                                photosQueue.photosToLoad.wait();
                            }
                        如果(photosQueue.photosToLoad.size()!= 0)
                        {
                            PhotoToLoad photoToLoad;
                            同步(photosQueue.photosToLoad){
                                photoToLoad = photosQueue.photosToLoad.pop();
                            }
                            BMP位= getBitmap(photoToLoad.url);
                            cache.put(photoToLoad.url,BMP);
                            对象标记= photoToLoad.imageView.getTag();
                            如果(标记= NULL和放大器;!及((字符串)标签).equals(photoToLoad.url)){
                                BitmapDisplayer BD =新BitmapDisplayer(BMP,photoToLoad.imageView);
                                活动一=(活动)photoToLoad.imageView.getContext();
                                a.runOnUiThread(BD);
                            }
                        }
                        如果(Thread.interrupted())
                            打破;
                    }
                }赶上(InterruptedException异常E){
                    //允许线程退出
                }
         }
     }
     PhotosLoader photoLoaderThread =新PhotosLoader();

     类BitmapDisplayer实现Runnable
        {
            点阵位图;
            ImageView的ImageView的;
            公共BitmapDisplayer(位图B,ImageView的我){位= B,ImageView的= I;}
            公共无效的run()
            {
                如果(位图!= NULL)
                    imageView.setImageBitmap(位);
                其他
                    imageView.setImageResource(stub_id);
            }
        }

        公共无效clearCache(){
            //清除内存高速缓存
            cache.clear();

            //明确的SD高速缓存
            文件[]文件= cacheDir.listFiles();
            对于(F文件:文件)
                f.delete();
        }
}
 

和我的应用程序类,而不是做的最好的办法,但:

 公共类MyApplication的扩展应用{

    ImageLoader的mImageLoader;


    @覆盖
    公共无效的onCreate(){

        INT ^ h =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getHeight();

        INTW¯¯ =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getWidth();
        mImageLoader =新ImageLoader的(getApplicationContext()中,h,w)的;
        super.onCreate();

    公共ImageLoader的getImageLoader(){
        返回mImageLoader;
    }

    @覆盖
    公共无效onLowMemory(){
        mImageLoader.clearCache();
        Log.d(我的应用,在低内存);
        super.onLowMemory();
    }
}
 

而最糟糕的:经过一段时间后,我收到OOM异常时,ImageLoader的试图去code另一个位图。 我将AP preciate您的任何帮助。谢谢你。

修改我已经摆脱了艰苦的缓存,但我仍然得到这个OOM异常。在我看来,我做未便滑稽。我甚至不知道我应该提供什么样的额外信息... 这是我从服务器上下载的图像是pretty的大,虽然。而应用程序未能分配行驶约。 1.5 MB,这就是我在LogCat中看到。但我就是想不通,为什么不VM清楚我的SoftHashMap如果有需要的内存...

解决方案
  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取代你SoftHashMap实施;字符串,SoftReference&LT;位图&GT;取代。阅读关于SoftReference。这是良好的非常简单的实现在内存中缓存。它拥有对象在内存中是否有足够的内存。如果有内存太少SoftReference释放的对象。

  6. 我也可以推荐您使用的LinkedHashMap在内存中缓存。它有一个特殊的constuctor迭代在其中的条目进行了最后一次访问的订单项目。所以,当你有超过15个项目在缓存中,你可以删除最近最少访问的项目。由于文档说:

      

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

  7. 您知道我的设计思路与小图像在脑海,像50 * 50。如果你有更大的图像,你应该想想他们多么的内存占用。如果他们把太多,你可以只缓存他们到SD卡,但没有记忆。性能可能会较慢,但OOM不会是一个问题了。

  8. 不相关的OOM。我可以看到你调用clearCache()的onLowMemory()。不是一件好事,因为clearCache()还删除缓存从SD。你应该只明确在内存中缓存并不SD高速缓存。

I know that this was disscused many times before, but I've got memory leak using LazyList and I've got no ideas how to fix it. 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 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();
        }
}

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();
    }
}

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.

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. Here's an amazing article on analyzing memory leaks. It can definitely help you. http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html.

  2. 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.

  3. 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.

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

  5. 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.

  6. 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:

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

  7. 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.

  8. 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.

这篇关于然而,另一个图像下载问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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