【发布时间】: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