Android ListView性能优化实例讲解

前言:

  对于ListView,大家绝对都不会陌生,只要是做过Android开发的人,哪有不用ListView的呢?

  只要是用过ListView的人,哪有不关心对它性能优化的呢?

  关于如何对ListView进行性能优化,不仅是面试中常常会被问到的(我前段时间面试了几家公司,全部都问到了这个问题了),而且在实际项目中更是非常重要的一环,它甚至在某种程度上决定了用户是否喜欢接受你的APP。(如果你的列表滑起来很卡,我敢说很多人会直接卸载)

  网上关于如何对ListView进行性能优化,提出了很多方案。但是我搜过很多资料,却感觉很多文章都写得比较模糊,没有代码说明,让我感到很累。要知道能给程序员最直接感官刺激的,当然是代码啦!!!

一、Listview 性能优化方案

  1).复用convertView

    在getItemView中,判断convertView是否为空,如果不为空,可复用。如果couvertview中的view需要添加listerner,代码一定要在if(convertView==null){}之外。

  2).异步加载图片

    item中如果包含有webimage,那么最好异步加载

  3).快速滑动时不显示图片

    当快速滑动列表时(SCROLL_STATE_FLING),item中的图片或获取需要消耗资源的view,可以不显示出来;而处于其他两种状态(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来

二、实战讲解如何优化ListView

    2.1 我们先定义一个ListView

<ListView
        android:id="@+id/listview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:divider="#7A7A7A"
        android:dividerHeight="10dp"
        />

    2.2 然后我们去写一个网络请求,获取网络的json字符串。

    这里,我们用到xutils框架的httputil,通过它,可以很方便的进行网络请求。 至于请求的url,我们使用慕课网提供的视频数据列表接口“http://www.imooc.com/api/teacher?type=4&num=30”。先让我们看下我写的一个HTTP请求的工具类:

import android.content.Context;

import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.RequestParams;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest.HttpMethod;
import com.lidroid.xutils.util.LogUtils;

/**
 * 网络请求工具类
 *
 * @author lining
 */
public class HttpUtil {
    /**
     * 请求的根URL地址
     */
    public static final String BASE_URL = "http://www.imooc.com/api/teacher?type=4&num=50";

    public static void sendRequest(final Context context,
                                   final HttpMethod method, RequestParams params,
                                   final IOAuthCallBack iOAuthCallBack) {
        HttpUtils http = new HttpUtils();

        http.configCurrentHttpCacheExpiry(1000 * 5);
        // 设置超时时间
        http.configTimeout(5 * 1000);
        http.configSoTimeout(5 * 1000);

        if (method == HttpMethod.GET) {
            http.configCurrentHttpCacheExpiry(5000); // 设置缓存5秒,5秒内直接返回上次成功请求的结果。
        }

        http.send(method, BASE_URL, params,
                new RequestCallBack<String>() {

                    @Override
                    public void onStart() {
                        LogUtils.d(method.name() + " request is onStart.......");
                    }

                    @Override
                    public void onSuccess(ResponseInfo<String> responseInfo) {
                        LogUtils.d("statusCode:" + responseInfo.statusCode + " ----->" + responseInfo.result);
                        iOAuthCallBack.getIOAuthCallBack(responseInfo.result);// 利用接口回调数据传输
                    }

                    @Override
                    public void onFailure(HttpException error, String msg) {
                        LogUtils.d("statusCode:" + error.getExceptionCode() + " -----> " + msg);
                        iOAuthCallBack.getIOAuthCallBack("FF");// 利用接口回调数据传输
                    }
                });
    }
}

  工具类其实并没有啥特别之处,无非就是利用Xutils框架的HttpUtil发送网络请求,获取数据。 方法参数里,我们加入了一个IOAuthCallBack回调接口,该接口主要用户在Activity和工具类之间回调请求结果数据。

/**
 * 数据请求回调接口
 */
public interface IOAuthCallBack
{
    // 成功
    public void getIOAuthCallBack(String result);
}

    下面,我们Activity发送一个网络请求,获取json数据,并回调处理:

 private void qryDataFromServer() {
        HttpUtil.sendRequest(this, HttpRequest.HttpMethod.GET, null, this);
    }

    @Override
    public void getIOAuthCallBack(String result) {

        RspData rspData = GsonUtil.getGson().fromJson(result, RspData.class);
        // 更新UI列表

        KechengAdapter mAdapter = new KechengAdapter(this, rspData.data);
        listview.setAdapter(mAdapter);
    }

这里关于json数据的解析使用的GSON,无啥特别说明之处,把实体类的代码贴出来看下:

public class RspData {
    public String status;
    public List<KeCheng> data;
    public String msg;
}

public class KeCheng {

    public String id;

    public String name;

    public String picSmall;

    public String picBig;

    public String description;

    public String learner;
}

    2.3 有了集合数据之后,去定义BaseAdapter

   在此之前,我们先看下list item的布局文件:list_item_kecheng.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/picBig"
        android:layout_width="fill_parent"
        android:layout_height="180dp"
        android:scaleType="fitXY"
        android:src="@mipmap/ic_launcher"
        />
    <TextView
        android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="CSS动画实用技巧"
        android:singleLine="true"
        android:padding="10dp"
        android:textSize="15sp"
        />

    <TextView
        android:id="@+id/description"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="教你使用CSS实现惊艳的动画效果!"
        android:textSize="12sp"
        android:lines="2"
        android:padding="10dp"
        />

</LinearLayout>

  接下来,让我们好好看看Adapter是如何定义的:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class KechengAdapter extends BaseAdapter {

    private Context mContext;

    private LayoutInflater mInflater;

    private List<KeCheng> mDatas;

    public KechengAdapter(Context context, List<KeCheng> datas) {
        mContext = context;
        mInflater = LayoutInflater.from(mContext);
        mDatas = datas;
    }

    @Override
    public int getCount() {
        return (mDatas != null ? mDatas.size() : 0);
    }

    @Override
    public Object getItem(int position) {
        return (mDatas != null ? mDatas.get(position) : null);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item_kecheng, null);

            holder = new ViewHolder();
            holder.picBig = (ImageView) convertView.findViewById(R.id.picBig);
            holder.name = (TextView) convertView.findViewById(R.id.name);
            holder.description = (TextView) convertView.findViewById(R.id.description);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        final KeCheng keCheng = mDatas.get(position);

        if (keCheng != null) {
            ImageLoaderUtil.getInstance().displayListItemImage(keCheng.picBig, holder.picBig);
            holder.name.setText(keCheng.name);
            holder.description.setText(keCheng.description);

        }
        return convertView;
    }

    static class ViewHolder {

        ImageView picBig;

        TextView name;

        TextView description;
    }
}

   ListView性能优化的重点就是如何去处理BaseAdapter,且看上面的代码,我们在getView中,判断convertView是否为空,如果不为空,可复用。如何复用的呢?

  我们通过convertview的setTag方法和getTag方法来将我们要显示的数据来绑定在convertview上。如果convertview 是第一次展示我们就创建新的Holder对象与之绑定,并在最后通过return convertview 返回,去显示;如果convertview 是回收来的那么我们就不必创建新的holder对象,只需要把原来的绑定的holder取出加上新的数据就行了。

   如果couvertview中的view需要添加listerner,代码一定要在if(convertView==null){}之外。

   看代码够仔细的人能够发现有这么一行代码,ImageLoaderUtil.getInstance().displayListItemImage(keCheng.picBig, holder.picBig); 这是使用的图片异步加载框架Universal-Image-Loader来完成对网络图片的异步加载、缓存,(强烈推荐使用)使用这个开源框架后,我们就无需再为如何加载缓存网络图片烦恼啦!

   快随我一起看看如何配置这个框架吧:

