java.lang.OutOfMemoryError во время прокрутки RecyclerView - PullRequest
0 голосов
/ 09 октября 2018

У меня есть 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 ... и после этого он упал).

ОБНОВЛЕНИЕ: с лучшим анализом я обнаружил, что один подэлемент имеет 2,5 МБ в памяти.Если у меня будет 5 заказов, содержащих по 20 предметов каждый, он выделит 250 МБ пространства в оперативной памяти.И это с кешированием изображений Glide.

ОБНОВЛЕНИЕ 2: Есть ли способ загрузить только видимые виды?Поэтому, когда пользователь прокручивает, он загружает новые виды, а верхний, который будет отключен от дисплея, будет удален из памяти.Я думал, что утилита RecycleView делает это по умолчанию, перерабатывая представления макетов элементов.

ОБНОВЛЕНИЕ 3: я реализовал новый просмотр и инициализацию адаптера для внутреннего списка.Этот rv и адаптер инициализируются, когда представление в onBindViewHolder () помечается как развернутое, и если оно не развернуто, то RV и Adapter установлены в null.Итак, я реализовал Nested RecyclerView.Проблема в том, что мой внутренний просмотрщик вообще не прокручивает.Я должен установить прокрутку и установить высоту RV на фиксированный размер (например, 400dp), потому что, если я оставлю его match_parent или wrap_content, он выдаст OutOfMemoryError, если внутри более 20 элементов -> это не переработка просмотров.Как добиться того, чтобы оба вида рециркуляции прокручивались вертикально?

enter image description here

Визуализация макета:

enter image description here

1 Ответ

0 голосов
/ 09 октября 2018

Ваши группы (каждая из которых содержит 1000 элементов) просто слишком велики.

Вы должны сгладить иерархию, то есть открыть все элементы по отдельности для RecyclerView.Вашему адаптеру потребуется переопределить getItemViewType (), чтобы возвращать разные значения для разных типов представлений, и вам нужно будет возвращать разные типы держателей представлений в зависимости от элемента в каждой позиции.

Таким образом, вы будетераздувать только то количество представлений, которое умещается на экране (плюс несколько дополнительных, которые RecyclerView предварительно запрашивает, чтобы избежать слишком большого надувания во время прокрутки).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...