RecyclerView中VectorDrawable复用优化及Picasso加载适配问题咨询
嘿,这个场景我太熟悉了——RecyclerView里反复创建VectorDrawable确实会触发频繁GC,导致列表滑动掉帧,而且Picasso默认确实不支持VectorDrawable加载。给你几个实用的解决方案,按需选:
方案1:自定义Picasso RequestHandler,让它支持VectorDrawable
Picasso的扩展性很强,我们可以写一个自定义的RequestHandler来处理VectorDrawable资源,同时利用Picasso的内存缓存机制自动复用Drawable对象,不用自己管理缓存。
步骤如下:
- 实现自定义的
VectorDrawableRequestHandler:
public class VectorDrawableRequestHandler extends RequestHandler { private static final String SCHEME = "res"; @Override public boolean canHandleRequest(Request data) { // 处理以res://开头的Uri,比如res://com.your.package/2131230800 return SCHEME.equals(data.uri.getScheme()); } @Override public Result load(Request request, int networkPolicy) throws IOException { Context context = request.context; int resId = context.getResources().getIdentifier( request.uri.getLastPathSegment(), "drawable", context.getPackageName() ); // 用ResourcesCompat加载,兼容旧版本,并且确保Drawable可复用 Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), resId, null); if (drawable == null) { throw new IOException("VectorDrawable not found for resId: " + resId); } return new Result(drawable, Picasso.LoadedFrom.MEMORY); } }
- 在初始化Picasso的时候添加这个Handler:
Picasso picasso = new Picasso.Builder(context) .addRequestHandler(new VectorDrawableRequestHandler()) .build(); // 如果用全局单例,替换默认的Picasso实例 Picasso.setSingletonInstance(picasso);
- 在Adapter里加载VectorDrawable:
// 构造res://格式的Uri,比如resId是R.drawable.ic_vector Uri uri = Uri.parse("res://" + context.getPackageName() + "/" + R.drawable.ic_vector); picasso.load(uri) .into(holder.imageView);
这样Picasso会自动缓存这个Drawable,下次加载直接复用,不用每次新建。
方案2:在Adapter中提前缓存VectorDrawable(适合少量Drawable场景)
如果你的VectorDrawable数量不多(比如只有几种类型的图标),直接在Adapter初始化时把所有需要的Drawable加载好,存在一个Map里,onBind的时候直接取,逻辑更简单。
示例代码:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private HashMap<Integer, Drawable> drawableCache; private Context context; public MyAdapter(Context context) { this.context = context; drawableCache = new HashMap<>(); // 提前加载所有需要的VectorDrawable cacheDrawable(R.drawable.ic_vector_1); cacheDrawable(R.drawable.ic_vector_2); // ... 其他需要的resId } private void cacheDrawable(int resId) { Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), resId, null); if (drawable != null) { // 确保Drawable可安全复用,避免创建副本 drawable.mutate().clearMutated(); drawableCache.put(resId, drawable); } } @Override public void onBindViewHolder(ViewHolder holder, int position) { // 根据position获取对应的resId int resId = getResIdForPosition(position); Drawable drawable = drawableCache.get(resId); if (drawable != null) { holder.imageView.setImageDrawable(drawable); } } // ... 其他Adapter代码 }
这个方案的好处是没有额外依赖,逻辑直接,适合Drawable类型少的场景;缺点是如果Drawable数量太多,提前加载会占用内存。
方案3:换用Glide(原生支持VectorDrawable)
如果不想折腾Picasso的扩展,Glide从4.x版本开始原生支持VectorDrawable加载,而且自带完善的内存/磁盘缓存机制,复用性拉满。
示例代码:
Glide.with(context) .load(R.drawable.ic_vector) .into(holder.imageView);
Glide会自动处理VectorDrawable的复用,不用你操心缓存逻辑,而且对RecyclerView的优化做得很好,滑动流畅度有保障。如果项目可以切换图片加载库,这是最省心的方案。
最后提醒一下:不管用哪种方案,都要注意不要随意调用Drawable的mutate()方法(除非你需要修改某个实例的属性),因为mutate()会创建一个新的Drawable副本,破坏复用性。
内容的提问来源于stack exchange,提问作者f3ell0w

