Android 使用NetworkImageView(Volley库)时的OOM 背景

Android 使用NetworkImageView(Volley库)时的OOM 背景,android,bitmap,out-of-memory,android-volley,networkimageview,Android,Bitmap,Out Of Memory,Android Volley,Networkimageview,使用是处理从web显示图像的一种方便方法 但是,它有一些bug(正如我所写的) 问题 使用它可能会遇到的一个问题是,它无法以内存效率高的方式对来自web的图像进行解码 这意味着,如果使用一个包含多个NetworkImageView的gridView,并且每个gridView都显示一个分辨率未知(可能小,也可能大)的图像,则最终会出现OOM 例如,您可以将此对象的url设置为,并亲自查看应用程序在显示位图后使用了多少内存,与之前使用的内存相比 问题 如何修改NetworkImageView解码位图

使用是处理从web显示图像的一种方便方法

但是,它有一些bug(正如我所写的)

问题 使用它可能会遇到的一个问题是,它无法以内存效率高的方式对来自web的图像进行解码

这意味着,如果使用一个包含多个NetworkImageView的gridView,并且每个gridView都显示一个分辨率未知(可能小,也可能大)的图像,则最终会出现OOM

例如,您可以将此对象的url设置为,并亲自查看应用程序在显示位图后使用了多少内存,与之前使用的内存相比

问题 如何修改NetworkImageView解码位图的方式


我可以改变它的一种方法是让它解码位图,同时将其缩小到所需的大小(或者至少将其最大值设置为屏幕大小),例如使用缩小的方法。