import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;

import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;

import java.io.File;

/**
 * 配置全局的 Android-Universal-Image-Loader
 */
public class ImageLoaderUtil {
    private static ImageLoaderUtil instance = null;

    private ImageLoader mImageLoader;

    // 列表中默认的图片
    private DisplayImageOptions mListItemOptions;

    // 头像图片
    private DisplayImageOptions mUserHeadOptions;

    private ImageLoaderUtil(Context context) {
        mImageLoader = ImageLoader.getInstance();
        mListItemOptions = new DisplayImageOptions.Builder()
                // 设置图片Uri为空或是错误的时候显示的图片
                .showImageForEmptyUri(R.mipmap.load_default_img)
                .showStubImage(R.mipmap.load_default_img)
                        // 设置图片加载/解码过程中错误时候显示的图片
                .showImageOnFail(R.mipmap.load_default_img)
                        // 加载图片时会在内存、磁盘中加载缓存
                .cacheInMemory()
                .cacheOnDisc()
                .bitmapConfig(Bitmap.Config.RGB_565)
                .delayBeforeLoading(300)
                .build();

    }

    public static ImageLoaderUtil getInstance() {
        return instance;
    }

    public synchronized static ImageLoaderUtil init(Context context) {
        if (instance == null) {
            instance = new ImageLoaderUtil(context);
        }

        File cacheDir = context.getExternalFilesDir("news/pictures");
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).threadPriority(
                Thread.NORM_PRIORITY - 2).denyCacheImageMultipleSizesInMemory()
                // .imageDownloader(imageDownloader).imageDecoder(imageDecoder)
                .discCacheFileNameGenerator(new Md5FileNameGenerator()).tasksProcessingOrder(QueueProcessingType.LIFO).memoryCacheExtraOptions(
                        360, 360).memoryCache(new UsingFreqLimitedMemoryCache(4 * 1024 * 1024)).discCache(
                        new UnlimitedDiscCache(cacheDir)).build();
        // Initialize ImageLoader with configuration.
        ImageLoader.getInstance().init(config);

