【问题标题】:java.lang.OutOfMemoryError during RecyclerView scrollingRecyclerView 滚动期间的 java.lang.OutOfMemoryError
【发布时间】:2018-10-09 18:00:21
【问题描述】:

我有RecyclerView,里面还有其他物品。如果我单击 RecyclerView 中的项目,每个RecyclerView 项目都有其他项目的列表,这些项目在下面显示为子项目。为了避免嵌套 RecyclerView 的事情,我在 onBindViewHolder() 中迭代这些项目并将它们添加到 empty 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垂直滚动?

布局可视化:

【问题讨论】:

  • 这不是您问题的答案。只是一个建议。不要在 onBindHolder() 中使用 for 循环,也不要在其中使用 Inflate 布局。这会影响性能。你必须改变你的方法。
  • 如果您可以分享您的适配器,请
  • 在几种情况下(尤其是在较低的 API 设备上运行)从 Glide 更改为 Picasso 为我解决了这个问题。
  • 我已完全禁用图像,但仍然存在内存不足错误。

标签: android performance android-recyclerview kotlin


【解决方案1】:

您的组(每个组包含 1000 个项目)太大了。

您应该展平层次结构——也就是说,将所有项目单独公开给 RecyclerView。您的适配器将需要覆盖 getItemViewType() 以针对不同的视图类型返回不同的值,并且您需要根据每个位置的项目返回不同类型的视图持有者。

通过这种方式,您将只能在屏幕上显示尽可能多的视图(加上 RecyclerView 预先请求的一些额外视图,以避免在滚动时过度膨胀)。

【讨论】:

  • 问题是内存不足,10 个项目包含 10 个子项目(每个)。我不知道recyclerview的性能这么差。
  • 总共 100 次浏览应该不是问题。我不认为 RecyclerView 应该归咎于此。使用 Android Studio 的内置分配跟踪功能找出哪些分配给您带来了麻烦。
  • @martin1337 看看这个链接android.jlelse.eu/…我正在尝试使用嵌套回收器视图任何帮助将不胜感激这里是链接stackoverflow.com/questions/52747527/…
猜你喜欢
  • 2015-11-10
  • 1970-01-01
  • 2014-10-30
  • 2016-01-09
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多