含RecyclerView的ConstraintLayout转Bitmap异常或空白问题求助
解决包含RecyclerView的视图转Bitmap时OOM或空白问题
问题原因分析
第一种实现(OOM错误)
- 代码存在语法错误:
view.layout(0, 0, view.getMeasuredWidth() view.getMeasuredHeight());缺少逗号,导致视图布局异常; - 直接按屏幕尺寸创建Bitmap,若设备分辨率较高或需生成RecyclerView全部内容,Bitmap占用内存过大,触发内存溢出;
- 未及时回收Bitmap对象,内存占用持续累积。
第二种实现(空白Bitmap)
- 未调用
buildDrawingCache()构建最新绘制缓存,仅启用缓存无法获取有效内容; - 点击事件触发时,视图可能尚未完成绘制,导致缓存为空;
- RecyclerView的绘制缓存仅包含当前屏幕可见的Item,且未正确构建时会返回空白;
- 未在使用后关闭绘制缓存,可能导致后续缓存异常。
解决方案
根据需求选择对应实现:
方案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
相关产品推荐
相关产品推荐