        return instance;
    }

    /**
     * 列表图片
     *
     * @param uri
     * @param imageView
     */
    public void displayListItemImage(String uri, ImageView imageView) {
        String strUri = (isEmpty(uri) ? "" : uri);
        mImageLoader.displayImage(strUri, imageView, mListItemOptions);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }

    private boolean isEmpty(String str) {
        if (str != null && str.trim().length() > 0 && !str.equalsIgnoreCase("null")) {
            return false;
        }
        return true;
    }
}

   这是我写好的一个Universal-Image-Loader的工具类,以后可以直接使用它进行图片的下载缓存处理了。 当然在使用前,还需要进行初始化它,我们推荐在Application中对其进行初始化操作:

public class MyApp extends Application {

    public static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
        ImageLoaderUtil.init(context);
    }
}

   

    2.4 处理快速滑动时暂停加载图片

     我们知道,当快速滑动列表时(SCROLL_STATE_FLING),item中的图片获取需要消耗资源的View,可以不显示出来(因为滑动的过快,我们也不需要看图片啊);而处于其他两种状态(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来。

     那如何实现呢? 这里我还是推荐使用Universal-Image-Loader已经为大家封装好了的方法,(当然,别的框架,如Xutils也封装了相关的方法)。Universal-Image-Loader框架的com.nostra13.universalimageloader.core.assist.PauseOnScrollListener监听器已经封装了对滚动时图片处理的监听,我们只需要在为ListView组件设置滚动监听的时候,把PauseOnScrollListener的实例传入即可。这里,有必要让大家先看下PauseOnScrollListener的源码:

public class PauseOnScrollListener implements OnScrollListener {
    private ImageLoader imageLoader;
    private final boolean pauseOnScroll;
    private final boolean pauseOnFling;
    private final OnScrollListener externalListener;

    public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) {
        this(imageLoader, pauseOnScroll, pauseOnFling, (OnScrollListener)null);
    }

    public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling, OnScrollListener customListener) {
        this.imageLoader = imageLoader;
        this.pauseOnScroll = pauseOnScroll;
        this.pauseOnFling = pauseOnFling;
        this.externalListener = customListener;
    }

    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch(scrollState) {
        case 0:
            this.imageLoader.resume();
            break;
        case 1:
            if(this.pauseOnScroll) {
                this.imageLoader.pause();
            }
            break;
        case 2:
            if(this.pauseOnFling) {
                this.imageLoader.pause();
            }
        }

        if(this.externalListener != null) {
            this.externalListener.onScrollStateChanged(view, scrollState);
        }

    }

    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if(this.externalListener != null) {
            this.externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }

    }
}

    大家可以看到,PauseOnScrollListener实现了OnScrollListener接口,这也就是刚刚为啥说可以把PauseOnScrollListener的实例设置到ListView监听器的原因。PauseOnScrollListener有两个重要的构造方法,其中参数pauseOnScroll控制我们缓慢滑动ListView,GridView是否停止加载图片,pauseOnFling
