recyclerview 适配器内的 Volley 请求

Volley request inside recyclerview adapter

我在 recyclerview 适配器中使用 Volley 图像请求。 在快速滚动完成之前,请求似乎工作正常,每当我快速向上或向下滚动 recyclerview 时,应用程序崩溃并出现以下错误:

 java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Permission denied. See process maps in the log.
    at java.lang.Thread.nativeCreate(Native Method)
    at java.lang.Thread.start(Thread.java:883)
    at com.android.volley.RequestQueue.start(RequestQueue.java:134)
    at com.android.volley.toolbox.Volley.newRequestQueue(Volley.java:91)
    at com.android.volley.toolbox.Volley.newRequestQueue(Volley.java:67)
    at com.android.volley.toolbox.Volley.newRequestQueue(Volley.java:102)
    at com.squadtechs.hdwallpapercollection.main_activity.fragment.WallpaperAdapter.populateViews(WallpaperAdapter.kt:60)
    at com.squadtechs.hdwallpapercollection.main_activity.fragment.WallpaperAdapter.onBindViewHolder(WallpaperAdapter.kt:38)
    at com.squadtechs.hdwallpapercollection.main_activity.fragment.WallpaperAdapter.onBindViewHolder(WallpaperAdapter.kt:21)

以下是我的 onBindViewHolder() 代码:

   override fun onBindViewHolder(holder: WallpaperHolder, position: Int) {
    populateViews(holder, position)
}

private fun populateViews(holder: WallpaperHolder, position: Int) {
    val requestQueue = Volley.newRequestQueue(context)
    val imageRequest = ImageRequest(
        list[position].wallpaper_image_url,
        Response.Listener { response ->
            holder.imgGrid.scaleType = ImageView.ScaleType.CENTER
            holder.imgGrid.setImageBitmap(response)
            holder.progress.visibility = View.GONE
        },
        1024,
        860,
        ImageView.ScaleType.CENTER,
        null,
        Response.ErrorListener { error ->
            Toast.makeText(context, "Error loading Image", Toast.LENGTH_LONG).show()
            holder.progress.visibility = View.GONE
        }).setRetryPolicy(
        DefaultRetryPolicy(
            20000,
            DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
            DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
        )
    )
    requestQueue.add(imageRequest)
    holder.txtCategory.visibility = View.GONE
}

根据日志,在声明请求队列的行抛出错误,即val requestQueue = Volley.newRequestQueue(context)

记住:应用程序在正常滚动时运行良好,但在快速滚动时崩溃

每当应该显示之前未绑定到视图(或未绑定)的元素时,您的回收器视图将触发适配器的 onBindViewHolder

当您快速滚动时,绑定和解除绑定也会很快发生。每次bind都会产生一个HTTP请求,是比较耗内存的IO操作

这是灾难的根源。不要像这样基于常规用户交互发送 HTTP 请求。如果有人一直上下滚动,应用程序肯定会 运行 内存不足。

相反,想想更好的策略。可能异步预加载数据,或者至少在加载后缓存数据。

@fjc 正确指出 HTTP 请求是资源密集型的。如果您查看 populateViews 函数的第一行

val requestQueue = Volley.newRequestQueue(context)

这是OOM的主要原因。您正在为每个图像请求创建多个请求队列,因此占用所有资源,从而导致 OOM。为了克服这个问题,您需要为所有应用程序使用一个请求队列。 Google 还建议使用单例 class 来处理请求队列。DOC

if your application makes constant use of the network, it's probably most efficient to set up a single instance of RequestQueue that will last the lifetime of your app. You can achieve this in various ways. The recommended approach is to implement a singleton class that encapsulates RequestQueue and other Volley functionality. Another approach is to subclass Application and set up the RequestQueue in Application.onCreate(). But this approach is discouraged; a static singleton can provide the same functionality in a more modular way.

解决您的问题的一个快速方法是在您的项目中复制以下内容class

class MySingleton constructor(context: Context) {
    companion object {
        @Volatile
        private var INSTANCE: MySingleton? = null
        fun getInstance(context: Context) =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: MySingleton(context).also {
                    INSTANCE = it
                }
            }
    }
    val imageLoader: ImageLoader by lazy {
        ImageLoader(requestQueue,
                object : ImageLoader.ImageCache {
                    private val cache = LruCache<String, Bitmap>(20)
                    override fun getBitmap(url: String): Bitmap {
                        return cache.get(url)
                    }
                    override fun putBitmap(url: String, bitmap: Bitmap) {
                        cache.put(url, bitmap)
                    }
                })
    }
    val requestQueue: RequestQueue by lazy {
        // applicationContext is key, it keeps you from leaking the
        // Activity or BroadcastReceiver if someone passes one in.
        Volley.newRequestQueue(context.applicationContext)
    }
    fun <T> addToRequestQueue(req: Request<T>) {
        requestQueue.add(req)
    }
}

将 populateViews 函数的第一行替换为

val requestQueue = MySingleton.getInstance(context).requestQueue

这应该可以解决您的问题

另一种方法是使用 Volley 工具箱中的 NetworkImageView

如何使用 将您的 ImageView 替换为 NetworkImageView

<com.android.volley.toolbox.NetworkImageView
            android:id="@+id/imgGrid"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:scaleType="centerInside"
            />

并使用

加载图像
holder.imgGrid.setImageUrl(list[position].wallpaper_image_url,MySingleton.getInstance(context).imageLoader);