没想到现在学生找工作可真难,还要知道android一些优化的东西,下载是我在学习listview优化时找到的一些比较好的文章
分别转自:
http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html
http://www.cnblogs.com/felix-hua/archive/2012/01/06/2314436.html
http://blog.csdn.net/jacman/article/details/7087995
一、先了解listview的加载原理
工作原理:
- ListView 针对List中每个item,要求 adapter “给我一个视图” (getView)。
- 一个新的视图被返回并显示
如果我们有上亿个项目要显示怎么办?为每个项目创建一个新视图?NO!这不可能!
实际上Android为你缓存了视图。
Android中有个叫做Recycler的构件,下图是他的工作原理:
- 如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中。
- ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的。
- 当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图。
请看下面的示例代码,这里在getView中使用了System.out进行输出
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
|
public
class
MultipleItemsList
extends
ListActivity {
private
MyCustomAdapter mAdapter;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
mAdapter =
new
MyCustomAdapter();
for
(
int
i =
0
; i <
50
; i++) {
mAdapter.addItem(
"item "
+ i);
}
setListAdapter(mAdapter);
}
private
class
MyCustomAdapter
extends
BaseAdapter {
private
ArrayList mData =
new
ArrayList();
private
LayoutInflater mInflater;
public
MyCustomAdapter() {
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public
void
addItem(
final
String item) {
mData.add(item);
notifyDataSetChanged();
}
@Override
public
int
getCount() {
return
mData.size();
}
@Override
public
String getItem(
int
position) {
return
mData.get(position);
}
@Override
public
long
getItemId(
int
position) {
return
position;
}
@Override
public
View getView(
int
position, View convertView, ViewGroup parent) {
System.out.println(
"getView "
+ position +
" "
+ convertView);
ViewHolder holder =
null
;
if
(convertView ==
null
) {
convertView = mInflater.inflate(R.layout.item1,
null
);
holder =
new
ViewHolder();
holder.textView = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(mData.get(position));
return
convertView;
}
}
public
static
class
ViewHolder {
public
TextView textView;
}
}
|
执行程序,然后在Logcat中查看日志
getView 被调用 9 次 ,convertView 对于所有的可见项目是空值(如下)
02
-
05
13
:
47
:
32.559
: INFO/System.out(
947
): getView
0
null
02
-
05
13
:
47
:
32.570
: INFO/System.out(
947
): getView
1
null
02
-
05
13
:
47
:
32.589
: INFO/System.out(
947
): getView
2
null
02
-
05
13
:
47
:
32.599
: INFO/System.out(
947
): getView
3
null
02
-
05
13
:
47
:
32.619
: INFO/System.out(
947
): getView
4
null
02
-
05
13
:
47
:
32.629
: INFO/System.out(
947
): getView
5
null
02
-
05
13
:
47
:
32.708
: INFO/System.out(
947
): getView
6
null
02
-
05
13
:
47
:
32.719
: INFO/System.out(
947
): getView
7
null
02
-
05
13
:
47
:
32.729
: INFO/System.out(
947
): getView
8
null
|
然后稍微向下滚动List,直到item10出现:
convertView仍然是空值,因为recycler中没有视图(item1的边缘仍然可见,在顶端)
02
-
05
13
:
48
:
25.169
: INFO/System.out(
947
): getView
9
null
|
再滚动List
convertView不是空值了!item1离开屏幕到Recycler中去了,然后item11被创建
02
-
05
13
:
48
:
42.879
: INFO/System.out(
947
): getView
10
android.widget.LinearLayout
@437430f8
|
再滚动:
02
-
05
14
:
01
:
31.069
: INFO/System.out(
947
): getView
11
android.widget.LinearLayout
@437447d0
02
-
05
14
:
01
:
31.142
: INFO/System.out(
947
): getView
12
android.widget.LinearLayout
@43744ff8
02
-
05
14
:
01
:
31.279
: INFO/System.out(
947
): getView
13
android.widget.LinearLayout
@43743fa8
02
-
05
14
:
01
:
31.350
: INFO/System.out(
947
): getView
14
android.widget.LinearLayout
@43745820
02
-
05
14
:
01
:
31.429
: INFO/System.out(
947
): getView
15
android.widget.LinearLayout
@43746048
02
-
05
14
:
01
:
31.550
: INFO/System.out(
947
): getView
16
android.widget.LinearLayout
@43746870
02
-
05
14
:
01
:
31.669
: INFO/System.out(
947
): getView
17
android.widget.LinearLayout
@43747098
02
-
05
14
:
01
:
31.839
: INFO/System.out(
947
): getView
18
android.widget.LinearLayout
@437478c0
02
-
05
14
:
03
:
30.900
: INFO/System.out(
947
): getView
19
android.widget.LinearLayout
@43748df0
02
-
05
14
:
03
:
32.069
: INFO/System.out(
947
): getView
20
android.widget.LinearLayout
@437430f8
|
convertView 如我们所期待的非空了,在item11离开屏幕之后,它的视图(@437430f8)作为convertView容纳item21了
不同的项目布局(item layout)
我们再举一个稍微复杂的例子,在上例的list中加入一些分隔线
你需要做这些:
- 重(@Override)写 getViewTypeCount() – 返回你有多少个不同的布局
- 重写 getItemViewType(int) – 由position返回view type id
- 根据view item的类型,在getView中创建正确的convertView
以下是代码:
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
94
95
96
97
98
99
100
|
public
class
MultipleItemsList
extends
ListActivity {
private
MyCustomAdapter mAdapter;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
mAdapter =
new
MyCustomAdapter();
for
(
int
i =
1
; i <
50
; i++) {
mAdapter.addItem(
"item "
+ i);
if
(i %
4
==
0
) {
mAdapter.addSeparatorItem(
"separator "
+ i);
}
}
setListAdapter(mAdapter);
}
private
class
MyCustomAdapter
extends
BaseAdapter {
private
static
final
int
TYPE_ITEM =
0
;
private
static
final
int
TYPE_SEPARATOR =
1
;
private
static
final
int
TYPE_MAX_COUNT = TYPE_SEPARATOR +
1
;
private
ArrayList mData =
new
ArrayList();
private
LayoutInflater mInflater;
private
TreeSet mSeparatorsSet =
new
TreeSet();
public
MyCustomAdapter() {
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public
void
addItem(
final
String item) {
mData.add(item);
notifyDataSetChanged();
}
public
void
addSeparatorItem(
final
String item) {
mData.add(item);
// save separator position
mSeparatorsSet.add(mData.size() -
1
);
notifyDataSetChanged();
}
@Override
public
int
getItemViewType(
int
position) {
return
mSeparatorsSet.contains(position) ? TYPE_SEPARATOR : TYPE_ITEM;
}
@Override
public
int
getViewTypeCount() {
return
TYPE_MAX_COUNT;
}
@Override
public
int
getCount() {
return
mData.size();
}
@Override
public
String getItem(
int
position) {
return
mData.get(position);
}
@Override
public
long
getItemId(
int
position) {
return
position;
}
@Override
public
View getView(
int
position, View convertView, ViewGroup parent) {
ViewHolder holder =
null
;
int
type = getItemViewType(position);
System.out.println(
"getView "
+ position +
" "
+ convertView +
" type = "
+ type);
if
(convertView ==
null
) {
holder =
new
ViewHolder();
switch
(type) {
case
TYPE_ITEM:
convertView = mInflater.inflate(R.layout.item1,
null
);
holder.textView = (TextView)convertView.findViewById(R.id.text);
break
;
case
TYPE_SEPARATOR:
convertView = mInflater.inflate(R.layout.item2,
null
);
holder.textView = (TextView)convertView.findViewById(R.id.textSeparator);
break
;
}
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(mData.get(position));
return
convertView;
}
}
public
static
class
ViewHolder {
public
TextView textView;
}
}
|
运行程序,你会看到每4个item一个分割线
看看日志,无异常,所有的convertView都是空的
02
-
05
15
:
19
:
03.080
: INFO/System.out(
1035
): getView
0
null
type =
0
02
-
05
15
:
19
:
03.112
: INFO/System.out(
1035
): getView
1
null
type =
0
02
-
05
15
:
19
:
03.130
: INFO/System.out(
1035
): getView
2
null
type =
0
02
-
05
15
:
19
:
03.141
: INFO/System.out(
1035
): getView
3
null
type =
0
02
-
05
15
:
19
:
03.160
: INFO/System.out(
1035
): getView
4
null
type =
1
02
-
05
15
:
19
:
03.170
: INFO/System.out(
1035
): getView
5
null
type =
0
02
-
05
15
:
19
:
03.180
: INFO/System.out(
1035
): getView
6
null
type =
0
02
-
05
15
:
19
:
03.190
: INFO/System.out(
1035
): getView
7
null
type =
0
02
-
05
15
:
19
:
03.210
: INFO/System.out(
1035
): getView
8
null
type =
0
02
-
05
15
:
19
:
03.210
: INFO/System.out(
1035
): getView
9
null
type =
1
|
滚动list:
02
-
05
15
:
19
:
54.160
: INFO/System.out(
1035
): getView
10
null
type =
0
02
-
05
15
:
19
:
57.440
: INFO/System.out(
1035
): getView
11
android.widget.LinearLayout
@43744528
type =
0
02
-
05
15
:
20
:
01.310
: INFO/System.out(
1035
): getView
12
android.widget.LinearLayout
@43744eb0
type =
0
02
-
05
15
:
20
:
01.880
: INFO/System.out(
1035
): getView
13
android.widget.LinearLayout
@437456d8
type =
0
02
-
05
15
:
20
:
02.869
: INFO/System.out(
1035
): getView
14
null
type =
1
02
-
05
15
:
20
:
06.489
: INFO/System.out(
1035
): getView
15
android.widget.LinearLayout
@43745f00
type =
0
02
-
05
15
:
20
:
07.749
: INFO/System.out(
1035
): getView
16
android.widget.LinearLayout
@43747170
type =
0
02
-
05
15
:
20
:
10.250
: INFO/System.out(
1035
): getView
17
android.widget.LinearLayout
@43747998
type =
0
02
-
05
15
:
20
:
11.661
: INFO/System.out(
1035
): getView
18
android.widget.LinearLayout
@437481c0
type =
0
02
-
05
15
:
20
:
13.180
: INFO/System.out(
1035
): getView
19
android.widget.LinearLayout
@437468a0
type =
1
02
-
05
15
:
20
:
16.900
: INFO/System.out(
1035
): getView
20
android.widget.LinearLayout
@437489e8
type =
0
02
-
05
15
:
20
:
25.690
: INFO/System.out(
1035
): getView
21
android.widget.LinearLayout
@4374a8d8
type =
0
|
convertView对于分割线是空的,直到第一个分割线可见,当其离开屏幕,视图去到Recycler并且convertView开始起作用。
再看看关于viewholder优化的东西:
一、Adapter
Adapter是ListView和数据源之间的中间人。
当每条数据进入可见区时,adapter会调用他的getView()方法,并返回代表具体数据的视图。滚动的时候频繁调用,支持成千上万的数据。
①、最简单的方法,最慢最不实用
public void getView(int position , View convertView , ViewGroup parent){
View item = mInflater.inflate(R.layout.list_view, null);
(TextView)item.findViewById(R.id.text).setText(DATA[position]);
(ImageView)item.findViewById(R.id.img).setImageBitmap(icon);
return item;
}
②、利用convertView回收视图,效率提高200%
public void getView(int position , View convertView , ViewGroup parent){
if(convertView == null){
convertView = mInflater.inflate(R.layout.list_view, null);
}
(TextView)convertView.findViewById(R.id.text).setText(DATA[position]);
(ImageView)convertView.findViewById(R.id.img).setImageBitmap(icon);
return convertView;
}
③、使用ViewHolder模式,效率再提高50%
static class ViewHolder{
private TextView tv;
private ImageView iv;
}
public void getView(int position , View convertView , ViewGroup parent){
ViewHolder holder;
if(convertView == null){
convertView = mInflater.inflate(R.layout.list_view, null);
holder = new ViewHolder();
holder.tv = (TextView)convertView.findViewById(R.id.text);
holder.iv = (ImageView)convertView.findViewById(R.id.img);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.tv.setText(DATA[position]);
holder.iv.setImageBitmap(icon);
return convertView;
}
更新的效率比较:
第三篇:
在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。
ListView加载数据都是在public View getView(int position, View convertView, ViewGroup parent) {}方法中进行的(要自定义listview都需要重写listadapter:如BaseAdapter,SimpleAdapter,CursorAdapter的等的getvView方法),优化listview的加载速度就要让convertView匹配列表类型,并最大程度上的重新使用convertView。
getview的加载方法一般有以下三种种方式:
最慢的加载方式是每一次都重新定义一个View载入布局,再加载数据
public View getView(int position, View convertView, ViewGroup parent) {
View item = mInflater.inflate(R.layout.list_item_icon_text, null);
((TextView) item.findViewById(R.id.text)).setText(DATA[position]);
((ImageView) item.findViewById(R.id.icon)).setImageBitmap(
(position & 1) == 1 ? mIcon1 : mIcon2);
return item;
}
正确的加载方式是当convertView不为空的时候直接重新使用convertView从而减少了很多不必要的View的创建,然后加载数据
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item, parent, false);
}
((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]);
((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap(
(position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
最快的方式是定义一个ViewHolder,将convetView的tag设置为ViewHolder,不为空时重新使用即可
static class ViewHolder {
TextView text;
ImageView icon;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,
parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
三种方式加载效率对比如下图所示:
说明:上述三个例子代码摘自google 2010 I/O大会
当处理一些耗时的资源加载的时候需要做到以下几点,以使你的加载更快更平滑:
1. 适配器在界面主线程中进行修改
2. 可以在任何地方获取数据但应该在另外一个地方请求数据
3. 在主界面的线程中提交适配器的变化并调用notifyDataSetChanged()方法