首页 文章

RecyclerView滚动期间的java.lang.OutOfMemoryError

提问于
浏览
0

我有 RecyclerView ,其中有另一个项目 . 每个 RecyclerView 项目都有其他项目的列表,如果 RecyclerView 中有 click 项目,则显示为子项目 . 对于 avoid nested RecyclerView 事物,我在 onBindViewHolder() 中迭代这些项目并将它们添加到 empty LinearLayout inflating subitem布局中 .

发生 OutOfMemory 错误,因为我 scroll downbecause 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:有没有办法只加载可见的视图?因此,当用户滚动时,它将加载新视图,并且将从显示器中移除的顶部视图将从内存中移除 . 我认为recyclelerview默认通过回收项目布局视图来执行此操作 .

UPDATE 3: I've implemented new recyclerview and adapter initialization for inner list. This rv and adapter is initialized when view in onBindViewHolder() is marked as expanded and if it's not expanded RV and Adapter is set to null. So I've implemented Nested RecyclerView. Problem is that my inner recyclerview is not scrolling at all. I have to set scrolling and set RV height to fixed size (for example 400dp), because if I leave it match_parent or wrap_content, it will throw OutOfMemoryError if more than 20 items are inside -> it is not recycling views. How to achieve both recyclerviews to scroll vertically?

enter image description here

布局可视化:

enter image description here

1 回答

  • 0

    你的小组(每个包含1000个项目)太大了 .

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

    这样,您只需要在屏幕上填充尽可能多的视图(再加上一些额外的RecyclerView预请求,以避免在滚动时出现过多的通货膨胀) .

相关问题