ListView优化

在Android程序中,列表是一个很重要的部分,在看法中我们需要关心ListView的各方面的效率。最近也一直在思考这些问题,刚好看到Facebook的ListView优化,所以借鉴Facebook的方案将自己项目中的部分模块优化。请参考Fast Rendering News Feed on Androidfacebook新闻页ListView优化

1.基本知识

1.1 首先使用ViewHolder和ItemViewType优化ListView。
1.2 合理的任务调度。
1.3 降低ItemView的复杂度(嵌套层次)

2.Facebook优化方案。

使用基本的优化方案可以保证简单的ListView保持流畅,但是对于ListView中Item类型过多,基本的优化方案会导致ViewItem复用率低。

2.1 将每个Item拆分为多个Item。
Item设计图
将Item上部分和下部分相同的模块提取出来作为一个Item,即每一个Item分成多个Item,使得多个Item可以达到复用,提高复用率。

数据处理也需要将每个Item拆分为多个Item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
   public static FeedWrap  feed2ItemTop(Feed feed) {
if (feed == null) return null;
return new FeedWrap(FeedItem.HEAD, feed.getFeedId(), new FeedHead(feed.getSenderName(), feed.getFeedTime(), feed.getSenderImg()));
}

public static FeedWrap feed2ItemButtom(Feed feed) {
if (feed == null) return null;
return new FeedWrap(FeedItem.BUTTOM, feed.getFeedId(), new FeedButtom(feed.getIsPraiseByMe(), feed.getRelpyNum(), feed.getForwardNum(), feed.getPraiseNum()));
}

public static FeedWrap feed2ConTent(Feed feed) {

}

public static List<FeedWrap> feed2ConTent(List<Feed> feeds) {
if (feeds == null) return null;
ArrayList<FeedWrap> feedWraps = new ArrayList<>();
for (Feed feed : feeds) {
feedWraps.add(feed2ItemTop(feed));
feedWraps.add(feed2ConTent(feed));
feedWraps.add(feed2ItemButtom(feed));
}
return feedWraps;
}

2.2 预处理数据
数据格式化,创建spannable。

比如Json数据的反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static FeedWrap feed2Wrap(Feed feed) {
if (feed == null) return null;
FeedContent feedContent = null;
switch (feed.getFeedType()) {
case TEXT:
feedContent = GsonUtil.getGson().fromJson(feed.getFeedContent(), FeedContentText.class);
return new FeedWrap(FeedItem.Content.TEXT, feed.getFeedId(), feedContent);

case IMAGE:
feedContent = GsonUtil.getGson().fromJson(feed.getFeedContent(), FeedContentImage.class);
return new FeedWrap(FeedItem.Content.IMAGE, feed.getFeedId(), feedContent);

case VIDEO:
feedContent = GsonUtil.getGson().fromJson(feed.getFeedContent(), FeedContentVideo.class);
return new FeedWrap(FeedItem.Content.VIDEO, feed.getFeedId(), feedContent);

case REPOST:
feedContent = GsonUtil.getGson().fromJson(feed.getFeedContent(), FeedContentRepost.class);
return new FeedWrap(FeedItem.Content.REPOST, feed.getFeedId(), feedContent);

case NEWS:
feedContent = GsonUtil.getGson().fromJson(feed.getFeedContent(), FeedContentNews.class);
return new FeedWrap(FeedItem.Content.NEWS, feed.getFeedId(), feedContent);

default:
return new FeedWrap(FeedItem.Content.UNKNOW, feed.getFeedId(), null);
}
}

2.3 显示数据
FaceBook的方案是将没个Item做成一个自定义的View,方便管理,在demo中为了节省时间省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private View getViewByViewType(int itemViewType, ViewGroup viewGroup) {
View view = null;
Holder holder = null;
switch (itemViewType) {
case FeedItem.HEAD:
view = mInflater.inflate(R.layout.feed_item_head, viewGroup, false);
holder = new HeadViewHolder(view);
break;

case FeedItem.BUTTOM:
view = mInflater.inflate(R.layout.feed_item_bottom, viewGroup, false);
holder = new ButtomViewHolder(view);
break;

case FeedItem.Content.TEXT:
view = mInflater.inflate(R.layout.feed_item_text, viewGroup, false);
holder = new TextViewHolder(view);
break;

case FeedItem.Content.VIDEO:
view = mInflater.inflate(R.layout.feed_item_video, viewGroup, false);
holder = new VideoViewHolder(view);
break;

default:
view = mInflater.inflate(R.layout.feed_item_unknow, viewGroup, false);
break;
}
view.setTag(holder);
return view;
}

2.4 操作数据
之前数据分为多个Item和显示数据较为简单, 但是在操作数据中就会遇到问题,我分为了两种方案。第一种方案在删除数据时非常安全,但是每次删除会遍历集合,效率方面存在问题;第二种方案根据自己的实际情况,直接通过position删除数据,但这种删除数据给人一种存在隐患。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void remove(FeedWrap feedWrap) {
if (feedWrap!= null && mFeedWraps != null) {
Iterator<FeedWrap> feedWrapIterator = mFeedWraps.iterator();
while (feedWrapIterator.hasNext()) {
FeedWrap tempFeedWrap = feedWrapIterator.next();
if (tempFeedWrap.getFeedId() == feedWrap.getFeedId()) {
feedWrapIterator.remove();
}
}
}
}

/**
* 特殊情况, 根据删除按钮的item position 来确定其他同一条动态的其他item
* 这种效率高,但感觉有危险,自己根据自己的情况而定
* @param position
*/
public boolean remove(FeedWrap feedWrap, int position) {
if (checkRemoveData(feedWrap, position)) {
mFeedWraps.remove(position);
mFeedWraps.remove(position);
mFeedWraps.remove(position);
return true;
}
return false;
}

在Item点击的时候,默认ListView的Item有selector效果,但是分为多个Item以后,selector效果会导致Item分块, 看起来不是一个整体, 可以在ListView中设置属性selector为透明。

1
android:listSelector="#00000000"

3. 对Facebook方案疑惑的地方。

因为对Facebook方案存在疑惑,所以我没有使用Binder和PartDefinition。
3.1 It is called before the first time a binder is bound, and intelligently scheduled when there’s free CPU time on the UI thread.This makes it ideal for allocating click listeners, formatting strings, building spannables and so forth。
在Binder的prepare中进行click事件绑定,而prepare是在cpu闲时主线程中调用的(那就肯定不是getView中调用了),那就没有View, 我也不知道他是怎么绑定click事件的。所以我感觉prepare只是做一些数据的预处理操作。click应该在bind中设置。

Demo源码