RecyclerViewBackedScrollView的真实面目

Intro

FB的官方ListView例子中,使用了一个RecyclerViewBackedScrollView作为ListView的内部scrollView。ListViewExample.js

1
2
3
4
5
6
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />}
renderSeparator={this._renderSeparator}
/>

从名字上看很不错,“可重用的”,应该能解决性能问题。但是实际情况呢?
先直接告诉你,实际不是你想象的。

iOS平台

首先看ios平台的实现:
RecyclerViewBackedScrollView.ios.js

1
2
3
'use strict';
module.exports = require('ScrollView');

是的,只有这两行,就是直接返回ScrollView,这应该是FB先自己挖了个坑,等自己或者社区来填了。

Android平台

再看Android的吧,定位到 RecyclerViewBackedScrollView.android.js

1
2
3
4
var NativeAndroidRecyclerView = requireNativeComponent(
'AndroidRecyclerViewBackedScrollView',
RecyclerViewBackedScrollView
);

ok可以看到native层使用的是AndroidRecyclerViewBackedScrollView。再继续去java层,找到 RecyclerViewBackedScrollViewManager
根据其定义:

1
2
3
4
public class RecyclerViewBackedScrollViewManager extends
ViewGroupManager<RecyclerViewBackedScrollView>
implements ReactScrollViewCommandHelper.ScrollCommandHandler<RecyclerViewBackedScrollView> {
}

可以看到它管理的对应View为RecyclerViewBackedScrollView, 找到这个view的源码 RecyclerViewBackedScrollView

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Wraps {@link RecyclerView} providing interface similar to `ScrollView.js` where each children
* will be rendered as a separate {@link RecyclerView} row.
*
* Currently supports only vertically positioned item. Views will not be automatically recycled but
* they will be detached from native view hierarchy when scrolled offscreen.
*
* It works by storing all child views in an array within adapter and binding appropriate views to
* rows when requested.
*/
@VisibleForTesting
public class RecyclerViewBackedScrollView extends RecyclerView {
}

首先,确实是继承自RecyclerView。但是从其注释可以得到以下信息:

  • 只支持竖向的滚动
  • 视图不会被自动回收
  • 当视图不再屏幕范围内时,会被从native视图层级中移除
  • 将子视图保存在一个内部数组

所以,使用RecyclerViewBackedScrollView视图还是不会被自动回收的,但是会减少层级中的视图数目。这样就导致:

  • 渲染速度还是很快的
  • 内存会随着子视图的增加而增加

从其代码也能看出来, RecyclerView的三个主要方法 onCreateViewHolder(创建视图) onBindViewHolder(填充数据) getItemCount(获取数量):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public int getItemCount() {
return mViews.size();
}
@Override
public ConcreteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ConcreteViewHolder(new RecyclableWrapperViewGroup(parent.getContext()));
}
@Override
public void onBindViewHolder(ConcreteViewHolder holder, int position) {
RecyclableWrapperViewGroup vg = (RecyclableWrapperViewGroup) holder.itemView;
View row = mViews.get(position);
if (row.getParent() != vg) {
vg.addView(row, 0);
}
}

onBindViewHolder中做了一件事情,传入itemPosition,从mViews中获得这个row的view对象,而mViews是什么呢?
就是上面所提到的内部保存子视图的数组,看代码:

1
private final List<View> mViews = new ArrayList<>();

1
2
3
4
5
6
7
8
public void addView(View child, int index) {
mViews.add(index, child);
updateTotalChildrenHeight(child.getMeasuredHeight());
child.addOnLayoutChangeListener(mChildLayoutChangeListener);
notifyItemInserted(index);
}

结论

iOS平台下, RecyclerViewBackedScrollView形同虚设。
Android平台下,会减少层级中的视图数目,但不会自动重用。