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

如何提升RecyclerView中使用fromHTML实现的ImageGetter运行效率

优化RecyclerView中Html.fromHtml加载带图HTML的性能方案

核心问题原因

原生Html.fromHtml默认在调用线程执行,若直接在主线程/RecyclerView的onBindViewHolder中调用,HTML解析+ImageGetter同步拉取图片的逻辑会全部阻塞主线程,再加上RecyclerView绑定过程的频繁调用,必然会出现主线程过载、卡顿甚至崩溃的问题。另外ImageGetter要求同步返回Drawable的设计,本身和主流异步图片加载库的逻辑冲突,也是你无法直接拿到Drawable的核心原因。


具体优化方案

1. 把HTML解析逻辑完全迁移到子线程

  • 不要在主线程或绑定逻辑中调用Html.fromHtml,拿到列表数据源后,直接通过线程池/协程批量完成所有条目的HTML解析,把解析后的Spanned对象缓存到你的条目数据类中,绑定ViewHolder时直接给TextView设置预解析好的Spanned即可。
  • Android 7.0及以上版本Html.fromHtml本身是线程安全的,可放心在子线程调用。

2. 实现异步ImageGetter,适配主流图片加载库

不需要等待图片加载完成再返回Drawable,先同步返回占位Drawable,异步加载完成后再局部刷新TextView即可,以下是Glide的实现示例:

class GlideImageGetter(
    private val textView: TextView,
    private val glide: RequestManager
) : Html.ImageGetter {
    override fun getDrawable(source: String?): Drawable {
        // 先返回占位Drawable,提前设置好默认尺寸
        val placeholder = ContextCompat.getDrawable(textView.context, R.drawable.default_placeholder)!!
        placeholder.setBounds(0, 0, placeholder.intrinsicWidth, placeholder.intrinsicHeight)
        
        // 异步加载目标图片
        glide.asDrawable()
            .load(source)
            .into(object : CustomTarget<Drawable>() {
                override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
                    resource.setBounds(0, 0, resource.intrinsicWidth, resource.intrinsicHeight)
                    // 替换Spannable中的占位ImageSpan
                    val spannable = textView.text as Spannable
                    val targetSpan = spannable.getSpans(0, spannable.length, ImageSpan::class.java)
                        .find { it.drawable == placeholder }
                    targetSpan?.let {
                        spannable.removeSpan(it)
                        spannable.setSpan(
                            ImageSpan(resource), 
                            spannable.getSpanStart(it), 
                            spannable.getSpanEnd(it), 
                            spannable.getSpanFlags(it)
                        )
                    }
                    // 仅触发当前TextView重绘,不需要刷新整个RecyclerView条目
                    textView.text = spannable
                }

                override fun onLoadCleared(placeholder: Drawable?) = Unit
            })
        return placeholder
    }
}

Coil、Picasso都可以参考这个逻辑实现,不需要修改原有加载逻辑。

3. 优化RecyclerView更新逻辑

  • 不要全量调用notifyDataSetChanged(),使用DiffUtil计算新旧数据集差异,仅更新变化的条目,减少不必要的绑定操作。
  • 全量更新数据集时,把DiffUtil.calculateDiff逻辑放到子线程执行,计算完差异后再切回主线程调用dispatchUpdatesTo(adapter)更新,避免大数据量下Diff计算阻塞主线程。
  • 分页加载数据时调用notifyItemRangeInserted()做局部刷新,不要触发全量更新。
  • 可监听RecyclerView滑动状态,滑动过程中暂停图片加载,滑动停止后再恢复,减少主线程压力。

4. 额外优化点

  • 可预先解析HTML中的所有图片URL,提前预加载到图片库的内存缓存中,ImageGetter调用时可直接拿到缓存的Drawable,不需要等待网络请求。
  • 加载图片时通过图片库的override()方法限制最大尺寸,避免加载过大图片占用过多内存、增加绘制耗时。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 09:39:02