react native现在是一个热火朝天的框架。一个无原生开发经验的开发三两天即可搞出一个android/iso app,给做js的前端开发人员带来了狂欢。各种"ReactNative项目实战"的文章也遍布在互联网,比如qq 空间的<<ReactNative For Android 项目实战总结>>。然而,各种项目实战总结的背后却对react-native背后的问题一笔带过甚至只字不提。比如最常见的内存泄漏问题、Navigator巨慢内存泄漏、ListView巨吃内存。。。
言归正传说rn android listview问题。在react-native的android源码中您可以见到RecyclerViewBackedScrollView,react-native编写js时ListView组建中指定属性renderScrollComponent为RecyclerViewBackedScrollView它就使用android的RecyclerView,不过别高兴的太早了,它跟我们想象中的recycle八竿子打不着,您有多少DataSource它就会在内存里帮您钉住多少行的视图毫不吝啬。
知道了问题我们来找找解决方案(qq的开发员在祈祷facebook后续的版本解决)。国外的同行还是有不少人在提供解决方案的。比如react-native-sglistview、react-native-enhanced-listview,不过android的react-native框架 facebook偷懒没有实现OnChangeVisibleRows这个东西跑不动,即使可行这个东西也还是会存在内存泄漏的情况。最后找到了一个iOS的解决方案《Recycling Rows for High Performance React Native List Views》。他的主要思想是:
- react-native js,端创建足够的滑动行;
- TableViewChildren.js里维护一个binging数组,关联视图和对应数据的行号;
- 原生发送onChange消息告诉TableViewChildren视图对应数据的行号有更改;
- ReboundRenderer.js通过判断行号更改刷新视图。
这个方案非常巧妙,取巧地通过ReboundRenderer来重刷视图,原生主动更新js端视图。比起react-native-sglistview、enhanced-listview的思路更深入react-native原理,解决的思路值得借鉴。有了指导思想android照葫芦画瓢,一番码码android的RecyclerView可以复用item和刷新,不过它的样子却是错乱的。
跟踪日志更新的视图行号和数据行号均没有问题,百思不得其解重新回到读react-native代码。在翻阅RecyclerViewBackedScrollView源码时,两个函数的注释引起了我的注意。
即react-native自己接管了视图在屏幕中的measure和坐标计算。然而,我实现的RecyclerView item 的坐标在滑动、静止均是由RecyclerView包办的,那么很大可能是react-native在接管视图的坐标苗点计算有错误,所以导致RecyclerView错乱。
为了验证猜测需要理清react-native更新视图的流程(啃了一堆js类库非常头疼,没有好的js ide真不方便),下面是我理出的更新流程
简单归纳下:一个js render消息经过几层传递投递到ui消息队列,再由react-native原生代码绘制view。一个消息可以是createView、updateView等,而上文提到的坐标计算是updateLayout。updateLayout可以在createView、updateView、updateProperties等消息均可能触发。我们可以在UIViewOperationQueue或者NativeViewHierarchyManager中拦截updateLayout。
现在来考虑我的修正方案。在《Recycling Rows for High Performance React Native List Views》中通过ReboundRenderer组件来实现重新绘制row ui,看似非常巧妙实则我觉的作者是掉进React Native组件的生命周期的陷阱,而且他的做法除了需要一个额外的binging数组还会导致TableViewChildren重新render一遍,也就是踢掉旧row view产生了一组新的。其实ReboundRenderer组件是一个多余的累赘,完全可以在row view里判断rowID更改再重新render。总结下我的方案:
- 实现一个RealRecyclerView,自定义一个row view(RealRecyclerItemView),当RecyclerView滑动时发消息给RealRecyclerItemView,告诉它刷新视图;
- 在UIViewOperationQueue拦截updateLayout消息,发现updateLayout RealRecyclerItemView中断执行消息。
最后实现的方案见react-native-RealRecyclerView.