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

<ListViewandroid: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>() {@Overridepublic void onStart() {LogUtils.d(method.name() + " request is onStart.......");}@Overridepublic void onSuccess(ResponseInfo<String> responseInfo) {LogUtils.d("statusCode:" + responseInfo.statusCode + " ----->" + responseInfo.result);iOAuthCallBack.getIOAuthCallBack(responseInfo.result);// 利用接口回调数据传输}@Overridepublic 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);}@Overridepublic 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"><ImageViewandroid:id="@+id/picBig"android:layout_width="fill_parent"android:layout_height="180dp"android:scaleType="fitXY"android:src="@mipmap/ic_launcher"/><TextViewandroid: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"/><TextViewandroid: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;}@Overridepublic int getCount() {return (mDatas != null ? mDatas.size() : 0);}@Overridepublic Object getItem(int position) {return (mDatas != null ? mDatas.get(position) : null);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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;@Overridepublic 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() {@Overridepublic 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;}}@Overridepublic 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

转载于:https://www.cnblogs.com/hehe520/p/6329978.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458970.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Bzoj 3289: Mato的文件管理 莫队,树状数组,逆序对,离散化,分块

3289: Mato的文件管理 Time Limit: 40 Sec Memory Limit: 128 MBSubmit: 1539 Solved: 665[Submit][Status][Discuss]Description Mato同学从各路神犇以各种方式&#xff08;你们懂的&#xff09;收集了许多资料&#xff0c;这些资料一共有n份&#xff0c;每份有一个大小和一…

linux头文件 库,Linux操作系统的头文件和库文件搜索路径

一、 头文件1 “”中的头文件&#xff0c;在源文件当前目录查找2 -I 中指定目录 -I可以在CFLAG中指定3 gcc的环境变量 C_INCLUDE_PATH, CPLUS_INCLUDE_PATH, OBJC_INCLUDE_PATH4 编译器预设路径、内定目录&#xff1a;/usr/include/usr/local/include/usr/lib/gcc-lib/i386-lin…

vs2010创建和使用动态链接库(dll)

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 本文将创建一个简单的动态链接库&#xff0c;并编写一个应用台控制程序使用该动态链接…

通用二进制

通用二进制 通用二进制&#xff08;Universal binary&#xff09;是苹果电脑公司提出的一种程序代码&#xff0c;使程序能以本地程序的形式运行在使用PowerPC或者英特尔微处理器&#xff08;x86&#xff09;的麦金塔电脑上&#xff0c;在同一个程序包中同时为两种架构提供最理想…

Python~win32com~Excel

import win32com.client#wwin32com.client.Dispatch("Word.Application") #w.Visible1owin32com.client.Dispatch("Excel.Application") o.Visible1 o.Workbooks.Add() o.Cells(1,1).Value"Hello"转载于:https://www.cnblogs.com/lynclynn/p/530…

linux显示光盘命令行,使用wodim在命令行下烧录光盘

使用wodim在命令行下烧录光盘发布时间:2009-02-27 16:23:11来源:红联作者:zhania作者&#xff1a;linuxtoy出自http://linuxtoy.org/archives/burning-cd-with-wodim.html我们以前介绍的 Linux 光盘烧录工具多为图形化的程序&#xff0c;今天来看看如何使用 wodim 在命令行下烧…

Android(java)学习笔记144:网络图片浏览器的实现(ANR)

1.我们在Android下&#xff0c;实现使用http协议进行网络通信&#xff0c;请求网络数据。这里是获取网络上的图片信息&#xff0c;让它可以显示在手机上&#xff1b; 但是我们这个手机连接网络是很费时间&#xff0c;如果我们在主线程&#xff08;UI线程&#xff09;中写这个网…

DLL导出函数名称改编的解决方法

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 1.DLL编译后导出函数名称改变 在编写一个DLL后&#xff0c;为了能被别的程序调用&…

组合自定义控件的步骤详解

Android 步骤&#xff1a; 1 自定义组合控件的布局settint_view.xml<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"and…

linux如何建立隐藏目录,【Linux】文件与目录的默认权限与隐藏权限

01. 文件默认权限&#xff1a;umask文件的权限可以使用chmod来改变&#xff0c;但是我们默认创建文件的权限是什么&#xff1f;那就是与umask这个有关了。下来我们学习这个指令1.1 简单使用umask[rootiZbp13q6hd8z3xaagcmz6gZ /]# umask0022[rootiZbp13q6hd8z3xaagcmz6gZ /]# u…

Servlet和JSP学习指导与实践(二):Session追踪

前言&#xff1a; web应用中经常需要对某些有用的信息进行存储或者附加一些信息。本文主要介绍session&#xff0c;即“会话”跟踪的几种不同方式~ ----------------------------4种管理session的方式&#xff1a; 1.重写url 通过在请求的url后面追加参数信息进行会话跟踪。如&…

数据存储和界面展示(二)

#测试 黑盒测试 测试逻辑业务 白盒测试 测试逻辑方法 根据测试粒度 方法测试&#xff1a;function test 单元测试&#xff1a;unit test 集成测试&#xff1a;integration test 系统测试&#xff1a;system test 根据测试暴力程度 冒烟测试&#xff1a;smoke test 压力测…

linux在A目录下创建B文件,Linux课程---5、常用文件命令和目录命令(创建文件命令)...

Linux课程---5、常用文件命令和目录命令(创建文件命令)一、总结一句话总结&#xff1a;touch file11、管道符|有什么用&#xff1f;将前一个命令的结果作为后一个命令的输入&#xff1a;比如查看文件前3行&#xff1a;cat file1 | head -32、linux下如何复制粘贴命令是什么&…

window 系统上传文件到linux 系统出现dos 格式换行符

Windows里的文件在Unix/Mac下打开的话&#xff0c;在每行的结尾可能会多出一个^M符号&#xff0c;Unix/Mac系统下的文件在Windows里打开的话&#xff0c;所有文字会变成一行&#xff0c;所以为了避免这种情况的发生&#xff0c;我们可以在linux系统内转换格式 Centos系列可以直…

#pragma once与 #ifndef的区别

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 为了避免同一个文件被include多次 1 #ifndef方式2 #pragma once方式 在能够支持这…

android学习者优秀网址推荐

非常漂亮的android UI库集合&#xff0c;别人整理的&#xff0c;如果感觉不错&#xff0c;赶快收藏吧&#xff01;&#xff01; https://github.com/wasabeef/awesome-android-ui https://github.com/Trinea/android-open-project android中文社区网 http://www.android-studio…

linux while read文件,linux shell脚本用while read逐行读取文本的问题

问题:我现在是想用一个脚本获取一定列表服务器的运行时间。首先我建立一个名字为ip.txt的IP列表(一个IP一行)&#xff0c;再建好密钥实现不用密码直接登录。然后写脚本如下&#xff1a;#!/bin/bashwhile read ips;doecho $ips;done < ip.txt脚本实现了逐行读取列表中的IP&am…

常用字符串处理函数汇总

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** (一)strcmp函数 strcmp函数是比较两个字符串的大小,返回比较的结果。一般形式是&…

兼容性记录-class属性

getAttribute获得class属性时,IE6,IE7的传參是className,IE7和现代游览器都是class全部游览器DOMElement均有的className属性,其在IE各版本号下的均表现良好返回属性class值的字符串此外html5中DOMElement有个classList属性,它返回一个类型为DOMTokenList的对象,它当中有非常多…

magenta内核与linux,谷歌将推出新操作系统Fuchsia:Magenta语言为内核

谷歌现在研发出来并且推出使用的系统有Chrome OS、Android和Chromecasts&#xff0c;这三者在操作系统的市场中占得份额很高&#xff0c;但是好像谷歌对此并不满意&#xff0c;因为有相关消息显示&#xff0c;谷歌正在研发新的操作系统Fuchsia&#xff0c;该系统采用Magenta语言…