使用BRVAH遇:ViewHolder views must not be attached when created. Ensure that you are not passing 'true'

本文详细解析了在使用BRVAH的setEmptyView()时遇到的异常问题,该问题并非由常见的onCreateViewHolder()配置不当或ViewPager预加载引起,而是源于BRVAH自身的代码缺陷。通过参考GitHub上的#2796 issue,提供了具体的解决方案。

网上比较普遍的解决方案是 onCreateViewHolder() 中没指定false,

或者,ViewPager、ViewPager2 开启预加载

 

但我是在使用BRVAH的setEmptyView()的时候遇到的,因为报错信息没有具体的行号。

一开始一直以为是上述 2 种问题。

找了半天,最后发现原来是BRVAH自身代码不健壮的问题。

具体解决方案参考 第  #2796 issue,

https://github.com/CymChad/BaseRecyclerViewAdapterHelper/issues/2796

public abstract class HwRecyclerPagerAdapter extends HwPagerAdapter { private static final int CACHE_SIZE = 5; private SparseArray<List<ViewHolder>> caches = new SparseArray<>(); public abstract ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType); public abstract void onBindViewHolder(@NonNull ViewHolder holder, int position); public int getItemViewType(int position) { return 0; } public abstract void onViewRecycled(@NonNull ViewHolder holder); @NonNull @Override public Object instantiateItem(@NonNull ViewGroup viewGroup, int position) { int viewType = getItemViewType(position); //todo 从缓存取 ViewHolder holder = caches.get(viewType).get(0);//fixme 实例代码 if (holder == null) { holder = onCreateViewHolder(viewGroup, position); holder.viewType = viewType; holder.root.setTag(android.R.id.content, holder); } if (holder.root.getParent() != null) { throw new IllegalStateException("ViewHolder views must not be attached when created. "); } onBindViewHolder(holder, position); viewGroup.addView(holder.root); return holder.root; } @Override public void destroyItem(@NonNull ViewGroup viewGroup, int i, @NonNull Object object) { if (!(object instanceof View)) { // log return; } View root = (View) object; ViewHolder holder = (ViewHolder) root.getTag(android.R.id.content); onViewRecycled(holder); List<ViewHolder> queue = caches.get(holder.viewType); if (queue == null) { queue = new ArrayList<>(CACHE_SIZE); } // todo 这里做缓存管理 } public abstract static class ViewHolder { protected int viewType; @NonNull protected View root; public ViewHolder(@NonNull View root) { this.root = root; } } }能把ViewHolder优化成他这样使用
最新发布
09-29
<think>我们正在讨论的是两个适配器:HwRecyclerPagerAdapter 和 PreviewMediaViewPagerAdapter。 目标:将 HwRecyclerPagerAdapter 中的 ViewHolder 优化成 PreviewMediaViewPagerAdapter 中 ViewHolder使用方式。 观察 PreviewMediaViewPagerAdapter 中 ViewHolder使用特点: 1. 使用ViewHolder 模式,将每个页面的视图组件封装在 MediaViewHolder 内部类中。 2. 实现了视图缓存:有两个缓存列表分别缓存图片和视频的 ViewHolder(imageViewHolderCache 和 videoViewHolderCache),并设置最大缓存数量。 3. 在 instantiateItem 方法中,首先尝试从缓存中获取 ViewHolder,如果缓存中没有则新建。 4. 在 destroyItem 方法中,将不再使用ViewHolder 放入缓存以便复用。 而 HwRecyclerPagerAdapter 是一个 RecyclerView.Adapter,它本身已经内置了 ViewHolder 的缓存和复用机制(通过 RecyclerView 的 Recycler 池)。因此,我们不需要自己管理缓存池,RecyclerView 会帮我们管理。 但是,用户希望将 HwRecyclerPagerAdapter 里的 ViewHolder 优化成 PreviewMediaViewPagerAdapter 中 ViewHolder使用方式,这可能意味着: - 希望像 PreviewMediaViewPagerAdapter 那样,将不同类型(如图片和视频)的 ViewHolder 分别缓存,并且限制每种类型的缓存数量。 - 或者,希望将视图的初始化、数据绑定等逻辑封装在 ViewHolder 内部,使代码更清晰。 然而,RecyclerView 本身已经对 ViewHolder 进行了缓存和复用,并且可以设置不同 viewType 的缓存池大小。因此,我们不需要自己维护两个缓存列表(imageViewHolderCache 和 videoViewHolderCache),而是利用 RecyclerView 的机制。 但是,如果我们想要限制某种类型的缓存数量(例如,图片最多缓存5个,视频最多缓存3个),我们可以通过设置 RecyclerView 的 RecycledViewPool 来实现。 具体步骤: 1. 在 HwRecyclerPagerAdapter 中,我们仍然需要定义不同的 ViewHolder 类型(例如 ImageViewHolder 和 VideoViewHolder),并重写 getItemViewType 方法。 2. 我们可以设置 RecyclerView 的 RecycledViewPool 中每种类型的最大缓存数量。 示例代码: 首先,定义两种 ViewHolder 类型: ```java public static final int VIEW_TYPE_IMAGE = 0; public static final int VIEW_TYPE_VIDEO = 1; @Override public int getItemViewType(int position) { MediaSource mediaSource = mMediaSources.get(position); if (PreviewMediaConstant.PREMEDIA_IMAGE.equalsIgnoreCase(mediaSource.getType())) { return VIEW_TYPE_IMAGE; } else if (PreviewMediaConstant.PREMEDIA_VIDEO.equalsIgnoreCase(mediaSource.getType())) { return VIEW_TYPE_VIDEO; } return super.getItemViewType(position); } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == VIEW_TYPE_IMAGE) { View view = inflater.inflate(R.layout.ascf_media_image_viewer_item, parent, false); return new ImageViewHolder(view); } else if (viewType == VIEW_TYPE_VIDEO) { View view = inflater.inflate(R.layout.ascf_media_video_viewer_item, parent, false); return new VideoViewHolder(view); } // 默认情况(可以抛异常或返回一个空视图) return ...; } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { MediaSource mediaSource = mMediaSources.get(position); if (holder instanceof ImageViewHolder && PreviewMediaConstant.PREMEDIA_IMAGE.equalsIgnoreCase(mediaSource.getType())) { ((ImageViewHolder) holder).bind(mediaSource, position); } else if (holder instanceof VideoViewHolder && PreviewMediaConstant.PREMEDIA_VIDEO.equalsIgnoreCase(mediaSource.getType())) { ((VideoViewHolder) holder).bind(mediaSource, position); } } // 定义 ImageViewHolder 和 VideoViewHolder static class ImageViewHolder extends RecyclerView.ViewHolder { PhotoView photoView; ProgressBar progressBar; ImageViewHolder(View itemView) { super(itemView); photoView = itemView.findViewById(R.id.image_view); progressBar = itemView.findViewById(R.id.progress_bar); } void bind(MediaSource mediaSource, int position) { // 绑定数据到图片视图 String url = mediaSource.getUrl(); // 加载图片等操作 } } static class VideoViewHolder extends RecyclerView.ViewHolder { AscfVideoView videoView; ProgressBar progressBar; VideoViewHolder(View itemView) { super(itemView); videoView = itemView.findViewById(R.id.ascf_video_view); progressBar = itemView.findViewById(R.id.progress_bar); } void bind(MediaSource mediaSource, int position) { // 绑定数据到视频视图 String url = mediaSource.getUrl(); String poster = mediaSource.getPoster(); // 加载视频等操作 } } ``` 然后,我们可以设置 RecyclerView 的 RecycledViewPool 的缓存数量: ```java RecyclerView recyclerView = ...; // 你的RecyclerView(作为ViewPager使用) RecyclerView.RecycledViewPool pool = recyclerView.getRecycledViewPool(); // 设置图片类型的最大缓存数量为5 pool.setMaxRecycledViews(VIEW_TYPE_IMAGE, 5); // 设置视频类型的最大缓存数量为5 pool.setMaxRecycledViews(VIEW_TYPE_VIDEO, 5); ``` 这样,RecyclerView 就会自动管理 ViewHolder 的缓存,并且每种类型的缓存数量不超过我们设置的最大值。 因此,我们可以说:HwRecyclerPagerAdapter 中的 ViewHolder 已经通过 RecyclerView 的机制进行了优化,但我们还可以通过设置 RecycledViewPool 来限制每种类型的缓存数量,以达到类似 PreviewMediaViewPagerAdapter 中缓存管理的效果。 另外,PreviewMediaViewPagerAdapter 中在 destroyItem 方法中释放了视频资源,并清除了图片加载(Glide.clear)。在 RecyclerView 的适配器中,我们可以在 onViewRecycled 方法中做类似的事情: ```java @Override public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { super.onViewRecycled(holder); if (holder instanceof VideoViewHolder) { ((VideoViewHolder) holder).videoView.release(); } else if (holder instanceof ImageViewHolder) { // 清除Glide加载 Glide.with(((ImageViewHolder) holder).photoView).clear(((ImageViewHolder) holder).photoView); } } ``` 总结:我们可以通过以下方式优化 HwRecyclerPagerAdapter 中的 ViewHolder: 1. 使用不同类型的 ViewHolder(继承自 RecyclerView.ViewHolder)来封装不同媒体类型的视图。 2. 利用 RecyclerView 的 RecycledViewPool 设置每种 ViewHolder 类型的最大缓存数量。 3. 在 onViewRecycled 方法中释放资源(如视频播放器、图片加载)。 这样优化的效果类似于 PreviewMediaViewPagerAdapter 中 ViewHolder使用方式,但利用了 RecyclerView 的机制,更加标准。 注意:由于 HwRecyclerPagerAdapter 可能已经继承自 RecyclerView.Adapter,所以上述方法可以直接应用。 但是,如果 HwRecyclerPagerAdapter 不是继承自 RecyclerView.Adapter,那么我们需要重构适配器以使用 RecyclerView.Adapter 和 ViewHolder 模式。 因此,回答是:可以优化,具体方式如上所述。 生成的相关问题: 1. 如何在 RecyclerView 中为不同的 ViewType 设置缓存数量? 2. 在 RecyclerView 的适配器中,如何释放图片和视频资源以避免内存泄漏? 3. 如何实现一个支持多种媒体类型(图片、视频)的 ViewPager? </think>### 能否将 HwRecyclerPagerAdapter 的 ViewHolder 优化为 PreviewMediaViewPagerAdapter 的模式? 是的,可以将 `HwRecyclerPagerAdapter` 中的 ViewHolder 优化为类似 `PreviewMediaViewPagerAdapter` 的实现方式,但需要结合 RecyclerView 的特性和 ViewHolder 设计原则进行调整。以下是具体优化方案: #### 1. **ViewHolder 缓存机制优化** `PreviewMediaViewPagerAdapter` 的核心优势在于 **分类型缓存** 和 **主动缓存管理**: ```java // PreviewMediaViewPagerAdapter 的实现 private final List<MediaViewHolder> imageViewHolderCache = new LinkedList<>(); private final List<MediaViewHolder> videoViewHolderCache = new LinkedList<>(); // 从缓存获取 ViewHolder private MediaViewHolder getCachedViewHolder(int viewType) { if (viewType == 0 && !imageViewHolderCache.isEmpty()) return imageViewHolderCache.remove(0); else if (viewType == 1 && !videoViewHolderCache.isEmpty()) return videoViewHolderCache.remove(0); return null; } // 缓存回收 private void cacheViewHolder(int viewType, MediaViewHolder holder) { if (viewType == 0 && imageViewHolderCache.size() < MAX_CACHE_SIZE) imageViewHolderCache.add(holder); else if (viewType == 1 && videoViewHolderCache.size() < MAX_CACHE_SIZE) videoViewHolderCache.add(holder); } ``` **适配到 HwRecyclerPagerAdapter 的方案**: ```java public class HwRecyclerPagerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { // 分类型缓存池 private final SparseArray<Queue<RecyclerView.ViewHolder>> viewHolderCache = new SparseArray<>(); private static final int MAX_CACHE_PER_TYPE = 3; // 每类最大缓存数 @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // 优先从缓存获取 Queue<RecyclerView.ViewHolder> cacheQueue = viewHolderCache.get(viewType); if (cacheQueue != null && !cacheQueue.isEmpty()) { return cacheQueue.poll(); } // 无缓存时新建 return createNewViewHolder(parent, viewType); } @Override public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { super.onViewRecycled(holder); // 回收 ViewHolder 到缓存池 int viewType = holder.getItemViewType(); Queue<RecyclerView.ViewHolder> cacheQueue = viewHolderCache.get(viewType); if (cacheQueue == null) { cacheQueue = new LinkedList<>(); viewHolderCache.put(viewType, cacheQueue); } if (cacheQueue.size() < MAX_CACHE_PER_TYPE) { resetViewHolder(holder); // 重置状态 cacheQueue.offer(holder); } } // 重置 ViewHolder 状态 private void resetViewHolder(RecyclerView.ViewHolder holder) { if (holder instanceof ImageViewHolder) { ((ImageViewHolder) holder).progressBar.setVisibility(View.GONE); Glide.with(holder.itemView).clear(((ImageViewHolder) holder).imageView); } else if (holder instanceof VideoViewHolder) { ((VideoViewHolder) holder).videoView.release(); } } } ``` #### 2. **分类型 ViewHolder 设计** 借鉴 `PreviewMediaViewPagerAdapter` 的媒体类型区分: ```java // 媒体类型常量 private static final int TYPE_IMAGE = 0; private static final int TYPE_VIDEO = 1; @Override public int getItemViewType(int position) { MediaItem item = mediaItems.get(position); return item.isVideo() ? TYPE_VIDEO : TYPE_IMAGE; } // 专用 ViewHolder 类 static class ImageViewHolder extends RecyclerView.ViewHolder { ImageView imageView; ProgressBar progressBar; ImageViewHolder(View itemView) { super(itemView); imageView = itemView.findViewById(R.id.iv_media); progressBar = itemView.findViewById(R.id.progress_bar); } } static class VideoViewHolder extends RecyclerView.ViewHolder { VideoView videoView; ProgressBar progressBar; VideoViewHolder(View itemView) { super(itemView); videoView = itemView.findViewById(R.id.video_view); progressBar = itemView.findViewById(R.id.progress_bar); } } ``` #### 3. **资源加载优化** 结合 `PreviewMediaViewPagerAdapter` 的资源管理策略: ```java @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { MediaItem item = mediaItems.get(position); if (holder instanceof ImageViewHolder) { ImageViewHolder vh = (ImageViewHolder) holder; vh.progressBar.setVisibility(View.VISIBLE); Glide.with(vh.itemView) .load(item.getUrl()) .into(vh.imageView); } else if (holder instanceof VideoViewHolder) { VideoViewHolder vh = (VideoViewHolder) holder; vh.progressBar.setVisibility(View.VISIBLE); vh.videoView.setVideoURI(Uri.parse(item.getUrl())); vh.videoView.setOnPreparedListener(mp -> { vh.progressBar.setVisibility(View.GONE); mp.start(); }); } } ``` #### 4. **内存优化关键点** 1. **缓存限制**: ```java // 设置每类 ViewHolder 最大缓存数 private static final int MAX_CACHE_PER_TYPE = 3; ``` 避免无限缓存导致内存压力[^2]。 2. **资源释放**: ```java @Override public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof VideoViewHolder) { ((VideoViewHolder) holder).videoView.suspend(); } } ``` 3. **全局缓存池**: ```java // 在 Activity/Fragment 中共享缓存池 RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool(); sharedPool.setMaxRecycledViews(TYPE_IMAGE, 5); recyclerView.setRecycledViewPool(sharedPool); ``` ### 优化效果对比 | **特性** | **优化前** | **优化后** | |------------------------|-----------------------------------|-----------------------------------| | 缓存机制 | RecyclerView 默认缓存 | 分类型主动缓存管理 | | 内存占用 | 可能累积未释放的 ViewHolder | 严格控制每类缓存数量 | | 视图复用 | 依赖 RecyclerView 回收策略 | 主动回收 + 状态重置 | | 媒体资源释放 | 需手动处理 | 在 onViewRecycled() 自动触发 | | 多类型支持 | 需自行实现区分逻辑 | 内置分类型 ViewHolder 体系 | ### 总结 通过将 `PreviewMediaViewPagerAdapter` 的 ViewHolder 管理策略移植到 `HwRecyclerPagerAdapter`: 1. 实现 **分类型缓存池**,通过 `SparseArray<Queue>` 管理不同类型 ViewHolder 2. 在 `onViewRecycled()` 中 **主动回收 ViewHolder** 并重置状态 3. 添加 **缓存数量限制**(MAX_CACHE_PER_TYPE)防止内存溢出 4. 保留 RecyclerView 原有的 **高效复用机制** 作为基础 此方案在 RecyclerView 体系下实现了类似 ViewPager 的主动缓存管理,适用于需要高性能媒体预览的场景[^1][^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值