文档章节

react native Android 真正回收复用 RecyclerView/ListView

obaniu
 obaniu
发布于 2016/09/23 08:14
字数 1002
阅读 6835
收藏 12

    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-sglistviewreact-native-enhanced-listview,不过android的react-native框架 facebook偷懒没有实现OnChangeVisibleRows这个东西跑不动,即使可行这个东西也还是会存在内存泄漏的情况。最后找到了一个iOS的解决方案《Recycling Rows for High Performance React Native List Views》。他的主要思想是:

  1. react-native js,端创建足够的滑动行;
  2. TableViewChildren.js里维护一个binging数组,关联视图和对应数据的行号;
  3. 原生发送onChange消息告诉TableViewChildren视图对应数据的行号有更改;
  4. 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。总结下我的方案:

  1. 实现一个RealRecyclerView,自定义一个row view(RealRecyclerItemView),当RecyclerView滑动时发消息给RealRecyclerItemView,告诉它刷新视图;
  2. 在UIViewOperationQueue拦截updateLayout消息,发现updateLayout RealRecyclerItemView中断执行消息。

   最后实现的方案见react-native-RealRecyclerView.
        

© 著作权归作者所有

obaniu
粉丝 37
博文 83
码字总数 39241
作品 0
广州
高级程序员
私信 提问
加载中

评论(7)

开心小六子
开心小六子
你好,我测试了下内存,发现内存还是会一直上涨,是否真的回收了呢?
Rctte
Rctte
listView 并没有拦截到?用了onMeasure 视图就不渲染出来 是什么情况
Ranger_wang
Ranger_wang
rowHeight={100}
如果不定义高度,item的高度无法正常展示
这样定义每项高度都定死了,不能动态改变
oo2oo2oo
oo2oo2oo

引用来自“obaniu”的评论

引用来自“oo2oo2oo”的评论

引用来自“obaniu”的评论

引用来自“oo2oo2oo”的评论

这个的确是真正的回收ListView。其他的procrank内存都在一直涨。
但是有一部分代码看不懂,特注册账号请教一下:

JS:
var rCount = Math.round(height / this.props.rowHeight * 1.6);
if (rCount < 9) rCount = 9;
这个1.6和9是代表什么意思呢

JAVA:
void setRowHeight(int rowHeight) {
mRowHeight = (int) PixelUtil.toPixelFromDIP(rowHeight);
final int height = Math.max(DisplayMetricsHolder.getScreenDisplayMetrics().heightPixels, DisplayMetricsHolder.getScreenDisplayMetrics().widthPixels);
mHoldItems = Math.round(1.6f * height / this.mRowHeight);
if (mHoldItems < 6) mHoldItems = 6;
}
这里面也有1.6,而且为什么height要取宽高的Math.max呢

回复@oo2oo2oo : 由于RecyclerView需要一定数量的view来复用,所以我们需要通过设置item的高度来计算recyclerview回收复用的试图数量。1.6的意思是填充1.6个屏幕高度的视图。
OK,感谢!那这个可以自己随便定义一个合适的值了。

Java部分的呢,为什么要取长宽的最大值呢,应该直接拿高度来计算才对吧,另外mHoldItems最小设置为6,这个6也是个经验值吗?

回复@oo2oo2oo : 你说可以随便定义就随便定义咯,just try it.
OK,感谢!
obaniu
obaniu 博主

引用来自“oo2oo2oo”的评论

引用来自“obaniu”的评论

引用来自“oo2oo2oo”的评论

这个的确是真正的回收ListView。其他的procrank内存都在一直涨。
但是有一部分代码看不懂,特注册账号请教一下:

JS:
var rCount = Math.round(height / this.props.rowHeight * 1.6);
if (rCount < 9) rCount = 9;
这个1.6和9是代表什么意思呢

JAVA:
void setRowHeight(int rowHeight) {
mRowHeight = (int) PixelUtil.toPixelFromDIP(rowHeight);
final int height = Math.max(DisplayMetricsHolder.getScreenDisplayMetrics().heightPixels, DisplayMetricsHolder.getScreenDisplayMetrics().widthPixels);
mHoldItems = Math.round(1.6f * height / this.mRowHeight);
if (mHoldItems < 6) mHoldItems = 6;
}
这里面也有1.6,而且为什么height要取宽高的Math.max呢

回复@oo2oo2oo : 由于RecyclerView需要一定数量的view来复用,所以我们需要通过设置item的高度来计算recyclerview回收复用的试图数量。1.6的意思是填充1.6个屏幕高度的视图。
OK,感谢!那这个可以自己随便定义一个合适的值了。

Java部分的呢,为什么要取长宽的最大值呢,应该直接拿高度来计算才对吧,另外mHoldItems最小设置为6,这个6也是个经验值吗?

回复@oo2oo2oo : 你说可以随便定义就随便定义咯,just try it.
oo2oo2oo
oo2oo2oo

引用来自“obaniu”的评论

引用来自“oo2oo2oo”的评论