Volley有一种内置的方法,可以将图像适配到您提到的给定宽度和高度。您需要停止使用
NetworkImageView
提供的加载图像的方便方法,而这些方法不使用它。我建议使用以下方法来减少OOM错误的机会:

  • 停止使用
    NetworkImageView
    。使用常规的
    ImageView
    并实现侦听器,以便在图像可用时应用该图像。这是步骤2的先决条件。使用
    NetworkImageView
    get()
    方法可能会导致我的经验中出现问题
  • 创建
    ImageLoader
    并使用
    get()
    方法接收
    ImageRequest
    。如果可以,请使用将maxHeight和maxWidth作为参数的可选构造函数
  • 当您在
    ImageLoader
    中使用前面提到的
    get()
    方法时,请保存该方法返回的
    ImageContainer
    引用,这样,如果在请求完成之前回收视图,您就可以取消请求
  • ImageLoader
    构造函数中的
    ImageCache
    提供良好的实现。这将降低解码已有位图时的冗余度
  • 如果您的体系结构允许,请尝试在位图上使用
    recycle()
    编辑:添加代码示例
    (2)+(4)的代码段

    (3)的代码段假定ViewHolder模式和imageContainerViewHolder类的成员。该原则适用于任何体系结构

    // when applying a new view cancel the previous request first
    
    if (imageContainer != null) {
        imageContainer.cancelRequest();
    }
    
    // calculate the max height and max width
    
    imageContainer = sImageLoader.get(imageUrl, 
        new DefaultImageListener(image), maxWidth, maxHeight);
    
    默认图像加载器(您可以在此处执行以下操作):


    我在搜索中找到了更好的解决方案:-)

    NetworkImageView知道链接中第104行的宽度和第105行的高度

    下面是在的确切代码

    您只需将此信息转发到图像加载程序

    第141行调用ImageLoader#get(字符串 requestUrl,最终ImageListener(侦听器)方法,不带宽度和 高度。将此调用更改为ImageLoader#get(字符串requestUrl, ImageListener ImageListener,int-maxWidth,int-maxHeight)

    的第141行到第172行的代码替换为以下代码

    ImageContainer newContainer = mImageLoader.get(mUrl,
                new ImageListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        if (mErrorImageId != 0) {
                            setImageResource(mErrorImageId);
                        }
                    }
    
                    @Override
                    public void onResponse(final ImageContainer response, boolean isImmediate) {
                        // If this was an immediate response that was delivered inside of a layout
                        // pass do not set the image immediately as it will trigger a requestLayout
                        // inside of a layout. Instead, defer setting the image by posting back to
                        // the main thread.
                        if (isImmediate && isInLayoutPass) {
                            post(new Runnable() {
                                @Override
                                public void run() {
                                    onResponse(response, false);
                                }
                            });
                            return;
                        }
    
                        if (response.getBitmap() != null) {
                            setImageBitmap(response.getBitmap());
                        } else if (mDefaultImageId != 0) {
                            setImageResource(mDefaultImageId);
                        }
                    }
                }, width, height);
    

    你能给我一个示例代码吗?另外,如果谷歌不能以任何好的/定制的方式处理大型图像,为什么要创建这个类呢?
    NetworkImageView
    是以基本方式加载远程图像的语法糖。如果你听一下主题演讲,你会看到Ficus(首席开发人员)说,
    NetworkImageView
    基本上是为“懒惰”的开发人员准备的(他的意思很好)。这是一个很好的基本解决方案,但是如果你需要一些特定的东西,你必须使用不同的机制。我将编辑我的答案,并提供一些示例。懒惰的开发人员通常是一个好的开发人员,它需要别人的工作,而不是重新发明轮子,浪费时间在新的bug上。事实并非如此,因为这个类对于可能填满内存的图像来说不够好。它甚至没有办法以自定义方式解决这个问题。这并不完全正确,我只是发布了一些自定义修复。而且事情从来都不是黑白的。你需要考虑到Volley是新产品,和所有新产品一样,它也在不断发展和扩展。你有没有在超大图片上测试过你的代码,比如我发布的图片?你能告诉我要放的确切代码和放在哪里(不仅仅是行号)吗?这样就解决了问题?也许你应该在github网站的问题/请求上写下它?很遗憾,我无法测试你写的东西,因为我已经停止使用这个库了。也许有一天我会再次尝试。这也是我最后做的!虽然,我不确定的缓存图像虽然。。。如果您从一个Gridview开始,并对代码进行了更改,这是否意味着缓存的“图像”会更小?当您切换到“全屏”视图时,单击GridView图像后,它会重用低样本图像吗?我不确定缓存的大小是否小,但当您将图像设置为NetworkImageView时,它会设置较小的图像大小。这能防止所有内存不足错误吗?
    private class DefaultImageListener implements ImageListener {
        private ImageView imageView;
    
        public DefaultImageListener(ImageView view) {
            imageView = view
        }
    
        @Override
        public void onErrorResponse(VolleyError error) {
            //handle errors
        }
    
        @Override
        public void onResponse(ImageContainer response, boolean isImmediate) {
            if (response.getBitmap() != null) {
                imageView.setImageBitmap(response.getBitmap());
            }
        }
    }
    
        private void loadImageIfNecessary(final boolean isInLayoutPass) {
        int width = getWidth(); // at line no 104
        int height = getHeight(); // at line no 105
    
    ImageContainer newContainer = mImageLoader.get(mUrl,
                new ImageListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        if (mErrorImageId != 0) {
                            setImageResource(mErrorImageId);
                        }
                    }
    
                    @Override
                    public void onResponse(final ImageContainer response, boolean isImmediate) {
                        // If this was an immediate response that was delivered inside of a layout
                        // pass do not set the image immediately as it will trigger a requestLayout
                        // inside of a layout. Instead, defer setting the image by posting back to
                        // the main thread.
                        if (isImmediate && isInLayoutPass) {
                            post(new Runnable() {
                                @Override
                                public void run() {
                                    onResponse(response, false);
                                }
                            });
                            return;
                        }
    
                        if (response.getBitmap() != null) {
                            setImageBitmap(response.getBitmap());
                        } else if (mDefaultImageId != 0) {
                            setImageResource(mDefaultImageId);
                        }
                    }
                }, width, height);