Listview加载的性能优化是如何实现的

时间:2022-04-03 09:28:30

在android开发中listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以*的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。

listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次:

0、最原始的加载

1、利用convertview

2、利用viewholder

3、实现局部刷新

〇、最原始的加载

这里是不经任何优化的adapter,为了看起来方便,把listview的数据直接在构造函数里传给adapter了,代码如下:

?
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
private class adapteroptml extends baseadapter {
private layoutinflater mlayoutinflater;
private arraylist<integer> mlistdata;
public adapteroptml(context context, arraylist<integer> data) {
mlayoutinflater = layoutinflater.from(context);
mlistdata = data;
}
@override
public int getcount() {
return mlistdata == null ? : mlistdata.size();
}
@override
public object getitem(int position) {
return mlistdata == null ? : mlistdata.get(position);
}
@override
public long getitemid(int position) {
return position;
}
@override
public view getview(int position, view convertview, viewgroup parent) {
view viewroot = mlayoutinflater.inflate(r.layout.listitem, parent, false);
if (viewroot != null) {
textview txt = (textview)viewroot.findviewbyid(r.id.listitem_txt);
txt.settext(getitem(position) + "");
}
return viewroot;
}
}

一、利用convertview

上述代码的第27行在eclipse中已经提示警告:

unconditional layout inflation from view adapter: should use view holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling

这个意思就是说,被移出可视区域的view是可以回收复用的,它作为getview的第二个参数已经传进来了,所以没必要每次都从xml里inflate。

经过优化后的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
@override
public view getview(int position, view convertview, viewgroup parent) {
if (convertview == null) {
convertview = mlayoutinflater.inflate(r.layout.listitem, parent, false);
}
if (convertview != null) {
textview txt = (textview)convertview.findviewbyid(r.id.listitem_txt);
txt.setvisibility(view.visible);
txt.settext(getitem(position) + "");
}
return convertview;
}

上述代码加了判断,如果传入的convertview不为null,则直接复用,否则才会从xml里inflate。

按照上述代码,如果手机一屏最多同时显示5个listitem,则最多需要从xml里inflate 5 次,比adapteroptml0中每个listitem都需要inflate显然效率高多了。

上述的用法虽然提高了效率,但带来了一个陷阱,如果复用convertview,则需要重置该view所有可能被修改过的属性。

举个例子:

如果第一个view中的textview在getview中被设置成invisible了,而现在第一个view在滚动过程中出可视区域,并假设它作为参数传入第十个view的getview而被复用

那么,在第十个view的getview里面不仅要settext,还要重新setvisibility,因为这个被复用的view当前处于invisible状态!

二、利用viewholder

从adapteroptml0第27行的警告中,我们还可以看到编译器推荐了一种模型叫viewholder,这是个什么东西呢,先看代码:

private class adapteroptml extends baseadapter {

?
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
32
33
34
35
36
37
38
39
private layoutinflater mlayoutinflater;
private arraylist<integer> mlistdata;
public adapteroptml(context context, arraylist<integer> data) {
mlayoutinflater = layoutinflater.from(context);
mlistdata = data;
}
private class viewholder {
public viewholder(view viewroot) {
txt = (textview)viewroot.findviewbyid(r.id.listitem_txt);
}
public textview txt;
}
@override
public int getcount() {
return mlistdata == null ? : mlistdata.size();
}
@override
public object getitem(int position) {
return mlistdata == null ? : mlistdata.get(position);
}
@override
public long getitemid(int position) {
return position;
}
@override
public view getview(int position, view convertview, viewgroup parent) {
if (convertview == null) {
convertview = mlayoutinflater.inflate(r.layout.listitem, parent, false);
viewholder holder = new viewholder(convertview);
convertview.settag(holder);
}
if (convertview != null && convertview.gettag() instanceof viewholder) {
viewholder holder = (viewholder)convertview.gettag();
holder.txt.setvisibility(view.visible);
holder.txt.settext(getitem(position) + "");
}
return convertview;
}
}

从代码中可以看到,这一步做的优化是用一个类viewholder来保存listitem里面所有找到的子控件,这样就不用每次都通过耗时的findviewbyid操作了。

这一步的优化,在listitem布局越复杂的时候效果越为明显。

三、实现局部刷新

ok,到目前为止,listview普遍需要的优化已经做的差不多了,那就该考虑实际使用场景中的优化需求了。

实际使用listview过程中,通常会在后台更新listview的数据,然后调用adatper的notifydatasetchanged方法来更新listview的ui。

那么问题来了,一般情况下,一次只会更新listview的一条/几条数据,而调用notifydatasetchanged方法则会把所有可视范围内的listitem都刷新一遍,这是不科学的!

所以,进一步优化的空间在于,局部刷新listview,话不多说见代码:

