zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    获取android手机内所有图片
    进程、线程与任务程序之间的关系
    wife的简单使用(一个简单的例子)
    [转载]mode_t等系统数据类型
    双调欧几里得旅行商问题
    HDU OJ分类
    CLRS2.4__逆序对
    [转载] Android 2.3.3 API 读取通讯录中电话号码的实例
    [转]有关TinyXML使用的简单总结
    ARG_MAX问题
  • 原文地址:https://www.cnblogs.com/hehe520/p/6329978.html
Copyright © 2011-2022 走看看