Android的内存错误与延迟加载图片 [英] Android Out of Memory error with Lazy Load images

查看:134
本文介绍了Android的内存错误与延迟加载图片的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现了费多尔的code <一个href="http://stackoverflow.com/questions/541966/android-how-do-i-do-a-lazy-load-of-images-in-listview">here并实现了它到我的项目。唯一的区别是,我的应用程序不具有一个列表视图中,相反,我访问1图像在来自服务器的时间。当活动启动,我称之为DisplayImage(......),以显示第一张照片。然后有2个按钮(previous /下一个),单击时,他们称之为DisplayImage(......)。

它正常工作了一小会儿,但后来我得到一个内存错误。在他的code上,他评论说,您可能需要使用SoftReference。我假定这将解决我的问题,对不对?我与它起到了一下周围,但是当我试图修改它使用SoftReference,图像从未加载。我从来没有使用SoftReference之前,所以我想我只是失去了一些东西。我将如何修改code(ImageLoader的),以解决我的OOM错误?是否有缓存的图片在您浏览他们的更好的办法?

更新: 这里是code如果你不想查看源代码中的其他文件。

 包com.fedorvlasov.lazylist;

进口的java.io.File;
进口java.io.FileInputStream中;
进口java.io.FileNotFoundException;
进口java.io.FileOutputStream中;
进口的java.io.InputStream;
进口java.io.OutputStream中;
进口的java.net.URL;
进口的java.util.HashMap;
进口java.util.Stack中;
进口android.app.Activity;
进口android.content.Context;
进口android.graphics.Bitmap;
进口android.graphics.BitmapFactory;
进口android.widget.ImageView;

