如何实现循环视图滚动期间的java.lang.OutOfMemoryError?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (84)

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缓存图像。

布局可视化:

提问于
用户回答回答于

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

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

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励