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);
我在 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);