由浅入深讲解android开发中listview的性能优化

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的性能优化

时间: 2024-10-05 03:03:38

由浅入深讲解android开发中listview的性能优化的相关文章

android开发中 listview和checkbox结合

通过重写listview的adapter,将listview和checkbox结合在一起,并且二者可以分别操作. [1].[文件] activity_menu.xml ~ 2KB    下载(13) 跳至 [1] [2] [3] [4] ? 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

Android开发学习之路--性能优化之常用工具

??android性能优化相关的开发工具有很多很多种,这里对如下六个工具做个简单的使用介绍,主要有Android开发者选项,分析具体耗时的Trace view,布局复杂度工具Hierarchy View,应用启动时间,Memory.CPU.Network分析,静态代码检查工具Lint以及程序稳定性monkey.下面就开始学习下这些工具吧. 一.Android开发者选项 ??Andorid开发工具众多,首先就是手机自带的开发者选项了,至于手机怎么启动开发者选项,那么请自行百度或者google了,接

关于Net开发中一些SQLServer性能优化的建议

一. ExecuteNonQuery和ExecuteScalar 对数据的更新不需要返回结果集,建议使用ExecuteNonQuery.由于不返回结果集可省掉网络数据传输.它仅仅返回受影响的行数.如果只需更新数据用ExecuteNonQuery性能的开销比较小. ExecuteScalar它只返回结果集中第一行的第一列.使用 ExecuteScalar 方法从数据库中检索单个值(例如id号).与使用 ExecuteReader 方法, 返回的数据执行生成单个值所需的操作相比,此操作需要的代码较少

Android开发中常用的ListView列表的优化方式ViewHolder

在Android开发中难免会遇到大量的数据加载到ListView中进行显示, 然后其中最重要的数据传递桥梁Adapter适配器是常用的,随着市场的需 求变化ListView'条目中的内容是越来越多这就需要程序员来自定义适配器, 而关键的就是适配器的优化问题,适配器没有优化好往往就会造成OOM (内存溢出)或者是滑动卡顿之类的问题,接下来我就给大家介绍一种常 用的Adapter优化方法 1 /** 2 * list View的适配器 3 */ 4 class Adapter extends Bas

Android开发中Flag参数的讲解

Android开发中Flag参数的讲解: Intent对象在Android开发中起着举足轻重的作用,其内置了丰富的常量,用于传递数据, 下面本文将介绍跟Task有关的一些Flag参数,各参数的理解均来自Android API和本人在实际项目中的体验,如果有描述不当之处,还请各位不吝赐教. 1.FLAG_ACTIVITY_BROUGHT_TO_FRONT:不在程序代码中设置,在launchMode中设置singleTask模式时系统帮你设定. 2.FLAG_ACTIVITY_CLEAR_TOP:清

android开发中应该注意的问题

1. Activity可继承自BaseActivity,便于统一风格与处理公共事件,构建对话框统一构建器的建立,万一需要整体变动,一处修改到处有效. 2. 数据库表段字段常量和SQL逻辑分离,更清晰. 3. 全局变量放全局类中,模块私有放自己的管理类中,不要相信庞大的管理的东西会带来什么好处,可能是一场灾难. 4. 如果数据没有必要加载,私有模块数据务必延迟初始化,谨记为用户节省内存,总不会有坏处. 5. 异常抛出,集中到合适的位置处理,不要抛出来异常立即捕获,搞的到处是catch. 6. 地址

android开发中的5种存储数据方式

数据存储在开发中是使用最频繁的,根据不同的情况选择不同的存储数据方式对于提高开发效率很有帮助.下面笔者在主要介绍Android平台中实现数据存储的5种方式. 1.使用SharedPreferences存储数据 SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置比如窗口状态,一般在Activity中 重载窗口状态onSaveInstance State保存一般使用SharedPreferences完成,它提供了Android平台常规的Long长 整

Android开发:ListView、AdapterView、RecyclerView全面解析

目录 AdapterView简介 AdapterView本身是一个抽象类,AdapterView及其子类的继承关系如下图: 特征: AdapterView继承自ViewGroup,本质是个容器 AdapterView可以包含多个"列表项",并将这多个列表项以合适的形式展示 AdapterView显示的列表项内容由Adapter提供 它派生的子类在用法上也基本相似,只是在显示上有一定区别,因此把他们也归为一类. 由AdapterView直接派生的三个类: AbsListView.AbsS

如何在Android开发中让你的代码更有效率

如何在Android开发中让你的代码更有效率 最近看了一个视频,名字叫做Doing More With Less: Being a Good Android Citizen,主要是讲如何用少少的几句代码来改善Android App的性能.在这个视频里面,演讲者以一个图片app为例讲解如何应用Android中现有的东西来改善app性能问题. 这个图片app的代码:https://github.com/penkzhou/iogallery.ppt:http://greenrobot.qiniudn.