You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含RecyclerView的ConstraintLayout转Bitmap异常或空白问题求助

解决包含RecyclerView的视图转Bitmap时OOM或空白问题

问题原因分析

第一种实现(OOM错误)

  1. 代码存在语法错误:view.layout(0, 0, view.getMeasuredWidth() view.getMeasuredHeight());缺少逗号,导致视图布局异常;
  2. 直接按屏幕尺寸创建Bitmap,若设备分辨率较高或需生成RecyclerView全部内容,Bitmap占用内存过大,触发内存溢出;
  3. 未及时回收Bitmap对象,内存占用持续累积。

第二种实现(空白Bitmap)

  1. 未调用buildDrawingCache()构建最新绘制缓存,仅启用缓存无法获取有效内容;
  2. 点击事件触发时,视图可能尚未完成绘制,导致缓存为空;
  3. RecyclerView的绘制缓存仅包含当前屏幕可见的Item,且未正确构建时会返回空白;
  4. 未在使用后关闭绘制缓存,可能导致后续缓存异常。

解决方案

根据需求选择对应实现:

方案1:生成当前屏幕显示内容的Bitmap(适合仅需可见区域作为壁纸)

核心要点:

  • 等待视图绘制完成后再生成缓存;
  • 手动构建绘制缓存并及时释放;
  • 缩放Bitmap至壁纸所需尺寸,减少内存占用;
  • 使用后回收Bitmap释放内存。

代码示例:

val viewModel: TaskViewModle = ViewModelProvider(this).get(TaskViewModle::class.java)
val viewContent: View = findViewById(R.id.includedView)
val wallBtn: Button = findViewById(R.id.wallBTN)
val wm = WallpaperManager.getInstance(this)
val recyclerView: RecyclerView = findViewById(R.id.taskRecycler)
val quoteAdapter = TaskAdapter(this)

recyclerView.adapter = quoteAdapter
recyclerView.layoutManager = LinearLayoutManager(this)

viewModel.readAllTask.observe(this, Observer { task ->
    quoteAdapter.setData(task)
})

wallBtn.setOnClickListener {
    // 等待视图完成绘制
    viewContent.post {
        viewContent.setDrawingCacheEnabled(true)
        // 构建高质量绘制缓存
        viewContent.buildDrawingCache(true)
        val cacheBitmap = viewContent.drawingCache ?: return@setOnClickListener
        
        // 创建缓存副本,避免缓存被系统回收
        val bm = Bitmap.createBitmap(cacheBitmap)
        // 关闭绘制缓存
        viewContent.setDrawingCacheEnabled(false)
        
        try {
            // 获取壁纸所需最小尺寸,缩放Bitmap减少内存占用
            val desiredWidth = wm.desiredMinimumWidth
            val desiredHeight = wm.desiredMinimumHeight
            val scaledBitmap = Bitmap.createScaledBitmap(bm, desiredWidth, desiredHeight, true)
            
            wm.setBitmap(scaledBitmap)
            
            // 回收Bitmap释放内存
            bm.recycle()
            scaledBitmap.recycle()
            Log.d("Success", "壁纸设置成功")
        } catch (e: IOException) {
            Log.e("error :(", "无法设置壁纸", e)
            bm.recycle()
        }
    }
}

方案2:生成RecyclerView全部内容的Bitmap(适合需完整列表作为壁纸)

核心要点:

  • 计算RecyclerView所有Item的总高度;
  • 创建对应尺寸的Bitmap并绘制所有Item;
  • 保存并恢复RecyclerView的滚动状态;
  • 缩放Bitmap至壁纸尺寸,避免OOM。

代码示例:

val viewModel: TaskViewModle = ViewModelProvider(this).get(TaskViewModle::class.java)
val wallBtn: Button = findViewById(R.id.wallBTN)
val wm = WallpaperManager.getInstance(this)
val recyclerView: RecyclerView = findViewById(R.id.taskRecycler)
val quoteAdapter = TaskAdapter(this)

recyclerView.adapter = quoteAdapter
recyclerView.layoutManager = LinearLayoutManager(this)

viewModel.readAllTask.observe(this, Observer { task ->
    quoteAdapter.setData(task)
})

wallBtn.setOnClickListener {
    recyclerView.post {
        val fullBitmap = getFullRecyclerViewBitmap(recyclerView) ?: return@post
        
        try {
            val desiredWidth = wm.desiredMinimumWidth
            val desiredHeight = wm.desiredMinimumHeight
            val scaledBitmap = Bitmap.createScaledBitmap(fullBitmap, desiredWidth, desiredHeight, true)
            
            wm.setBitmap(scaledBitmap)
            
            fullBitmap.recycle()
            scaledBitmap.recycle()
            Log.d("Success", "完整列表壁纸设置成功")
        } catch (e: IOException) {
            Log.e("error :(", "无法设置壁纸", e)
            fullBitmap.recycle()
        }
    }
}

// 计算RecyclerView所有Item的总高度(含分割线)
fun getRecyclerViewTotalHeight(recyclerView: RecyclerView): Int {
    var totalHeight = 0
    val adapter = recyclerView.adapter ?: return 0
    val layoutManager = recyclerView.layoutManager ?: return 0
    
    for (i in 0 until adapter.itemCount) {
        val holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i))
        adapter.onBindViewHolder(holder, i)
        holder.itemView.measure(
            MeasureSpec.makeMeasureSpec(recyclerView.width, MeasureSpec.EXACTLY),
            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
        )
        totalHeight += holder.itemView.measuredHeight
        
        // 添加分割线高度(如果有)
        recyclerView.itemDecorationCount.takeIf { it > 0 }?.let { count ->
            for (j in 0 until count) {
                val decoration = recyclerView.getItemDecorationAt(j)
                val outRect = Rect()
                decoration.getItemOffsets(outRect, holder.itemView, recyclerView, null)
                totalHeight += outRect.bottom - outRect.top
            }
        }
    }
    return totalHeight
}

// 生成包含RecyclerView全部内容的Bitmap
fun getFullRecyclerViewBitmap(recyclerView: RecyclerView): Bitmap? {
    val totalHeight = getRecyclerViewTotalHeight(recyclerView)
    if (totalHeight == 0 || recyclerView.width == 0) return null
    
    // 使用RGB_565格式节省内存(比ARGB_8888少一半内存)
    val bitmap = Bitmap.createBitmap(recyclerView.width, totalHeight, Bitmap.Config.RGB_565)
    val canvas = Canvas(bitmap)
    
    // 保存当前滚动状态
    val layoutManager = recyclerView.layoutManager as LinearLayoutManager
    val firstVisiblePos = layoutManager.findFirstVisibleItemPosition()
    val firstView = layoutManager.findViewByPosition(firstVisiblePos)
    val topOffset = firstView?.top ?: 0
    
    // 滚动到顶部,确保所有Item都能被绘制
    layoutManager.scrollToPositionWithOffset(0, 0)
    recyclerView.invalidate()
    
    // 将RecyclerView绘制到Canvas
    recyclerView.draw(canvas)
    
    // 恢复原来的滚动位置
    layoutManager.scrollToPositionWithOffset(firstVisiblePos, topOffset)
    
    return bitmap
}

内容的提问来源于stack exchange,提问作者Anka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 15:30:58