这个的确是真正的回收ListView。其他的procrank内存都在一直涨。
但是有一部分代码看不懂,特注册账号请教一下:

JS:
var rCount = Math.round(height / this.props.rowHeight * 1.6);
if (rCount < 9) rCount = 9;
这个1.6和9是代表什么意思呢

JAVA:
void setRowHeight(int rowHeight) {
mRowHeight = (int) PixelUtil.toPixelFromDIP(rowHeight);
final int height = Math.max(DisplayMetricsHolder.getScreenDisplayMetrics().heightPixels, DisplayMetricsHolder.getScreenDisplayMetrics().widthPixels);
mHoldItems = Math.round(1.6f * height / this.mRowHeight);
if (mHoldItems < 6) mHoldItems = 6;
}
这里面也有1.6,而且为什么height要取宽高的Math.max呢

回复@oo2oo2oo : 由于RecyclerView需要一定数量的view来复用,所以我们需要通过设置item的高度来计算recyclerview回收复用的试图数量。1.6的意思是填充1.6个屏幕高度的视图。
OK,感谢!那这个可以自己随便定义一个合适的值了。

Java部分的呢,为什么要取长宽的最大值呢,应该直接拿高度来计算才对吧,另外mHoldItems最小设置为6,这个6也是个经验值吗?
oo2oo2oo
oo2oo2oo
这个的确是真正的回收ListView。其他的procrank内存都在一直涨。
但是有一部分代码看不懂,特注册账号请教一下:

JS:
var rCount = Math.round(height / this.props.rowHeight * 1.6);
if (rCount < 9) rCount = 9;
这个1.6和9是代表什么意思呢

JAVA:
void setRowHeight(int rowHeight) {
mRowHeight = (int) PixelUtil.toPixelFromDIP(rowHeight);
final int height = Math.max(DisplayMetricsHolder.getScreenDisplayMetrics().heightPixels, DisplayMetricsHolder.getScreenDisplayMetrics().widthPixels);
mHoldItems = Math.round(1.6f * height / this.mRowHeight);
if (mHoldItems < 6) mHoldItems = 6;
}
这里面也有1.6,而且为什么height要取宽高的Math.max呢
ReactNative&weex&&DeviceOne对比

  React Native出来有一段时间了,国内的weex和deviceone是近期发布的,我可以说从2011年就开始关注快速开发的跨平台平台技术了,接触过phoneGap、数字天堂、appcan等早期的移动中间件技术...

jonh_felix
2016/07/25
727
0
踢开Android 开发中的绊脚石

在开发过程中,许多并算不上高级技能甚至连基础知识都不算的东西经常被忽略,但这些东西还经常是开发过程中的绊脚石,很长时间都解决不了,一旦找到了解决办法,就茅塞顿开了“原来是这样啊,...

Xiao_Mai
2017/10/24
0
0
Android RecyclerView

简介: RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,但是直接把viewholder的实现封装起来,用户只要实现自己的viewh...

姠眀兲汻蒝
2015/11/24
148
0
React Native列表视图FlatList使用优化实践指南

列表视图在app中是非常常见的,目前React Native比较严重的性能问题集中在FlatList大列表等地方,以下通过js层的优化,甚至原生层的优化封装,使性能媲美原生。 FlatList React Native 0.43...

qiushijie
04/03
0
0
Android最新组件RecyclerView,替代ListView

Android最新组件RecyclerView,替代ListView 时间 2014-10-22 20:08:48 CSDN博客 原文 http://blog.csdn.net/allen315410/article/details/40379159 主题 RecyclerView ListView 转载请注明出......

shzwork
09/27
17
0

没有更多内容

加载失败,请刷新页面

加载更多

ArrayList输出的几种方法

ArrayList输出的几种方法!!! import java.util.ArrayList;import java.util.Iterator;import java.util.List; public class ArrayList1 {    public static void main(Str......

柒礼拜
5分钟前
1
0
ZKEYS公有云管理系统账号注册流程

1.进入ZKEYS官网,单击首页右上角免费注册按钮 2.选择注册方式,有手机注册和有限注册两种 3.填写手机或邮箱,设置密码 4.注册完成,即可登录ZKEYS会员中心 单击右上角免费注册按钮 填写手机...

BirdCloud
7分钟前
1
0
好程序员web前端分享web前端入门知识

好程序员web前端分享web前端入门知识,给大家分享一些Web前端工程师要掌握的基础知识和技能,下面一起来看看。 1、网页的基本结构(HTML、CSS) HTML是一种标记语言,而不是编程语言,最基本是...

好程序员官网
10分钟前
2
0
使用pyinstaller打包qt程序提示 找不到.dll问题

使用pyinstaller打包qt程序,运行时提示 找不到.dll问题。 import osimport sysif hasattr(sys, 'frozen'): os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH'] 需要在......

開援带碼
11分钟前
3
0
mysql导入数据库

原链接:https://msd.misuland.com/pd/3223833238703185046 发现row size长度过长,导致出现错误: 解决方案: 查询系统参数: show variables like '%innodb_strict_mode%';show variab......

tobej
13分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部