控制猛的滑动ListView,GridView是否停止加载图片。而另一个参数OnScrollListener customListener则可以用于留给开发者继续回到处理相应的滑动监听事件,比如列表是否滑动到了最后等等。

      知道了如何利用PauseOnScrollListener,那我们在Activity之中只需要设置一句简单的监听代码即可:

listview.setOnScrollListener(new PauseOnScrollListener(ImageLoaderUtil.getInstance().getImageLoader(), false, true));

      如何你的项目需要下来刷新或者是滑动加载等功能,你又必须提供滑动事件的回调参数:

listview.setOnScrollListener(new PauseOnScrollListener(ImageLoaderUtil.getInstance().getImageLoader(), false, true, onScrollListener));
 private AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            switch (scrollState) {
                case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                    // 触摸后滚动
                    break;

                case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                    // 滚动状态
                    break;

                case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                    // 空闲状态
                    if (view.getLastVisiblePosition() == view.getCount() - 1) {
                        System.out.println("************滚动到了最后一个***************");
                    }
                    break;
            }
        }
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

        }
    };

好啦,这样做出的ListView已经很完美了,让我们欣赏下它的效果吧:

结束语:

    本文主要通过三个方面:1、复用convertView;2、异步加载图片; 3、ListView快速滑动时不显示图片介绍了如何对ListView进行性能优化,这是最常见也是最重要的三个方面,建议大家务必将其使用在自己项目的开发中,以提高列表的易用性!

     当然,文章还提到了两个第三方框架的使用:Xutils和Universal-Image-Loader,这是两个非常使用的框架,建议大家也能学习下。

    如果大家还有别的优化方案,建议提出来,共同学习,共同进步。

源码下载地址:http://download.csdn.net/detail/zuiwuyuan/9055795

Gitub下载地址:https://github.com/zuiwuyuan/ListViewOptimized

时间: 2024-08-03 17:44:51

Android ListView性能优化实例讲解的相关文章

[Android]ListView性能优化之视图缓存(续)

前言 在上一篇ListView性能优化之视图缓存我们讨论了Google I/O中的优化方法,在各个论坛发帖后得到了不错的反馈,诸如:使用ViewHolder技术Tag的问题,利用HashMap自行存储的方案等.这里结合新浪微博中主界面的做法及测试数据与大家进一步探讨.   声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: http://over140.cnblogs.com    文章 [Android]ListView性能优化之视图缓

[Android]ListView性能优化之视图缓存

前言 ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题.本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流.   声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: http://over140.cnblogs.com   正文 一.准备 1.1 了解关于Google IO大会关于Adapter的优化,参考以下文章: Android开发之List

Android应用性能优化最佳实践.

移动开发 Android应用性能优化最佳实践 罗彧成 著 图书在版编目(CIP)数据 Android应用性能优化最佳实践 / 罗彧成著. -北京:机械工业出版社,2017.1 (移动开发) ISBN 978-7-111-55616-9 I. A- II. 罗- III. 移动终端-应用程序-程序设计 IV. TN929.53 中国版本图书馆CIP数据核字(2016)第315986号 Android应用性能优化最佳实践 出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037

