• ListView性能


    ListView性能[译]

    原文路径:http://blog.jteam.nl/2009/09/17/exploring-the-world-of-android-part-2/

    ListView是一种可以显示一系列项目并能进行滚动显示的View。在每行里,既可以是简单的文本,也可以是复杂的结构。一般情况下,你都需要保证ListView运行得很好(即:渲染更快,滚动流畅)。在接下来的内容里,我将就ListView的使用,向大家提供几种解决不同性能问题的解决方案。

    如果你想使用ListView,你就不得不使用ListAdapter来显示内容。SDK中,已经有了几种简单实现的Adapter:

    ·         ArrayAdapter<T> (显示数组对象,使用toString()来显示)

    ·         SimpleAdapter (显示Maps列表)

    ·         SimpleCursorAdapter(显示通过Cursor从DB中获取的信息)

    这些实现对于显示简单的列表来说,非常棒!一旦你的列表比较复杂,你就不得不书写自己的ListAdapter实现。在多数情况下,直接从ArrayAdapter扩展就能很好地处理一组对象。此时,你需要处理的工作只是告诉系统如何处理列表中的对象。通过重写getView(int, View, ViewGroup)方法即可达到。

    在这里,举一个你需要自定义ListAdapter的例子:显示一组图片,图片的旁边有文字挨着。

    images with text in a ListView
    ListView例:以图片文字方式显示的Youtube搜索结果

    图片需要实时从internet上下载下来。让我们先创建一个Class来代表列表中的项目:

    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列表。

    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的:

    <?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来负责图片的下载。此外,它还缓存了图片,防止单个图片被下载多次。

    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类中,我们可以这样替换:

    ImageView imageView = (ImageView) rowView.findViewById(R.id.image);

    imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl()));

    换成

    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

    因此,修改代码如下:

    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的对象。它这样定义:

    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

    自我的修养,从点滴做起。
  • 相关阅读:
    Eclipse的Tomcat插件安装
    Struts1.2 struts-config.xml配置详解
    JavaWeb:报错信息The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path
    dede 忘记密码在数据库中修改方法
    如何将XML文件中的数据传送并保存在关系数据库中
    lamp
    开发人员必读的11本最具影响力书籍
    org.apache.commons.lang.StringUtils类
    B. Jzzhu and Sequences
    线段树——习题、lazy解析
  • 原文地址:https://www.cnblogs.com/tshq/p/5642893.html
Copyright © 2020-2023  润新知