facebook新闻页ListView优化

以下转自“aaapei”的译文原文:http://blog.aaapei.com/article/2015/02/facebookxin-wen-ye-listviewyou-hua 

引言

原文链接:https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/ 透漏的信息量不大,且大多数项目并不会遇到facebook这种ListView的场景,不过可以拓展下思路:逻辑单元不一定是视图单元;移动端不要死搬MVC的架构,在市场上仍是中低端机型为主时,还是应该多考虑性能;附上rebbit的关于本文的讨论,有些干货 :)

基础知识

android系统每隔16.7ms发出一个渲染信号,通知ui线程进行界面的渲染。为了达到流畅的体验,应用程序需要在这个时间内完成应用逻辑, 使系统达到60fps。当一个Listview被添加到布局时,其关联的adapter的getView方法将会被回调。在16.7毫秒这样一个时间单元 内,可见listitem单元的getView方法将被按照顺序执行。在大多数情况下,由于其他绘图行为的存在,例如measure和 draw,getVIew实际分配到执行时间远低于16ms。一旦listview包含复杂控件时,在16毫秒内不能完成渲染,用户只能看到上一祯的结 果,这时就发生了掉帧。

Facebook新闻页介绍

Facebook的新闻页是一个复杂的listview控件,如何使它获得流畅的滚动体验一直困扰我们。 首先,新闻页的每一条新闻的可见区域非常大,包含一系列的文本以及照片;其次,新闻的展现类型也很多样,除了文本以及照片,新闻的附件还可包含链接、音 频、视频等。除此之外,新闻还可以被点赞、被转载,导致一个新闻会被其他新闻包含在内。当新闻被大量用户转载时,甚至会出现一条新闻占据两个屏幕的情况。 加上android用户的机型多为中低端设备,这使我们在16.7ms内完成新闻页的渲染变的非常困难。

新闻页最初架构

在2012年,我们将新闻页从web-view转化成本地控件,在最初的那个版本中,基于View-Model-Binder设计模型,我们为新闻 listitem创建了一个自定义StoryView类,这个类有一个bindModel方法,该方法用于和数据进行绑定。代码是这样的:

StoryView的包含的子控件都会有一个bindModel方法,例如HeadVIew通过该方法与其相关的数据进行绑定。

这种设计,代码非常直观清晰,但他的缺点也很明显:

  • listview复用机制不能有效的工作,Android's recycling mechanism does not work well in this case: Every item in the ListView was usually a StoryView, but once bound to a story, two StoryViews would be radically different and recycling one into the other wasn't effective.(这一段存疑,直接放原文)

  • 逻辑嵌套:采用bindModel绑定控件和数据,业务逻辑与视图逻辑耦合,导致逻辑类层次非常深;

  • 布局嵌套非常深:不但导致低效的视图渲染,例如新闻被不停的转载的极端场景下还会导致栈溢出;

  • bindModel方法逻辑过重:bindModel方法在当用户滚动列表时被ui线程回调,由于所有的数据解析都在这个方法内,导致该方法耗时

以上这些问题虽有他们单独的解决方法,例如我们可以自己设计一套回收机制解决storyView复用问题。但基于维护成本和开发时间考虑,我们决定进行一次重构。

重构方案

重构工作大约是一年之前开始的,为了解决前一个架构的问题,首先我们决定将一条新闻分隔成多个listview item。例如,新闻的headerview将是一个独立的listitem。这样,我们可以利用android回收机制,HeaderView新闻子控 件将被不同的新闻复用。另外,切分成小view也使得内存占用更小,在之前的架构中,Storyview部分的可见会导致这个Storyview被加载到 内存中,而现在,粒度更小,只有可见的子控件才会被加载。

另一个大的修改是,我们将视图逻辑和数据逻辑分离,StoryView被分离成两个类: 只负责展现的视图类,以及一个Binder类。视图类仅包含set方法(例如HeaderView包含了setTitle,setSubTitle。 setProfiePic等等)。Binder类包含了原来的bindMethod的逻辑,binder类包含三个方 法:prepare,bind,unbind。 bind方法调用view的set方法设置数据,unbind清理视图数据,prepare方法在cpu空闲期间做一些预初始化工作,例如进行click 事件绑定、数据格式化、创建spannable等等,它会在getView方法之前被调用

我们遇到的技术难点是Binder的设计,由于StoryView被拆分不同的子控件,一条新闻可能会包含多个不同的Binder。而在之前,我们只需要根据视图的树结构进行结构化赋值。因此,我们引进了PartDefinition类,PartDefinition负责维护一条新闻包含哪些子控件、包含Binder的类型以及为新闻创建Binder类,有两种类型的PartDefinition:单个PartDefinition以及PartDefinition集合。

一个新闻在重构之后的PartDefinition结构是这样的:

结论

  • 采取新的架构,内存错误减少了17%,总crash率减少了8%,彻底解决涨溢出问题

  • 渲染时间减少了10%,大新闻场景不再掉帧

  • 精简了原来的自定义回收机制,同时在重构过程中增加了单元测试


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ListView 是一种常用的 Android 控件,适用于展示大量数据的列表。当我们要实现一个新闻页面时,可以使用 ListView 来展示多条新闻的标题、日期等信息。 首先,我们需要准备一个包含新闻信息的数据集合。可以使用 ArrayList 或者其他集合类来保存每条新闻的标题、日期和其他相关信息。然后,我们可以创建一个适配器类,继承自 ArrayAdapter 或者 BaseAdapter,用于将数据和 ListView 进行关联。 在适配器中,我们可以重写 getView() 方法,通过 LayoutInflater 加载我们自定义的新闻列表项布局,并设置相应的数据。通过 convertView 参数可以实现列表项的复用,以提高性能。在 getView() 方法中,我们可以根据位置获取对应的新闻数据,并将其展示到布局中。 接下来,我们需要在布局文件中引入 ListView 控件,并设置对应的属性,如布局方向、分割线、滚动条等。在代码中,我们可以通过 findViewById() 方法获取到 ListView 控件的实例,并使用 setAdapter() 方法将适配器与 ListView 相关联。 最后,通过监听 ListView 的点击事件,我们可以跳转到新闻的详细页面,或者执行其他相关操作。可以通过 setOnItemClickListener() 方法设置点击事件的监听器,当用户点击某个列表项时,可以获取到对应的位置,然后通过该位置获取到对应的新闻数据,进行相关处理。 总之,使用 ListView 来做新闻页面可以方便地展示大量的新闻列表,并通过适配器和点击事件的处理,实现与用户的交互和跳转逻辑。这样的设计可以提高用户体验,同时也提供了一种便捷的方式来展示和管理新闻数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值