Kotlin下TextView中HTML内嵌图片下载缓存优化方案求助
问题与优化方案
我曾参考Stack Overflow上的问题实现了两套代码,用于加载HTML内容中<img>标签内的图片,但遇到两个问题:一是部分图片无法被缓存,二是静态Picasso实例会造成应用内存泄漏。现寻求更优实现方案,要求支持Kaolin。
原有实现代码
PicassoCache
public class PicassoCache { // @SuppressLint("StaticFieldLeak") private static Picasso picassoInstance = null; private PicassoCache(Context context) { Downloader downloader = new OkHttp3Downloader(context, Integer.MAX_VALUE); Picasso.Builder builder = new Picasso.Builder(context); builder.downloader(downloader); picassoInstance = builder.build(); } public static Picasso getPicassoInstance(Context context) { if (picassoInstance == null) { //noinspection InstantiationOfUtilityClass new PicassoCache(context); return picassoInstance; } return picassoInstance; } }
PicassoImageGetter
public class PicassoImageGetter implements Html.ImageGetter { private static final String TAG = "PicassoImageGetter"; private TextView textView = null; Context mContext; public PicassoImageGetter(TextView target, Context context) { textView = target; mContext = context; } public PicassoImageGetter() { } @Override public Drawable getDrawable(String source) { BitmapDrawablePlaceHolder drawable = new BitmapDrawablePlaceHolder(); // Picasso.get().load(source).into(drawable); PicassoCache.getPicassoInstance(mContext) .load(source) .priority(Picasso.Priority.HIGH) .error(R.drawable.no_image) .into(drawable); return drawable; } @SuppressWarnings("deprecation") private class BitmapDrawablePlaceHolder extends BitmapDrawable implements com.squareup.picasso.Target { protected Drawable drawable; @Override public void draw(final Canvas canvas) { if (drawable != null) { drawable.draw(canvas); } } public void setDrawable(Drawable drawable) { this.drawable = drawable; int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); drawable.setBounds(0, 0, width, height); setBounds(0, 0, width, height); if (textView != null) { textView.setText(textView.getText()); } } @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { setDrawable(new BitmapDrawable(mContext.getResources(), bitmap)); } @Override public void onBitmapFailed(Exception e, Drawable errorDrawable) { setDrawable(errorDrawable); Log.e(TAG, "onBitmapFailed: "+e.toString() ); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } } }
原有使用方式
val imageGetter = PicassoImageGetter(binding.blogContent, this) val html: Spannable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY, imageGetter, null) as Spannable } else { Html.fromHtml(content, imageGetter, null) as Spannable } binding.blogContent.text = html
效果展示

优化方案
1. 修复内存泄漏
移除静态Picasso实例,改用Application全局初始化,避免持有Activity/Fragment Context导致泄漏:
class MyApp : Application() { override fun onCreate() { super.onCreate() // 设置50MB磁盘缓存,避免缓存配置异常 val downloader = OkHttp3Downloader(this, 50 * 1024 * 1024) val picasso = Picasso.Builder(this) .downloader(downloader) .build() Picasso.setSingletonInstance(picasso) } }
2. 解决缓存不全问题
替换Integer.MAX_VALUE为合理缓存大小(如50MB),避免OkHttp缓存机制异常;Picasso默认已开启内存/磁盘缓存,无需额外配置。
3. 优化ImageGetter,防止Target被GC回收
持有所有加载目标的引用,避免Picasso回调时Target已被回收导致图片加载失败;同时在页面销毁时清理请求,释放资源:
public class PicassoImageGetter implements Html.ImageGetter { private static final String TAG = "PicassoImageGetter"; private final TextView textView; private final Context context; private final List<BitmapDrawablePlaceHolder> targetList = new ArrayList<>(); public PicassoImageGetter(TextView target, Context context) { this.textView = target; // 使用Application Context,避免泄漏 this.context = context.getApplicationContext(); } @Override public Drawable getDrawable(String source) { BitmapDrawablePlaceHolder drawable = new BitmapDrawablePlaceHolder(); targetList.add(drawable); Picasso.get() .load(source) .priority(Picasso.Priority.HIGH) .error(R.drawable.no_image) .into(drawable); return drawable; } // 页面销毁时调用,清理请求和引用 public void clear() { for (BitmapDrawablePlaceHolder target : targetList) { Picasso.get().cancelRequest(target); } targetList.clear(); } @SuppressWarnings("deprecation") private class BitmapDrawablePlaceHolder extends BitmapDrawable implements com.squareup.picasso.Target { protected Drawable drawable; @Override public void draw(final Canvas canvas) { if (drawable != null) { drawable.draw(canvas); } } public void setDrawable(Drawable drawable) { this.drawable = drawable; int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); drawable.setBounds(0, 0, width, height); setBounds(0, 0, width, height); if (textView != null) { textView.setText(textView.getText()); } } @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { setDrawable(new BitmapDrawable(context.getResources(), bitmap)); } @Override public void onBitmapFailed(Exception e, Drawable errorDrawable) { setDrawable(errorDrawable); Log.e(TAG, "onBitmapFailed: " + e.toString()); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } } }
4. 优化使用方式,适配生命周期
val imageGetter = PicassoImageGetter(binding.blogContent, this) val html: Spannable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY, imageGetter, null) as Spannable } else { Html.fromHtml(content, imageGetter, null) as Spannable } binding.blogContent.text = html // 在页面销毁时清理资源 override fun onDestroy() { super.onDestroy() imageGetter.clear() }
5. Kaolin适配说明
如果是在Kaolin UI框架中使用,需确保:
- 使用Kaolin提供的Context或生命周期感知组件获取上下文,避免持有无效引用。
- 若Kaolin使用Compose,可将ImageGetter逻辑适配为Compose支持的图文加载方式,或通过
AndroidView包裹TextView进行兼容。
内容的提问来源于stack exchange,提问作者Dr Mido
相关产品推荐
相关产品推荐

