【问题标题】:OOM when using NetworkImageView ( of Volley library)使用 NetworkImageView 时出现 OOM(Volley 库)
【发布时间】:2013-09-05 16:56:53
【问题描述】:

背景

使用Volley libraryNetworkImageView 是处理来自网络的图片的一种便捷方式。

但是,它有一些错误(正如我写的 here)。

问题

使用它可能会遇到的一个问题是,它无法以内存高效的方式解码来自网络的图像。

这意味着,如果您使用一个带有多个 NetworkImageView 的 gridView,并且每个都显示一个具有未知分辨率的图像(可能很小,可能很大),您最终会得到一个 OOM 。

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

问题

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

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

【问题讨论】:

    标签: android bitmap out-of-memory android-volley networkimageview


    【解决方案1】:

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

    1. 停止使用NetworkImageView。使用常规的ImageView 并实现侦听器以在图像可用时应用图像。 这是第 2 步的先决条件。将NetworkImageViewget() 方法一起使用可能会导致我的经验出现问题`。
    2. 创建一个ImageLoader 并使用接收ImageRequestget() 方法。如果可以,请使用将 maxHeightmaxWidth 作为参数的可选构造函数。
    3. 当您在ImageLoader 中使用前面提到的get() 方法时,请保存该方法返回的ImageContainer 引用,这样如果在请求完成之前视图被回收,您就可以取消请求。李>
    4. ImageLoader 构造函数中的ImageCache 提供良好的实现。这将降低解码已经可用的位图的冗余。
    5. 如果您的架构允许,请尝试在位图上使用recycle() 方法,但请注意不要回收您可能仍需要的那些。

    编辑:添加代码示例

    (2) + (4) 的代码 sn-p

    // assuming sRequestQueue is your static global request queue 
    // and `BitmapCache` is a good implementation for the `ImageCache` interface
    sImageLoader = new ImageLoader(sRequestQueue, new BitmapCache());
    

    (3) 的代码 sn-p 假设 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);
    

    默认的图片加载器(你可以在这里做你想做的):

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

    【讨论】:

    • 您能提供一个示例代码吗?另外,如果谷歌不能以任何好的/定制的方式处理大图像,为什么还要制作这个类?
    • NetworkImageView 是用于以基本方式加载远程图像的语法糖。如果你听一下主题演讲,你会看到 Ficus(首席开发人员)说 NetworkImageView 基本上是,我引用,用于“懒惰”的开发人员(他的意思是很好的方式)。这是一个很好的基本解决方案,但如果您需要特定的东西,您将不得不使用不同的机制。我将编辑我的答案并提供一些示例。
    • 一个懒惰的开发人员通常是一个优秀的开发人员,他们会接手别人的工作,而不是重新发明轮子并在新的错误上浪费时间。事实并非如此,因为此类对于可能填充内存的图像来说还不够好。它甚至没有办法以自定义方式解决此问题。
    • 这并不完全正确,我只是发布了一些自定义修复。事情也从来不是非黑即白的。您需要考虑到 Volley 是新产品,并且与所有新产品一样,它也在不断发展和扩展。
    • 你是否在超大图片上测试过你的代码,就像我发布的那样?
    【解决方案2】:

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

    NetworkImageView 在链接 NetworkImageView.java 中知道它在第 104 行的宽度和第 105 行的高度

    下面是NetworkImageView.java的确切代码

        private void loadImageIfNecessary(final boolean isInLayoutPass) {
        int width = getWidth(); // at line no 104
        int height = getHeight(); // at line no 105
    

    您只需将此信息转发给图像加载器。

    在第 141 行 NetworkImageView.java 调用 ImageLoader#get(String requestUrl, final ImageListener listener) 方法没有宽度和 高度。将此调用更改为 ImageLoader#get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight)。

    NetworkImageView.java的第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);
    

    【讨论】:

    • 能否请您显示要放置的确切代码以及放置位置(不仅仅是行号)?
    • 所以这解决了问题?也许您应该在 github 网站的问题/请求上写下它?遗憾的是,自从我停止使用这个库以来,我无法测试你写的东西。也许有一天我会再试一次。
    • 这也是我最终做的!虽然,我不确定缓存的图像...如果您从 Gridview 开始,并更改此代码,这是否意味着缓存的“图像”会更小?而当你切换到“全屏”视图时,点击 GridView 图像时,它会重复使用低样本图像吗?
    • 我不确定缓存的大小是否很小,但是当您将图像设置为 NetworkImageView 时,它会设置图像的小尺寸。
    • 这是否可以防止所有内存不足错误?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-16
    • 1970-01-01
    • 2013-07-30
    • 2015-07-20
    • 2017-04-12
    • 1970-01-01
    • 2014-10-11
    相关资源
    最近更新 更多