公共类ImageLoader的{

    //最简单的内存缓存实现。这应该被替换为类似SoftReference或BitmapOptions.inPurgeable(自1.6)
    私人的HashMap&LT;字符串,位图&GT;缓存=新的HashMap&LT;字符串,位图&GT;();

    私人文件cacheDir;

    公共ImageLoader的(上下文的背景下){
        //使背景THEAD低优先级。这样也不会影响用户界面性能
        photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);

        //查找目录保存缓存图像
        如果(android.os.Environment.getExternalStorageState()。等于(android.os.Environment.MEDIA_MOUNTED))
            cacheDir =新的文件(android.os.Environment.getExternalStorageDirectory(),LazyList);
        其他
            cacheDir = context.getCacheDir();
        如果(!cacheDir.exists())
            cacheDir.mkdirs();
    }

    最终诠释stub_id = R.drawable.stub;
    公共无效DisplayImage(字符串URL,活动活动,ImageView的ImageView的)
    {
        如果(cache.containsKey(URL))
            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){
        尝试 {
            //德code图像尺寸
            BitmapFactory.Options O =新BitmapFactory.Options();
            o.inJustDe codeBounds = TRUE;
            BitmapFactory.de codeStream(新的FileInputStream(f)项,空,O);

            //找到正确的比例值。它应该是2的幂。
            最终诠释REQUIRED_SIZE = 70;
            INT width_tmp = o.outWidth,height_tmp = o.outHeight;
            int标= 1;
            而(真){
                如果(width_tmp / 2'; REQUIRED_SIZE || height_tmp / 2'; REQUIRED_SIZE)
                    打破;
                width_tmp / = 2;
                height_tmp / = 2;
                规模* = 2;
            }

            //德code与inSampleSize
            BitmapFactory.Options O2 =新BitmapFactory.Options();
            o2.inSampleSize =规模;
            返回BitmapFactory.de codeStream(新的FileInputStream(f)项,空,O2);
        }赶上(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的所有实例
        公共无效清洁(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();

    //用于在UI线程中显示位图
    类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();
    }

}
 

下面是在我试图执行SoftReference同一类。我不认为我这样做是正确的,因为这并不能显示加载后,屏幕上的画面。

 包com.mycompany.myapp;

进口的java.io.File;
进口java.io.FileInputStream中;
进口java.io.FileNotFoundException;
进口java.io.FileOutputStream中;
进口的java.io.InputStream;
进口java.io.OutputStream中;
进口java.lang.ref.SoftReference;
进口的java.net.URL;
进口的java.util.HashMap;
进口java.util.Stack中;

进口android.app.Activity;
进口android.content.Context;
进口android.graphics.Bitmap;
进口android.graphics.BitmapFactory;
进口android.widget.ImageView;

公共类ImageLoader的{

    //最简单的内存缓存实现。这应该被替换为类似SoftReference或BitmapOptions.inPurgeable(自1.6)
    私人的HashMap&LT;字符串,SoftReference&LT;位图&GT;&GT;缓存=新的HashMap&LT;字符串,SoftReference&LT;位图&GT;&GT;();

    私人文件cacheDir;

    公共ImageLoader的(上下文的背景下){
        //使后台线程低优先级。这样也不会影响用户界面性能
        photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);

        //查找目录保存缓存图像
        如果(android.os.Environment.getExternalStorageState()。等于(android.os.Environment.MEDIA_MOUNTED))
            cacheDir =新的文件(android.os.Environment.getExternalStorageDirectory(),MyApp的/温度);
        其他
            cacheDir = context.getCacheDir();
        如果(!cacheDir.exists())
            cacheDir.mkdirs();
    }

    最终诠释stub_id = R.drawable.loading;
    公共无效DisplayImage(字符串URL,活动活动,ImageView的ImageView的)
    {
        如果(cache.containsKey(URL)){
            imageView.setImageBitmap(空);
            System.gc()的;
            imageView.setImageBitmap(cache.get(URL)获得());
        } 其他 {
            queuePhoto(URL,活动,ImageView的);
            imageView.setImageBitmap(空);
            System.gc()的;
            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();
    }

    私人SoftReference&LT;位图&GT; getBitmap(字符串URL)
    {
        //我通过散列code识别图像。没有一个完美的解决方案,很好的演示。
        字符串文件名=将String.valueOf(url.hash code());
        文件F =新的文件(cacheDir,文件名);

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

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

    //德$ C $连拍影像和扩展它来减少内存消耗
    私人SoftReference&LT;位图&GT;德codeFILE(文件f){
        尝试 {
            //德code图像尺寸
            BitmapFactory.Options O =新BitmapFactory.Options();
            o.inJustDe codeBounds = TRUE;
            BitmapFactory.de codeStream(新的FileInputStream(f)项,空,O);

            //找到正确的比例值。它应该是2的幂。
            最终诠释REQUIRED_SIZE = 1024;
            INT width_tmp = o.outWidth,height_tmp = o.outHeight;
            int标= 1;
            而(真){
                如果(width_tmp / 2'; REQUIRED_SIZE || height_tmp / 2'; REQUIRED_SIZE)
                    打破;
                width_tmp / = 2;
                height_tmp / = 2;
                规模* = 2;
            }

            //德code与inSampleSize
            BitmapFactory.Options O2 =新BitmapFactory.Options();
            o2.inSampleSize =规模;

            返回cache.get(BitmapFactory.de codeStream(新的FileInputStream(f)项,空,O2));
        }赶上(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的所有实例
        公共无效清洁(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();
                        }
                        SoftReference&LT;位图&GT; 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();

    //用于在UI线程中显示位图
    类BitmapDisplayer实现Runnable
    {
        SoftReference&LT;位图&GT;位图;
        ImageView的ImageView的;
        公共BitmapDisplayer(SoftReference&LT;位图&GT; BMP,ImageView的我){位= BMP; ImageView的= I;}
        公共无效的run()
        {
            如果(位图!= NULL){
                imageView.setImageBitmap(空);
                System.gc()的;
                imageView.setImageBitmap(bitmap.get());
            } 其他 {
                imageView.setImageBitmap(空);
                System.gc()的;
                imageView.setImageResource(stub_id);
            }
        }
    }

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

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

}
 

解决方案

通常情况下,你的内存图片缓存声明应该是这个样子:

 私有静态HashMap中&LT;字符串,SoftReference&LT;位图&GT;&GT;缓存=
    新的HashMap&LT;字符串,SoftReference&LT;位图&GT;&GT;();
 

请注意您的OOM的问题不​​一定是关系到你的位图缓存。始终确保你停止/中断您的图像加载器在活动的的onDestroy方法或正在管理的任何其他类的finalize催生的主题。

使用Eclipse的内存分析工具与DDMS一起分析您的应用程序的内存使用情况: http://www.eclipse.org/mat /

I found Fedor's code here and implemented it into my project. The only difference is that my application does not have a list view, rather, I am accessing 1 image at a time from the server. When the activity launches, i call "DisplayImage(...)" to show the first picture. Then there are 2 buttons (previous/next) that when clicked, they call "DisplayImage(...)".

It works fine for a little while, but then I get an Out of Memory error. At the top of his code, he comments that you may want to use SoftReference. I am assuming that would fix my problem, right? I played around with it a bit but when I tried modifying it to use SoftReference, the images never load. I have never used SoftReference before so I figure I'm just missing something. How would I modify that code (ImageLoader) to fix my OOM error? Is there a better way of caching the pictures as you browse them?

UPDATE: Here is the code in case you don't want to view the other files in the 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();
    }

}

Here is the same class after I tried to implement SoftReference. I don't think I did it right because this does not display the picture on the screen after loading.

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

}

解决方案

Generally, your in-memory image cache declaration should look something like:

private static HashMap<String, SoftReference<Bitmap>> cache = 
    new HashMap<String, SoftReference<Bitmap>>();

Note your OOM problems may not necessarily be related to your Bitmap cache. Always ensure you stop/interrupt the thread spawned by your image loader in your Activity's onDestroy method or in the finalize of any other class that is managing it.

Use the Eclipse Memory Analyzer Tool in conjunction with DDMS to analyze your application's memory usage: http://www.eclipse.org/mat/

这篇关于Android的内存错误与延迟加载图片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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