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 ListView性能优化实例讲解的更多相关文章
- (翻译) Android ListView 性能优化指南
本文翻译了Lucas Rocha的Performance Tips for Android’s ListView.这是一篇关于介绍如何提升ListView性能的文章,非常的优秀.使得我拜读之后,忍不住 ...
- Android Listview 性能优化
首先我一般使用的适配器是BaseAdapter,其中有两个方法最主要,分别是: getCount,getView, 在对Listview 进行优化的时候,首先使用 convertview 和viewH ...
- Android之ListView性能优化——一行代码绑定数据——万能适配器
如下图,加入现在有一个这样的需求图,你会怎么做?作为一个初学者,之前我都是直接用SimpleAdapter结合一个Item的布局来实现的,感觉这样实现起来很方便(基本上一行代码就可以实现),而且也没有 ...
- Android进阶笔记14:ListView篇之ListView性能优化
1. 首先思考一个问题ListView如何才能提高效率 ? 当convertView为空时候,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象.当convertView不 ...
- Android进阶笔记11:ListView篇之ListView性能优化
1. 首先思考一个问题ListView如何才能提高效率 ? 当convertView为空时候,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象.当convertView不 ...
- ym——Android之ListView性能优化
转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! Android之ListView性能优化 假设有看过我写过的15k面试题的朋友们一定知 ...
- 包建强的培训课程(9):Android App性能优化
v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...
- Android开发性能优化总结(一)
安卓开发应用首先要讲究良好的用户体验,如果一款软件卡顿现象严重,不流畅,经常崩溃,那么将给用户带来极不良好的体验,从而损失用户. 在实际开发和学习中,我总结了一下关于安卓性能的优化,供大家参考交流. ...
- 【腾讯Bugly干货分享】跨平台 ListView 性能优化
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/FbiSLPxFdGqJ00WgpJ94yw 导语 精 ...
随机推荐
- 配置管理 ACM 在高可用服务 AHAS 流控降级组件中的应用场景
应用配置管理(Application Configuration Management,简称 ACM)是一款应用配置中心产品.基于ACM您可以在微服务.DevOps.大数据等场景下极大地减轻配置管理的 ...
- WPF 触发器例子
WPF的触发器很强大,这里简单附上触发器的一个小例子,分别用XMAL和CS代码来实现一个功能,鼠标悬停在button上时改变字体颜色 1.XMAL代码如下: <Window x:Class=&q ...
- MySQL.之 一行内容转换多行
MySQL.之 一行内容转换多行 描述: 将一行记录中的某一列值(值格式:数据之间用英文逗号隔开),将这一数据转换成多行. 例如:表 cds_var 中的 cds_value 中的数据格式:多个id之 ...
- SQL优化系列(二)- 优化Top SQL
优化最耗资源的N条SQL语句 如何从SGA或者AWR中找出最消耗资源的SQL, 例如最慢的20条SQL, 然后逐条优化? SQL自动优化工具SQL Tuning Expert Pro for Orac ...
- LeetCode169 Majority Element, LintCode47 Majority Number II, LeetCode229 Majority Element II, LintCode48 Majority Number III
LeetCode169. Majority Element Given an array of size n, find the majority element. The majority elem ...
- Android手机控制电脑撸出HelloWorld
最近在开发一个远程办公的软件. 昨天在手机调通,并且成功通过手机打开电脑上的Eclipse撸出来一个HelloWorld. 也许不久的将来, 下班后,拿着手机在家写代码了.工作时间直接变成24/24 ...
- Dubbo报org.I0Itec.zkclient.exception.ZkNoNodeException异常
解决办法就是添加zkclient的jar,maven工程的话增加如下引用: <dependency> <groupId>com.github.sgroschupf< ...
- 使用vscode书写markdown文件
插件推荐 markdown-preview-enhanced 打开 vscode 编辑器,在插件页搜索 markdown-preview-enhanced,接着点击 Install 按钮. 该插件的中 ...
- 一个iOS开发者对tvOS SDK的初探
http://www.cocoachina.com/ios/20151001/13652.html 作者:Chris Wagner原文地址:tvOS SDK: An iOS Developer’s I ...
- 散列表(Hash Table)
散列表(hash table): 也称为哈希表. 根据wikipedia的定义:是根据关键字(Key value)而直接访问在内存存储位置的数据结构.也就是说,它通过把键值通过一个函数的计算,映射到表 ...