?
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
private class adapteroptml3 extends baseadapter {
private layoutinflater mlayoutinflater;
private listview mlistview;
private arraylist<integer> mlistdata;
public adapteroptml3(context context, listview listview, arraylist<integer> data) {
mlayoutinflater = layoutinflater.from(context);
mlistview = listview;
mlistdata = data;
}
private class viewholder {
public viewholder(view viewroot) {
txt = (textview)viewroot.findviewbyid(r.id.listitem_txt);
}
public textview txt;
}
@override
public int getcount() {
return mlistdata == null ? 0 : mlistdata.size();
}
@override
public object getitem(int position) {
return mlistdata == null ? 0 : mlistdata.get(position);
}
@override
public long getitemid(int position) {
return position;
}
@override
public view getview(int position, view convertview, viewgroup parent) {
if (convertview == null) {
convertview = mlayoutinflater.inflate(r.layout.listitem, parent, false);
viewholder holder = new viewholder(convertview);
convertview.settag(holder);
}
if (convertview != null && convertview.gettag() instanceof viewholder) {
updateview((viewholder)convertview.gettag(), (integer)getitem(position));
}
return convertview;
}
public void updateview(viewholder holder, integer data) {
if (holder != null && data != null) {
holder.txt.setvisibility(view.visible);
holder.txt.settext(data + "");
}
}
public void notifydatasetchanged(int position) {
final int firstvisiableposition = mlistview.getfirstvisibleposition();
final int lastvisiableposition = mlistview.getlastvisibleposition();
final int relativeposition = position - firstvisiableposition;
if (position >= firstvisiableposition && position <= lastvisiableposition) {
updateview((viewholder)mlistview.getchildat(relativeposition).gettag(), (integer)getitem(position));
} else {
//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getview自动刷新
}
}
}

修改后的adapter新增了一个方法 public void notifydatasetchanged(int position) 可以根据position只更新指定的listitem。

局部刷新番外篇

在局部刷新数据的接口中,实际上还可以再干点事情:listview正在滚动的时候不去刷新。

具体的思路是,如果当前正在滚动,则记住一个pending任务,等listview停止滚动的时候再去刷,这样不会造成滚动的时候刷新错乱。代码如下:

?
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
private class adapteroptmlplus extends baseadapter implements onscrolllistener{
private layoutinflater mlayoutinflater;
private listview mlistview;
private arraylist<integer> mlistdata;
private int mscrollstate = scroll_state_idle;
private list<runnable> mpendingnotify = new arraylist<runnable>();
public adapteroptmlplus(context context, listview listview, arraylist<integer> data) {
mlayoutinflater = layoutinflater.from(context);
mlistview = listview;
mlistdata = data;
mlistview.setonscrolllistener(this);
}
private class viewholder {
public viewholder(view viewroot) {
txt = (textview)viewroot.findviewbyid(r.id.listitem_txt);
}
public textview txt;
}
@override
public int getcount() {
return mlistdata == null ? : mlistdata.size();
}
@override
public object getitem(int position) {
return mlistdata == null ? : mlistdata.get(position);
}
@override
public long getitemid(int position) {
return position;
}
@override
public view getview(int position, view convertview, viewgroup parent) {
if (convertview == null) {
convertview = mlayoutinflater.inflate(r.layout.listitem, parent, false);
viewholder holder = new viewholder(convertview);
convertview.settag(holder);
}
if (convertview != null && convertview.gettag() instanceof viewholder) {
updateview((viewholder)convertview.gettag(), (integer)getitem(position));
}
return convertview;
}
public void updateview(viewholder holder, integer data) {
if (holder != null && data != null) {
holder.txt.setvisibility(view.visible);
holder.txt.settext(data + "");
}
}
public void notifydatasetchanged(final int position) {
final runnable runnable = new runnable() {
@override
public void run() {
final int firstvisiableposition = mlistview.getfirstvisibleposition();
final int lastvisiableposition = mlistview.getlastvisibleposition();
final int relativeposition = position - firstvisiableposition;
if (position >= firstvisiableposition && position <= lastvisiableposition) {
if (mscrollstate == scroll_state_idle) {
//当前不在滚动,立刻刷新
log.d("snser", "notifydatasetchanged position=" + position + " update now");
updateview((viewholder)mlistview.getchildat(relativeposition).gettag(), (integer)getitem(position));
} else {
synchronized (mpendingnotify) {
//当前正在滚动,等滚动停止再刷新
log.d("snser", "notifydatasetchanged position=" + position + " update pending");
mpendingnotify.add(this);
}
}
} else {
//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getview自动刷新
log.d("snser", "notifydatasetchanged position=" + position + " update skip");
}
}
};
runnable.run();
}
@override
public void onscrollstatechanged(abslistview view, int scrollstate) {
mscrollstate = scrollstate;
if (mscrollstate == scroll_state_idle) {
//滚动已停止,把需要刷新的listitem都刷新一下
synchronized (mpendingnotify) {
final iterator<runnable> iter = mpendingnotify.iterator();
while (iter.hasnext()) {
iter.next().run();
iter.remove();
}
}
}
}
@override
public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) {
}
}

以上所述是针对listview加载的性能优化是如何实现的全部叙述,希望对大家有所帮助。