提高列表滑动流畅度和响应速度-RecyclerView的Prefetch机制源码解析

开发
在RecycleView的使用过程中,Prefetch技术可以大大提高列表的滑动流畅度和响应速度。

RecycleView的Prefetch原理是优化列表滑动性能和响应速度的重要机制。在RecycleView的使用过程中,Prefetch技术可以大大提高列表的滑动流畅度和响应速度。

Prefetch机制原理

RecycleView的Prefetch技术是在用户滑动列表时,系统提前预加载下一页或上一页的数据,以便在用户滑动到这些页面时能够快速显示数据,从而提高列表的滑动流畅度和响应速度,通过减少因数据加载和视图创建而导致的延迟,来改善用户体验。

(1) 依赖组件:

  • LayoutManager:负责计算并确定每个ItemView的位置和大小。
  • Adapter:负责提供数据并创建ItemView。

(2) 工作流程:

  • 当用户开始滑动列表时,LayoutManager会检测到滑动方向和速度。
  • 根据滑动方向和速度,LayoutManager计算出需要预加载的Item数量。
  • LayoutManager通过调用Adapter的prepareForPreLayout方法或类似机制来通知Adapter进行预加载。
  • Adapter根据传入的参数(如预加载的Item数量、位置等),从数据源中获取数据并创建ItemView。
  • 预加载的ItemView会被添加到RecycleView的Scrap缓存中,以便在需要时快速复用。

(3) 优化细节:

  • 系统会跟踪每个view type创建和绑定的平均时间,以预测未来创建和绑定的所需时间,从而更准确地安排预取任务。
  • 对于嵌套的RecyclerView,需要特别处理以确保内部RecyclerView也能进行预取。

Prefetch机制源码解析

(1) 计算需要预加载的Item数量:LayoutManager会在onLayoutChildren方法中调用Adapter的prepareForPreLayout方法来计算需要预加载的Item数量。根据LayoutManager的方向和滑动速度来计算需要预加载的Item数量,通过LayoutManager获取当前显示的第一个和最后一个数据项的位置,根据滑动方向来判断需要预加载哪些数据项。

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    // 绑定ViewHolder时进行预加载
    if (mLayoutManager != null) {
        int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
        int lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();

        // 向下滑动
        if (position > lastVisibleItem) {
            preload(position + 1);
        }
        // 向上滑动
        else if (position < firstVisibleItem) {
            preload(position - 1);
        }
    }

    // 绑定数据到ViewHolder
    holder.bindData(mData.get(position));
}

private void preload(int position) {
    // 预加载下一个数据项
    if (position >= 0 && position < mData.size()) {
        mData.get(position).preload();
    }
}

获取当前显示的第一个和最后一个数据项的位置,在绑定ViewHolder时判断滑动方向并进行预加载,通过调用Adapter的getItem方法来获取数据并创建ItemView。

@Override
public void prepareForPreLayout() {
    final int prefetchDistance = getExtraLayoutSpace(state);
    final int prefetchItemCount = prefetchDistance / mOrientationHelper.getTotalSpace();
    final int firstVisibleItem = getFirstChildPosition();
    if (mOrientation == VERTICAL) {
        for (int i = 1; i <= prefetchItemCount; i++) {
            final int position = firstVisibleItem + i;
            if (position < getItemCount()) {
                mPrefetchArray[i] = position;
            } else {
                break;
            }
        }
    } else {
        for (int i = 1; i <= prefetchItemCount; i++) {
            final int position = firstVisibleItem - i;
            if (position >= 0) {
                mPrefetchArray[i] = position;
            } else {
                break;
            }
        }
    }
}

LayoutManager通过调用Adapter的prepareForPreLayout方法来通知Adapter进行预加载。

(2) 获取数据并创建ItemView:Adapter根据传入的预加载的Item数量、位置等,从数据源中获取数据并创建ItemView。

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    final ViewHolder holder = createViewHolder(parent, viewType);
    if (mPrefetchMaxCountObserved > 0) {
        holder.itemView.addOnAttachStateChangeListener(mAttachListener);
    }
    return holder;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    onBindViewHolder(holder, position, mPayloads);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
    mPrefetchRegistry.markFetched(position);
    bindViewHolder(holder, position, payloads);
    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    if (lp instanceof LayoutParams) {
        ((LayoutParams) lp).mInsetsDirty = true;
    }
}

(3) 添加到Scrap缓存中:预加载的ItemView会被添加到RecycleView的Scrap缓存中,以便在需要时快速复用。

private void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
    RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
    final View itemView = holder.itemView;
    final RecyclerView.ViewHolder oldCachedViewHolder = getChangedHolder(itemView);
    if (oldCachedViewHolder != null) {
        unscrapView(oldCachedViewHolder);
    }
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    mRecyclerPool.putRecycledView(holder);
}

Prefetch执行时机

Prefetch执行时机不是直接暴露给开发者进行精确控制的API,RecyclerView内部使用了一种复杂的机制来预测哪些项可能很快会被需要,基于这些预测来触发数据的预取。

影响Prefetch执行时机的主要因素:

  • 滚动速度:当用户快速滚动列表时,RecyclerView会预测更多的项需要被预取,因为屏幕上的内容会更快地改变。如果用户滚动得很慢,可能只预取少数几个项。
  • 屏幕大小和列表项大小:这些因素决定了在屏幕上可以同时显示多少个列表项。较大的屏幕或较小的列表项可能会导致RecyclerView预测需要预取更多的数据。
  • RecyclerView的布局管理器(LayoutManager):不同的布局管理器(如LinearLayoutManager、GridLayoutManager等)可能有不同的滚动行为和性能特征,也会影响Prefetch的执行时机。
  • RecyclerView.LayoutManager的onLayoutCompleted和onScrollStateChanged方法:这些方法被用来通知RecyclerView关于其布局和滚动状态的变化。虽然这些方法不直接控制Prefetch,但可以用来了解何时可能会触发Prefetch。
  • 自定义的Prefetch距离:在某些情况下,开发者可能想要通过扩展RecyclerView或其LayoutManager来更精细地控制Prefetch的行为,包括何时开始预取数据。

需要注意的是,RecyclerView的Prefetch机制主要是为了优化滚动性能而设计的,不是为了给开发者提供直接的控制接口。如果默认Prefetch行为不满足需求,需要考虑优化数据加载逻辑(比如使用更高效的异步加载库,如Paging 3),或者通过自定义扩展RecyclerView组件来实现更复杂的预取逻辑。

责任编辑:赵宁宁 来源: 沐雨花飞蝶
相关推荐

2009-06-24 15:16:19

AJAX客户端

2012-11-27 11:14:11

Firefox

2014-03-31 16:15:47

移动应用优化

2011-08-29 17:16:29

Ubuntu

2019-12-09 09:34:47

缓存响应数据

2022-06-13 09:45:51

Hook技术移动应用响应速度

2009-06-16 15:04:14

JSP页面响应速度

2021-03-07 09:10:50

Windows10操作系统微软

2018-07-05 16:22:47

2024-07-23 08:08:18

2016-02-15 10:52:46

视频会议华为

2017-03-13 10:11:28

AndroidRecyclerVie功能介绍

2021-01-08 09:40:40

优化VUE性能

2019-06-26 09:32:28

华为禁令开发

2024-02-01 09:51:17

数据库缓存

2017-02-21 12:20:20

Android事件分发机制实例解析

2011-09-06 15:53:41

Qt平台GUI

2023-07-21 08:42:23

App汽车之家

2021-03-16 21:45:59

Python Resize机制

2011-06-09 16:14:14

点赞
收藏

51CTO技术栈公众号