0

我正在尝试实现与 UrlImageViewHelper ( https://github.com/koush/UrlImageViewHelper ) 非常相似的东西,您可以轻松地使用简单的一行代码,从 url 加载图像,如果图像已经下载它是从缓存中加载的。主要区别是我想要相同的效果,但不是从 url 下载它,我想使用我自己的客户端-服务器通信从我自己的服务器接收图像。我服务器上的每个图像都可以由一个字符串唯一标识,我将其用作图像的 id。

我的主要想法是:使用 LRU 缓存来保存图像,而不是保存位图(非常大),我想保存原始图像数据二进制,所以我可以使用相同的图像来构建不同大小的位图和质量取决于具体情况。

到目前为止,这是我的实现:

    public class ImageHandler {

    private static class BitmapCache extends LruCache<String, byte[]>
    {
        public WigoBitmapCache(int maxSize) {
            super(maxSize);
        }

        @Override
        protected int sizeOf(String key, byte[] value) {
            return value.length;
        }

    }

    private static class ImageHandlerThread extends Thread
    {

            /* THIS THREAD WILL DECODE THE IMAGE AND SET THE BITMAP TO THE IMAGEVIEW IN THE BACKGROUND */   
                Activity activity;
        ImageView imageView;
        byte[] imageBytes;

        public ImageHandlerThread(Activity activity, ImageView imageView, byte[] imageBytes)
        {
            this.activity=activity;
            this.imageView=imageView;
            this.imageBytes=imageBytes;
        }

        public void run() {

            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
            int factor1=o.outHeight/height;
            int factor2=o.outWidth/width;
                        /* height and width are for now constant */
            o = null;
            o = new BitmapFactory.Options();
            if (factor1>factor2)
                o.inSampleSize=factor1;
            else
                o.inSampleSize=factor2;
            Bitmap bit = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length,o);
            setBitmap(bit);
            bit = null;

        }


        private void setBitmap(final Bitmap bit) {
            activity.runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    imageView.setImageBitmap(bit);

                }
            });     
        }

    }

    private static class QueueItem
    { /*USED TO HOLD INFO ABOUT THE IMAGE REQUEST UNTIL THE IMAGE GETS FROM THE SERVER */
        String imageName;
        Activity activity;
        ImageView imageView;

        public QueueItem(String imageName, Activity activity, ImageView imageView) 
        {
            this.imageName=imageName;
            this.activity = activity;
            this.imageView = imageView;
        }

    }

    private BitmapCache cache; // this cache holds the image binaries 
    private ArrayList<QueueItem> queue; // this queue holds the info about the request, until the server sends the image

    public ImageHandler(int maxSize)
    {
        cache=new BitmapCache(maxSize);
        queue = new ArrayList<QueueItem>();

    }

    public synchronized void setBitmap(Activity activity, ImageView imageView, String imageName)
    {
        byte[] imageBytes = cache.get(imageName);
        if (imageBytes==null)
        {
            QueueItem item = new QueueItem(imageName, activity, imageView);
            queue.add(item);    

            /* HERE IS THE CODE TO RETRIEVE THE IMAGE BINARY FROM MY SERVER, THIS CODE WORKS FINE, SO THERE IS NO REASON TO BOHER YOU WITH IT */

        }
        else
        {
            ImageHandlerThread thread = new ImageHandlerThread(activity, imageView, imageBytes);
            thread.start();
        }

    }

    public synchronized void insert (String imageName, byte[] imageBytes)
    {

        /* THIS METHOD IS THE CALLBACK THAT IS CALLED WHEN THE IMAGE BINARY IS RECEIVED FROM THE SERVER */

        cache.put(imageName, imageBytes);

        for (QueueItem item: queue)
        {
            if (item.imageName.equals(imageName))
            {
                ImageHandlerThread thread = new ImageHandlerThread(item.activity, item.imageView, imageBytes);
                thread.start();
                queue.remove(item);
            }
        }
    }
}

基本上,这里的主要方法是setBitmap(),它获取activity,需要bitmap的imageView,以及图片名称的名称。如果图像已经在缓存中,则启动一个新线程将字节解码为适当大小的位图,并将位图设置为 imageView。如果图像不存在于缓存中,则将请求放入队列中,直到接收到图像,从服务器检索图像,然后启动与之前相同的线程。

所有这些工作都很好,问题是当 imageView 为图像设置另一个位图时,甚至当活动被​​销毁时,位图仍然驻留在内存中并且不被 GC 收集。

起初我认为这是因为我保留了对活动的引用,并且该引用使活动保持活力,但似乎并非如此,我对活动的引用非常短暂,一旦图像来自清除此引用的服务器。

我用这个实现很快就耗尽了内存,我不知道为什么或做什么来修复它。我创建的位图没有被收集,尽管我没有保留对它们的引用。这可能是我解码图像方式的产物吗?或者线程是否保留未正确收集的引用?有人有什么想法吗?

4

1 回答 1

0

好的,所以我误解了这个问题,发生的事情是 GC 没有收集完成的线程对象,因为我在调试模式下运行它,而不是在运行模式下。当我在运行模式下运行它时,我的应用程序的内存使用量根本没有超过 8MB,而当它处于调试模式时,我达到了 25MB+ 区域。

结论:不要相信调试模式下的 GC 和内存使用信息,尤其是当您有许多短期线程正在运行时。

于 2013-09-28T22:12:40.623 回答