Android开发性能优化总结

一. 加载 预加载:1.反射注解框架Reflect信息,在Application内多线程预加载至缓存.2.资源预加载 懒加载:1.Fragment懒加载2.资源懒加载 二. 缓存 1. Http缓存,淘汰时间 2. 图片缓存,bitmap压缩,Lru淘汰,持久化二级缓存 3. 反射注解框架Reflect信息缓存,防止多次反射操作 三. 异步防止Anr 1. 避免在UI线程做太多耗时操作,IntentReceiver > 10s Anr 2. 并发操作多用读写锁,少用synchronized,An

浅析安卓(Android)的性能优化_Android

Android性能的优化主要分为两点 1.布局优化 2.内存优化 布局优化 首先来看一下布局优化,系统在渲染UI的时候会消耗大量的资源,所以,对布局的优化就显得尤为重要 避免Overdraw 也就是避免过度的绘制,过度的绘制会浪费更多的资源,举个例子,Android系统会默认绘制Activity的背景,这时候我们再设置一个背景,这样默认的背景就属于过度绘制了,在『开发者工具』中有一个『调试GPU过度绘制』的选项,我们打开就可以通过颜色来判断过度绘制的次数 如图: 所以说我们尽可能的增大蓝色区域,

Android 对话框 Dialog使用实例讲解_Android

对话框 Dialog 什么是对话框 对话框是在当前的页面之上弹出的小窗口, 用于显示一些重要的提示信息, 提示用户的输入,确认信息,或显示某种状态.如 : 显示进度条对话框, 退出提示. 对话框的特点: 1, 当前界面弹出的小窗口. 2, 用户要与它进行交互, 可以接收用户输入的信息, 也可以反馈信息给用户. 常用对话框: 1, 普通对话框 AlertDialog 2, 进度条对话框 ProgressDialog 3, 日期对话框 DatePickerDialog 4, 时间对话框 TimePi

分享 Android 开发性能优化的技术要点

Android性能调优涉及到多方面的工作,因本人技术水平有限,目前只总结了以下部分,希望大家继续补充. 要点 使用异步     保持 APP 的高度响应,不要在 UI 线程做耗时操作,多使用异步任务    使用线程时要做好线程控制:使用队列.线程池    谨慎使用糟糕的 AysncTask . Timer    警惕异步任务引起的内存泄露    应该异步任务分类,比如 HTTP ,图片下载,文件读写,每一类的异步任务维护一个任务队列,而不是每一个任务都开一个线程( Volley 表示我一个可以搞

Android布局性能优化之按需加载View

有时应用程序中会有一些很少用到的复杂布局.在需要它们的时候再加载可以降低内存的消耗,同时也可以加快界面的渲染速度. 定义ViewStub ViewStub是一个轻量级的View,它没有高宽,也不会绘制任何东西.所以它的加载与卸载的成本很低.每个ViewStub都可以使用android:layout属性指定要加载的布局. 下面这个ViewStub用于一个半透明的ProgressBar的加载.它只有在新工作开始时才会显示. <ViewStub android:id="@+id/stub_imp

Android应用性能优化最佳实践.2.2 性能分析工具

2.2 性能分析工具 从前一节可以看到,Android系统在4.1以后从框架上解决了由于系统问题导致的卡顿现象,但在实际的使用过程中,在用户的感受上,卡顿仍然是应用开发中主要面临的问题,而原因从上一节的分析中也知道本质是VSync信号到来时,不能及时处理绘制事件导致,本节先抛出以下两个问题: 1)应用层做了什么会导致VSync事件不能及时处理? 2)卡顿能监控吗? 性能问题并不容易复现,也不好定位,光从几个场景不能完全覆盖所有的问题,因此在做性能优化时,最直接有效的方法,就是尽量复现存在性能问题