标签:android开发 性能优化 listview imageview 线程
ListView是一种可以显示一系列项目并能进行滚动显示的View。在每行里,既可以是简单的文本,也可以是复杂的结构。一般情况下,你都需要保证ListView运行得很好(即:渲染更快,滚动流畅)。在接下来的内容里,我将就ListView的使用,向大家提供几种解决不同性能问题的解决方案。
如果你想使用ListView,你就不得不使用ListAdapter来显示内容。SDK中,已经有了几种简单实现的Adapter:
· ArrayAdapter<T> (显示数组对象,使用toString()来显示)
· SimpleAdapter (显示Maps列表)
· SimpleCursorAdapter(显示通过Cursor从DB中获取的信息)
这些实现对于显示简单的列表来说,非常棒!一旦你的列表比较复杂,你就不得不书写自己的ListAdapter实现。在多数情况下,直接从ArrayAdapter扩展就能很好地处理一组对象。此时,你需要处理的工作只是告诉系统如何处理列表中的对象。通过重写getView(int, View, ViewGroup)方法即可达到。
在这里,举一个你需要自定义ListAdapter的例子:显示一组图片,图片的旁边有文字挨着。
图片需要实时从internet上下载下来。让我们先创建一个Class来代表列表中的项目:
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
|
public
class ImageAndText { private
String imageUrl; private
String text; public
ImageAndText(String imageUrl, String text) { this .imageUrl
= imageUrl; this .text
= text; } public
String getImageUrl() { return
imageUrl; } public
String getText() { return
text; } } |
现在,我们要实现一个ListAdapter,来显示ImageAndText列表。
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
|
public
class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> { public
ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts) { super (activity,
0, imageAndTexts); } @Override public
View getView(int position, View convertView, ViewGroup parent) { Activity
activity = (Activity) getContext(); LayoutInflater
inflater = activity.getLayoutInflater(); //
Inflate the views from XML View
rowView = inflater.inflate(R.layout.image_and_text_row, null ); ImageAndText
imageAndText = getItem(position); //
Load the image and set it on the ImageView ImageView
imageView = (ImageView) rowView.findViewById(R.id.image); imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl())); //
Set the text on the TextView TextView
textView = (TextView) rowView.findViewById(R.id.text); textView.setText(imageAndText.getText()); return
rowView; } public
static Drawable loadImageFromUrl(String url) { InputStream
inputStream; try
{ inputStream
= new
URL(url).openStream(); }
catch
(IOException e) { throw
new
RuntimeException(e); } return
Drawable.createFromStream(inputStream, "src" ); } } |
这些View都是从“image_and_text_row.xml”XML文件中inflate的:
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
|
<?xml
version= "1.0"
encoding= "utf-8" ?> <LinearLayout
xmlns:android= "http://schemas.android.com/apk/res/android" android:orientation= "horizontal" android:layout_width= "fill_parent" android:layout_height= "wrap_content" > <ImageView
android:id= "@+id/image" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:src= "@drawable/default_image" /> <TextView
android:id= "@+id/text" android:layout_width= "wrap_content" android:layout_height= "wrap_content" /> </LinearLayout> |
这个ListAdapter实现正如你所期望的那样,能在ListView中加载ImageAndText。但是,它唯一可用的场合是那些拥有很少项目、无需滚动即可看到全部的列表。如果ImageAndText列表内容很多的时候,你会看到,滚动起来不是那么的平滑(事实上,远远不是)。
性能改善
上面例子最大的瓶颈是图片需要从internet上下载。因为我们的代码都在UI线程中执行,所以,每当一张图片从网络上下载时,UI就会变得停滞。如果你用3G网络代替WiFi的话,性能情况会变得更糟。
为了避免这种情况,我们想让图片的下载处于单独的线程里,这样就不会过多地占用UI线程。为了达到这一目的,我们可能需要使用为这种情况特意设计的AsyncTask。实际情况中,你将注意到AsyncTask被限制在10个以内。这个数量是在Android SDK中硬编码的,所以我们无法改变。这对我们来说是一个制限事项,因为常常有超过10个图片同时在下载。
AsyncImageLoader
一个变通的做法是手动的为每个图片创建一个线程。另外,我们还应该使用Handler来将下载的图片invoke到UI线程。我们这样做的原因是我们只能在UI线程中修改UI。我创建了一个AsyncImageLoader类,利用线程和Handler来负责图片的下载。此外,它还缓存了图片,防止单个图片被下载多次。
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
|
public
class AsyncImageLoader { private
HashMap<String, SoftReference<Drawable>> imageCache; public
AsyncImageLoader() { imageCache
= new
HashMap<String, SoftReference<Drawable>>(); } public
Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) { if
(imageCache.containsKey(imageUrl)) { SoftReference<Drawable>
softReference = imageCache.get(imageUrl); Drawable
drawable = softReference.get(); if
(drawable != null )
{ return
drawable; } } final
Handler handler = new
Handler() { @Override public
void handleMessage(Message message) { imageCallback.imageLoaded((Drawable)
message.obj, imageUrl); } }; new
Thread() { @Override public
void run() { Drawable
drawable = loadImageFromUrl(imageUrl); imageCache.put(imageUrl,
new
SoftReference<Drawable>(drawable)); Message
message = handler.obtainMessage(0, drawable); handler.sendMessage(message); } }.start(); return
null ; } public
static Drawable loadImageFromUrl(String url) { //
... } public
interface ImageCallback { public
void imageLoaded(Drawable imageDrawable, String imageUrl); } } |
注意:我使用了SoftReference来缓存图片,允许GC在需要的时候可以对缓存中的图片进行清理。它这样工作:
· 调用loadDrawable(ImageUrl, imageCallback),传入一个匿名实现的ImageCallback接口
· 如果图片在缓存中不存在的话,图片将从单一的线程中下载并在下载结束时通过ImageCallback回调
· 如果图片确实存在于缓存中,就会马上返回,不会回调ImageCallback
在你的程序中,只能存在一个AsyncImageLoader实例,否则,缓存不能正常工作。在ImageAndTextListAdapter类中,我们可以这样替换:
1
2
3
|
ImageView
imageView = (ImageView) rowView.findViewById(R.id.image); imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl())); |
换成
1
2
3
4
5
6
7
8
9
10
11
12
13
|
final
ImageView imageView = (ImageView) rowView.findViewById(R.id.image); Drawable
cachedImage = asyncImageLoader.loadDrawable(imageAndText.getImageUrl(), new
ImageCallback() { public
void imageLoaded(Drawable imageDrawable, String imageUrl) { imageView.setImageDrawable(imageDrawable); } }); imageView.setImageDrawable(cachedImage); |
使用这个方法,ListView执行得很好了,并且感觉滑动更平滑了,因为UI线程再也不会被图片加载所阻塞。
更好的性能改善
如果你尝试了上面的解决方案,你将注意到ListView也不是100%的平滑,仍然会有些东西阻滞着它的平滑性。这里,还有两个地方可以进行改善:
· findViewById()的昂贵调用
· 每次都inflate XML
因此,修改代码如下:
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
|
public
class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> { private
ListView listView; private
AsyncImageLoader asyncImageLoader; public
ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts, ListView listView) { super (activity,
0, imageAndTexts); this .listView
= listView; asyncImageLoader
= new
AsyncImageLoader(); } @Override public
View getView(int position, View convertView, ViewGroup parent) { Activity
activity = (Activity) getContext(); //
Inflate the views from XML View
rowView = convertView; ViewCache
viewCache; if
(rowView == null )
{ LayoutInflater
inflater = activity.getLayoutInflater(); rowView
= inflater.inflate(R.layout.image_and_text_row, null ); viewCache
= new
ViewCache(rowView); rowView.setTag(viewCache); }
else
{ viewCache
= (ViewCache) rowView.getTag(); } ImageAndText
imageAndText = getItem(position); //
Load the image and set it on the ImageView String
imageUrl = imageAndText.getImageUrl(); ImageView
imageView = viewCache.getImageView(); imageView.setTag(imageUrl); Drawable
cachedImage = asyncImageLoader.loadDrawable(imageUrl, new
ImageCallback() { public
void imageLoaded(Drawable imageDrawable, String imageUrl) { ImageView
imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl); if
(imageViewByTag != null )
{ imageViewByTag.setImageDrawable(imageDrawable); } } }); imageView.setImageDrawable(cachedImage); //
Set the text on the TextView TextView
textView = viewCache.getTextView(); textView.setText(imageAndText.getText()); return
rowView; } } |
这里有两点需要注意:第一点是drawable不再是加载完毕后直接设定到ImageView上。正确的ImageView是通过tag查找的,这是因为我们现在重用了View,并且图片有可能出现在错误的行上。我们需要拥有一个ListView的引用来通过tag查找ImageView。
另外一点是,实现中我们使用了一个叫ViewCache的对象。它这样定义:
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
|
public
class ViewCache { private
View baseView; private
TextView textView; private
ImageView imageView; public
ViewCache(View baseView) { this .baseView
= baseView; } public
TextView getTextView() { if
(textView == null )
{ textView
= (TextView) baseView.findViewById(R.id.text); } return
titleView; } public
ImageView getImageView() { if
(imageView == null )
{ imageView
= (ImageView) baseView.findViewById(R.id.image); } return
imageView; } } |
有了ViewCache对象,我们就不需要使用findViewById()来多次查询View对象了。
总结
我已经向大家演示了3种改进ListView性能的方法:
· 在单一线程里加载图片
· 重用列表中行
· 缓存行中的View
由浅入深讲解android开发中listview的性能优化,布布扣,bubuko.com
标签:android开发 性能优化 listview imageview 线程
原文地址:http://blog.csdn.net/fngy123/article/details/37575033