android - RecyclerView 滚动期间的 java.lang.OutOfMemoryError
问题描述
我有RecyclerView
,里面还有其他物品。每个RecyclerView
项目都有其他项目的列表,如果我单击中的项目,这些项目将在下面显示为子项目RecyclerView
。为了避免嵌套 RecyclerView的事情,我遍历这些项目onBindViewHolder()
并将它们添加到空的 LinearLayout膨胀子项目布局。
OutOfMemory
当我向下滚动时发生错误,because there can be 1000 items and each item could have 1000 subitems.
在我的应用程序中它的订单列表,如果我单击此列表中的项目,订购的零件列表将一一显示。
如何解决这个问题。滚动也变得迟钝。我正在使用 Glide API 来缓存图像,但仍然会出现此错误。
recyclerView = view.findViewById<RecyclerView>(R.id.order_recycler_view).apply {
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
//set cache for rv
setItemViewCacheSize(50)
isDrawingCacheEnabled = true
drawingCacheQuality = View.DRAWING_CACHE_QUALITY_HIGH
}
在 RVAdapter onBindViewHolder() 内部:
for(orderItem in it.itemList){
val contentLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.ordered_list_item_layout, null, false)
fillItemView(contentLayout, orderItem, res)
holder.orderContentLayout.addView(contentLayout)
}
FillItemView 方法:
private fun fillItemView(contentLayout: View, orderItem: OrderedItem, res: Resources){
val orderItemImage: ImageView = contentLayout.findViewById(R.id.orderPartImage)
val orderItemName: TextView = contentLayout.findViewById(R.id.orderPartName)
val orderItemWeight: TextView = contentLayout.findViewById(R.id.orderPartWeight)
val orderPartPhoto: String? = orderItem.item.itemPhoto.optString(ctx.getString(R.string.part_photo))
setOrderedPartLogo(orderItemImage, res, orderPartPhoto)
val itemPrice: String = String.format("%.2f", orderItem.item.itemPrice)
val spanText = SpannableString(foodPrice + " €" + " " + "(" + orderItem.quantity.toString() + "x" + ")" + orderItem.item.itemName)
spanText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, itemPrice.length + 2, 0)
orderItemName.text = spanText
orderItemWeight.text = orderItem.item.itemWeigth
}
private fun setOrderedPartLogo(orderImageView: ImageView, resources: Resources, imageURL: String?) {
val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.no_photo)
val roundedBitMapDrawable: RoundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap)
roundedBitMapDrawable.isCircular = true
val requestOptions: RequestOptions = RequestOptions()
.circleCrop()
.placeholder(roundedBitMapDrawable)
.error(roundedBitMapDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
Glide.with(orderImageView.context)
.load(imageURL)
.apply(requestOptions)
.into(orderImageView)
}
整个适配器:
class OrderListAdapter(private var mActivity: FragmentActivity,
private var orderList: ArrayList<Order>, private var fragment: OrderListFragment):
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var expandedPosition = -1
private lateinit var mRecyclerView: RecyclerView
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//order item views
val orderName: TextView = itemView.findViewById(R.id.orderName)
val orderWaitTime: TextView = itemView.findViewById(R.id.orderWaitTime)
val orderAddress: TextView = itemView.findViewById(R.id.orderAddress)
val orderDate: TextView = itemView.findViewById(R.id.orderDate)
//details expandable layout
val orderDetailsExpandable: LinearLayout = itemView.findViewById(R.id.orderDetails)
val orderContentLayout: LinearLayout = itemView.findViewById(R.id.contentLayout)
val orderLayout: ConstraintLayout = itemView.findViewById(R.id.mainLayout)
}
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder {
// create a new view
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.order_recyclerview_item_layout, parent, false)
return ViewHolder(itemView)
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
mRecyclerView = recyclerView
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolder) {
val res = holder.itemView.context.resources
val ctx = holder.itemView.context
orderList[position].let {
val orderPrice: Int = it.orderJSON.getInt(ctx.getString(R.string.order_total_price))
val orderSupplierName: String? = it.orderSupplier?.json?.getString(ctx.getString(R.string.sup_name))
val orderDate: String = it.orderJSON.getString(ctx.getString(R.string.order_date))
val orderPaymentType: Int = it.orderJSON.getInt(ctx.getString(R.string.order_payment_type))
var orderPaymentTypeString = "unknown"
val orderDeliveryType: Int = it.orderJSON.getInt(ctx.getString(R.string.order_delivery_type))
val orderDeliveryPrice: Int = it.orderJSON.getInt(ctx.getString(R.string.order_delivery_price))
val orderJSONObject: JSONObject = it.orderJSON
val orderItemList: ArrayList<OrderedItem> = it.partsList
//OrderDate -> hours, minutes, day, month, year
val formattedOrderDate: OrderDate = getOrderDate(orderDate)
when(orderPaymentType){
1 -> orderPaymentTypeString = "credit"
2 -> orderPaymentTypeString = "credit"
3 -> orderPaymentTypeString = "money"
4 -> orderPaymentTypeString = "voucher"
}
//set order price, name and type
val orderPriceString: String = convertCentsToFloat(orderPrice)
if (orderSupplierName == null){
val spannableText = SpannableString(orderPriceString + " € " + ctx.getString(R.string.default_sup_name) + " - " + orderPaymentTypeString)
spannableText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, orderPriceString.length + 3, 0)
holder.orderName.text = spannableText
} else {
val spannableText = SpannableString(orderPriceString + " € " + orderSupplierName + " - " + orderPaymentTypeString)
spannableText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, orderPriceString.length + 3, 0)
holder.orderName.text = spannableText
}
//set order wait time
holder.orderWaitTime.text = formattedOrderDate.dateHours + ":" + formattedOrderDate.dateMinutes
//set order address
//holder.orderAddress.text = it.orderAddress
//set order date
holder.orderDate.text = formattedOrderDate.dateDay + "." + formattedOrderDate.dateMonth + "." + formattedOrderDate.dateYear
holder.orderContentLayout.removeAllViews()
//create layout for order items
for(orderItem in it.itemList){
val contentLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.ordered_list_item_layout, null, false)
fillItemView(contentLayout, orderItem, res, ctx)
holder.orderContentLayout.addView(contentLayout)
}
//create footer delivery
val deliveryLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.order_delivery_footer_layout, null, false)
fillDeliveryFooter(deliveryLayout, orderDeliveryType, orderDeliveryPrice, res, ctx)
holder.orderContentLayout.addView(deliveryLayout)
//create footer orderRepeat Button
val orderRepeatLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.order_repeat_order_layout, null, false)
holder.orderContentLayout.addView(orderRepeatLayout)
orderRepeatLayout.setOnClickListener {
fragment.switchToOrderCartActivity(orderItemList)
}
//expanding order view on click
val isExpanded = position == expandedPosition
holder.orderDetailsExpandable.visibility = if (isExpanded) View.VISIBLE else View.GONE
holder.itemView.isActivated = isExpanded
holder.orderLayout.setOnClickListener {
createLog("expPos ", position.toString())
orderList[position].let {
if(expandedPosition != position){
if(expandedPosition != -1){
val myLayout: View? = mRecyclerView.layoutManager.findViewByPosition(expandedPosition)
createLog("myLayout", myLayout.toString())
createLog("OrderExp", "Expanding layout")
if(myLayout != null){
myLayout.findViewById<LinearLayout>(R.id.orderDetails).visibility = View.GONE
}
}
createLog("expPosSet ", position.toString())
expandedPosition = position
} else {
expandedPosition = -1
}
notifyItemChanged(position)
scrollToTop(holder.itemView)
}
}
}
}
}
override fun getItemCount() = orderList.size
private fun scrollToTop(v: View) {
val itemToScroll = mRecyclerView.getChildAdapterPosition(v)
val centerOfScreen = mRecyclerView.width / 2 - v.width / 2
fragment.getRecyclerViewManager().scrollToPositionWithOffset(itemToScroll, centerOfScreen)
}
private fun fillItemView(contentLayout: View, orderItem: OrderedItem, res: Resources){
val orderItemImage: ImageView = contentLayout.findViewById(R.id.orderPartImage)
val orderItemName: TextView = contentLayout.findViewById(R.id.orderPartName)
val orderItemWeight: TextView = contentLayout.findViewById(R.id.orderPartWeight)
val orderPartPhoto: String? = orderItem.item.itemPhoto.optString(ctx.getString(R.string.part_photo))
setOrderedPartLogo(orderItemImage, res, orderPartPhoto)
val itemPrice: String = String.format("%.2f", orderItem.item.itemPrice)
val spanText = SpannableString(foodPrice + " €" + " " + "(" + orderItem.quantity.toString() + "x" + ")" + orderItem.item.itemName)
spanText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, itemPrice.length + 2, 0)
orderItemName.text = spanText
orderItemWeight.text = orderItem.item.itemWeigth
}
private fun fillDeliveryFooter(deliveryLayout: View, deliveryType: Int, deliveryPrice: Int, res: Resources, ctx: Context){
val deliveryImageIcon: ImageView = deliveryLayout.findViewById(R.id.deliveryIconImage)
val deliveryPriceTextView: TextView = deliveryLayout.findViewById(R.id.deliveryLabelText)
//set delivery icon
when(deliveryType){
1 -> deliveryImageIcon.setImageResource(R.drawable.ico_summary_delivery)
2 -> deliveryImageIcon.setImageResource(R.drawable.ico_summary_pickup)
else -> deliveryImageIcon.setImageResource(R.drawable.restauracia_no_photo)
}
//set delivery price, name label
val deliveryPriceString: String = convertCentsToFloat(deliveryPrice)
val deliverySpannable = SpannableString(deliveryPriceString + " € Doprava / Vyzdvihnutie")
deliverySpannable.setSpan(ForegroundColorSpan(res.getColor(R.color.colorPrice)), 0, deliveryPriceString.length + 2, 0)
deliveryPriceTextView.text = deliverySpannable
}
private fun setOrderedPartLogo(orderImageView: ImageView, resources: Resources, imageURL: String?) {
val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.no_photo)
val roundedBitMapDrawable: RoundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap)
roundedBitMapDrawable.isCircular = true
val requestOptions: RequestOptions = RequestOptions()
.circleCrop()
.placeholder(roundedBitMapDrawable)
.error(roundedBitMapDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
Glide.with(orderImageView.context)
.load(imageURL)
.apply(requestOptions)
.into(orderImageView)
}
private fun convertCentsToFloat(centPrice: Int): String {
val centOnlyPrice: Int = centPrice % 100
val euroPrice: Int = (centPrice - centOnlyPrice) / 100
if (centOnlyPrice < 10) {
val finalPrice: String = euroPrice.toString() + ".0" + centOnlyPrice.toString()
return finalPrice
} else {
val finalPrice: String = euroPrice.toString() + "." + centOnlyPrice.toString()
return finalPrice
}
}
private fun getOrderDate(date: String): OrderDate{
val rawDate: List<String> = date.split("T")
val dateOnly: String = rawDate[0]
val dateFormat: List<String> = dateOnly.split("-")
val timeOnly: String = rawDate[1]
val timeFormat: List<String> = timeOnly.split(":")
val finalDate = OrderDate(timeFormat[0], timeFormat[1], dateFormat[2], dateFormat[1], dateFormat[0])
return finalDate
}
fun createLog(tag: String, msg: String){
Log.i(tag, msg)
}
fun refreshOrder(orderListRefreshed: ArrayList<Order>){
orderList = orderListRefreshed
notifyDataSetChanged()
if(AndroidAssets.getInstance(mActivity).orderList.isEmpty()){
mRecyclerView.visibility = View.GONE
fragment.showFooterLayout()
} else{
mRecyclerView.visibility = View.VISIBLE
fragment.hideFooterLayout()
}
fragment.hideProgressBar()
}
}
来自 AndroidProfiler 关于内存使用情况的图像(内存使用量增加了大约 25 秒 - 这是我开始滚动 RecyclerView 的地方......然后它下降了)。
更新:通过更好的分析,我发现一个 SubItem 有 2.5MB 的内存。如果我有 5 个订单,每个订单包含 20 件商品,它将在 RAM 中分配 250MB 的空间。这与 Glide 缓存图像有关。
更新 2:有什么方法可以只加载可见视图?因此,当用户滚动时,它将加载新的视图,并且将关闭显示的顶部视图将从内存中删除。我认为 recyclerview 默认通过回收项目布局视图来做到这一点。
更新 3:我已经为内部列表实现了新的 recyclerview 和适配器初始化。此 rv 和适配器在 onBindViewHolder() 中的视图标记为展开时初始化,如果未展开 RV 和 Adapter 设置为 null。所以我实现了 Nested RecyclerView。问题是我的内部 recyclerview 根本没有滚动。我必须设置滚动并将 RV 高度设置为固定大小(例如 400dp),因为如果我保留它 match_parent 或 wrap_content,如果里面有超过 20 个项目,它将抛出 OutOfMemoryError -> 它不是回收视图。如何实现两个recyclerviews垂直滚动?
布局可视化:
解决方案
您的组(每个包含 1000 个项目)太大了。
您应该展平层次结构——也就是说,将所有项目单独公开给 RecyclerView。您的适配器将需要覆盖 getItemViewType() 以针对不同的视图类型返回不同的值,并且您需要根据每个位置的项目返回不同类型的视图持有者。
这样一来,您只会在屏幕上显示尽可能多的视图(加上 RecyclerView 预先请求的一些额外视图,以避免在滚动时过度膨胀)。
推荐阅读
- mysql - MySQL - GROUP BY,分组时只选择第一行
- http - 除了 HTTP 标头中的接受语言之外,还有其他方法可以确定客户端语言环境吗?
- python - 虽然循环没有按预期退出
- mercurial - 如何正确使用 hg 共享扩展?
- python - 重新加载如何将库文件处理为`so`
- python - 数据点是否在矩形内?
- xamarin - Xamarin.iOS 上缺少 DispatchQoS 结构
- python - 如何通过 python 将 json 文件/数据发布到 api url
- python - 如果行等于python中的值,则打印列名
- excel - 我们是否有“存在”功能来使用 VBA 搜索 excel 工作表