Listview加载的性能优化是如何实现的

在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。

listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次:

0、最原始的加载

1、利用convertView

2、利用ViewHolder

3、实现局部刷新

〇、最原始的加载

这里是不经任何优化的adapter,为了看起来方便,把listview的数据直接在构造函数里传给adapter了,代码如下:

private class AdapterOptmL extends BaseAdapter {
private LayoutInflater mLayoutInflater;
private ArrayList<Integer> mListData;
public AdapterOptmL(Context context, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
mListData = data;
}
@Override
public int getCount() {
return mListData == null ? : mListData.size();
}
@Override
public Object getItem(int position) {
return mListData == null ? : mListData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false);
if (viewRoot != null) {
TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
txt.setText(getItem(position) + "");
}
return viewRoot;
}
}

一、利用convertView

上述代码的第27行在Eclipse中已经提示警告:

Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling

这个意思就是说,被移出可视区域的view是可以回收复用的,它作为getview的第二个参数已经传进来了,所以没必要每次都从xml里inflate。

经过优化后的代码如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
}
if (convertView != null) {
TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt);
txt.setVisibility(View.VISIBLE);
txt.setText(getItem(position) + "");
}
return convertView;
}

上述代码加了判断,如果传入的convertView不为null,则直接复用,否则才会从xml里inflate。

按照上述代码,如果手机一屏最多同时显示5个listitem,则最多需要从xml里inflate 5 次,比AdapterOptmL0中每个listitem都需要inflate显然效率高多了。

上述的用法虽然提高了效率,但带来了一个陷阱,如果复用convertView,则需要重置该view所有可能被修改过的属性。

举个例子:

如果第一个view中的textview在getview中被设置成INVISIBLE了,而现在第一个view在滚动过程中出可视区域,并假设它作为参数传入第十个view的getview而被复用

那么,在第十个view的getview里面不仅要setText,还要重新setVisibility,因为这个被复用的view当前处于INVISIBLE状态!

二、利用ViewHolder

从AdapterOptmL0第27行的警告中,我们还可以看到编译器推荐了一种模型叫ViewHolder,这是个什么东西呢,先看代码:

private class AdapterOptmL extends BaseAdapter {

private LayoutInflater mLayoutInflater;
private ArrayList<Integer> mListData;
public AdapterOptmL(Context context, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
mListData = data;
}
private class ViewHolder {
public ViewHolder(View viewRoot) {
txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
}
public TextView txt;
}
@Override
public int getCount() {
return mListData == null ? : mListData.size();
}
@Override
public Object getItem(int position) {
return mListData == null ? : mListData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
ViewHolder holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
if (convertView != null && convertView.getTag() instanceof ViewHolder) {
ViewHolder holder = (ViewHolder)convertView.getTag();
holder.txt.setVisibility(View.VISIBLE);
holder.txt.setText(getItem(position) + "");
}
return convertView;
}
}

从代码中可以看到,这一步做的优化是用一个类ViewHolder来保存listitem里面所有找到的子控件,这样就不用每次都通过耗时的findViewById操作了。

这一步的优化,在listitem布局越复杂的时候效果越为明显。

三、实现局部刷新

OK,到目前为止,listview普遍需要的优化已经做的差不多了,那就该考虑实际使用场景中的优化需求了。

实际使用listview过程中,通常会在后台更新listview的数据,然后调用Adatper的notifyDataSetChanged方法来更新listview的UI。

那么问题来了,一般情况下,一次只会更新listview的一条/几条数据,而调用notifyDataSetChanged方法则会把所有可视范围内的listitem都刷新一遍,这是不科学的!

所以,进一步优化的空间在于,局部刷新listview,话不多说见代码:

private class AdapterOptmL3 extends BaseAdapter {
private LayoutInflater mLayoutInflater;
private ListView mListView;
private ArrayList<Integer> mListData;
public AdapterOptmL3(Context context, ListView listview, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
mListView = listview;
mListData = data;
}
private class ViewHolder {
public ViewHolder(View viewRoot) {
txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
}
public TextView txt;
}
@Override
public int getCount() {
return mListData == null ? 0 : mListData.size();
}
@Override
public Object getItem(int position) {
return mListData == null ? 0 : mListData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
ViewHolder holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
if (convertView != null && convertView.getTag() instanceof ViewHolder) {
updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));
}
return convertView;
}
public void updateView(ViewHolder holder, Integer data) {
if (holder != null && data != null) {
holder.txt.setVisibility(View.VISIBLE);
holder.txt.setText(data + "");
}
}
public void notifyDataSetChanged(int position) {
final int firstVisiablePosition = mListView.getFirstVisiblePosition();
final int lastVisiablePosition = mListView.getLastVisiblePosition();
final int relativePosition = position - firstVisiablePosition;
if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
} else {
//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新
}
}
} 

修改后的Adapter新增了一个方法 public void notifyDataSetChanged(int position) 可以根据position只更新指定的listitem。

局部刷新番外篇

在局部刷新数据的接口中,实际上还可以再干点事情:listview正在滚动的时候不去刷新。

具体的思路是,如果当前正在滚动,则记住一个pending任务,等listview停止滚动的时候再去刷,这样不会造成滚动的时候刷新错乱。代码如下:

private class AdapterOptmLPlus extends BaseAdapter implements OnScrollListener{
private LayoutInflater mLayoutInflater;
private ListView mListView;
private ArrayList<Integer> mListData;
private int mScrollState = SCROLL_STATE_IDLE;
private List<Runnable> mPendingNotify = new ArrayList<Runnable>();
public AdapterOptmLPlus(Context context, ListView listview, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
mListView = listview;
mListData = data;
mListView.setOnScrollListener(this);
}
private class ViewHolder {
public ViewHolder(View viewRoot) {
txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
}
public TextView txt;
}
@Override
public int getCount() {
return mListData == null ? : mListData.size();
}
@Override
public Object getItem(int position) {
return mListData == null ? : mListData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
ViewHolder holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
if (convertView != null && convertView.getTag() instanceof ViewHolder) {
updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));
}
return convertView;
}
public void updateView(ViewHolder holder, Integer data) {
if (holder != null && data != null) {
holder.txt.setVisibility(View.VISIBLE);
holder.txt.setText(data + "");
}
}
public void notifyDataSetChanged(final int position) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
final int firstVisiablePosition = mListView.getFirstVisiblePosition();
final int lastVisiablePosition = mListView.getLastVisiblePosition();
final int relativePosition = position - firstVisiablePosition;
if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
if (mScrollState == SCROLL_STATE_IDLE) {
//当前不在滚动,立刻刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");
updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
} else {
synchronized (mPendingNotify) {
//当前正在滚动,等滚动停止再刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");
mPendingNotify.add(this);
}
}
} else {
//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip");
}
}
};
runnable.run();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mScrollState = scrollState;
if (mScrollState == SCROLL_STATE_IDLE) {
//滚动已停止,把需要刷新的listitem都刷新一下
synchronized (mPendingNotify) {
final Iterator<Runnable> iter = mPendingNotify.iterator();
while (iter.hasNext()) {
iter.next().run();
iter.remove();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
}

以上所述是针对Listview加载的性能优化是如何实现的全部叙述,希望对大家有所帮助。

(0)

相关推荐

  • JavaScript中的无阻塞加载性能优化方案

    Javascript在浏览器中的性能,可以说是前端开发者所要面对的最重要的可用性问题. 在Yahoo的Yslow23条规则当中,其中一条是将JS放在底部 .原因是,事实上,大多数浏览器使用单进程处理UI和更新Javascript运行等多个任务,而同一时间只能有一个任务被执行.Javascript运行了多长时间,那么在浏览器空闲下来响应用户交互之前的等待时间就有多长. 从基本层面说,这意味着<script>标签的出现使整个页面因脚本解析.运行而出现等待.不论实际的 JavaScript 代码是内

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

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

  • js性能优化 如何更快速加载你的JavaScript页面

    确保代码尽量简洁 不要什么都依赖JavaScript.不要编写重复性的脚本.要把JavaScript当作糖果工具,只是起到美化作用.别给你的网站添加大量的JavaScript代码.只有必要的时候用一下.只有确实能改善用户体验的时候用一下. 尽量减少DOM访问 使用JavaScript访问DOM元素很容易,代码更容易阅读,但是速度很慢.下面介绍几个要点:限制使用JavaScript来修饰网页布局,把针对访问元素的引用缓存起来.有时,当你的网站依赖大量的DOM改动时,就应该考虑限制你的标记.这是改用

  • JavaScript性能优化总结之加载与执行

    前言 无论当前 JavaScript 代码是内嵌还是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成.JavaScript 执行过程耗时越久,浏览器等待响应用户输入的时间就越长.浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或 JavaScript 的命名空间,它们对后面页面内容造成影响.一个典型的例子就是在页面中使用document.write(),例如清单 1 清单 1 JavaScript 代码内嵌示例 <html> <head> <title

  • 详解Android_性能优化之ViewPager加载成百上千高清大图oom解决方案

    一.背景 最近做项目需要用到选择图片上传,类似于微信.微博那样的图片选择器,ContentResolver读取本地图片资源并用RecyclerView+Glide加载图片显示就搞定列表的显示,这个没什么大问题,重点是,点击图片进入大图浏览,比如你相册有几百张图片,也就意味着在ViewPager中需要加载几百个view,况且手机拍出来的图片都是1-2千万左右像素的高清大图(笔者手机2千万像素 也就是拍照出来的照片3888*5152),大小也有5-7个兆,ViewPager滑动不了十几张就oom了,

  • 性能优化之代码优化页面加载速度

    页面代码的优化对于页面加载速度也有不小的影响,最广为人知的: HTML头部的JavaScript和写在HTML标签中的Style会阻塞页面的渲染,因此CSS放在页面头部并使用Link方式引入,JavaScript的引入放在页面尾. 其次还有: 1.按需加载,把统计.分享等 JS 在页面 onload 后再进行加载,可以提高访问速度: 2.优化 Cookie ,减少 Cookie 体积: 3.避免 []() 的 src 为空: 4.尽量避免设置图片大小,多次重设图片大小会引发图片的多次重绘,影响性

  • Listview的异步加载性能优化

    Android中ListView是使用平率最高的控件之一(GridView跟ListView是兄弟,都是继承AbsListView),ListView优化最有效的无非就是采用ViewHolder来减少频繁的对view查询和更新,缓存图片加快解码,减小图片尺寸. 关于listview的异步加载,网上其实很多示例了,中心思想都差不多,不过很多版本或是有bug,或是有性能问题有待优化,下面就让在下阐述其原理以探索个中奥秘在APP应用中,listview的异步加载图片方式能够带来很好的用户体验,同时也是

  • jQuery按需加载轮播图(web前端性能优化)

    引言 关于幻灯轮播图,想必大家都不陌生,尤其是基于 jQuery 的,插件.代码网上一搜一大堆,但是真正符合自己需求的几乎没有,所以我要打造一个符合自身需求,经得起广大网民考验的 jQuery 轮播图! 思路 为什么说网上其他一些轮播图不符合我的要求?我的需求又是什么呢? 现在网上可以找到的多数幻灯轮播图的 jQuery 插件的作法是,先把图片和链接的 HTML 写好,然后控制隐藏和显示来轮流展示当前的幻灯图片.但是对用户而言,我们始终只是看到当前的一张图片,那其他几张隐藏的图片为什么要事先加载

  • Listview加载的性能优化是如何实现的

    在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建. listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次: 0.最原始的加载 1.利用convertView 2.利用ViewHolder 3.实现

  • easyui datagrid 大数据加载效率慢,优化解决方法(推荐)

    在使用easyui datagrid途中发现加载数据的效率真的不是一般的差.经测试IE8加载300条数据就感觉明显的慢了,加载2000条数据就另人崩溃用时差不多60秒,就算在google浏览器测试结果也快不了几秒. 平时听闻easyui datagrid效率底下,自己测试才发现真是使人无法忍受. 笔者只好百度,google解决方法,发现一篇文章说改 //1.3.3版本是这样的,其它版本也是这句代码 $(_1e0).html(_1e4.join("")); 改为: $(_1e0)[0].

  • IOS开发中加载大量网络图片优化方法

    IOS开发中加载大量网络图片如何优化 1.概述 在IOS下通过URL读一张网络图片并不像其他编程语言那样可以直接把图片路径放到图片路径的位置就ok,而是需要我们通过一段类似流的方式去加载网络图片,接着才能把图片放入图片路径显示.比如: -(UIImage *) getImageFromURL:(NSString *)fileURL { //NSLog(@"执行图片下载函数"); UIImage * result; NSData * data = [NSData dataWithCont

  • 页面中CSS加载方式的优化

    1.应该将 CSS 放置于结构的上方(一般放置于 head 元素内).CSS 是解释型语言,Firefox 和 IE 在等待 CSS 传输完成之前不会渲染任何东西.只有将 CSS 前置,才可在浏览器解析结构时,对页面进行渲染. This causes the blank white screen problem. The page is totally blank until the stylesheet at the bottom is downloaded, on the order of 

  • vue项目中一定会用到的性能优化技巧

    目录 引言 性能优化标准 Lighthouse 通用常规优化手段 通用性能优化分析 FCP(First Contentful Paint) LCP(Largest Contentful Paint) SpeedIndex(速度指数) 排查性能瓶颈 分析包内容 利用chorme devtool 的代码覆盖率 针对vue 的特殊优化 图片懒加载 虚拟滚动 vue 中的函数式组件 利用v-show .KeepAlive 复用dom 分批渲染组件 最后 引言 提起性能优化 很多人眼前浮现的面试经验是不是

  • Android性能优化之RecyclerView分页加载组件功能详解

    目录 引言 1 分页加载组件 1.1 功能定制 1.2 手写分页列表 1.3 生命周期管理 2 github 引言 在Android应用中,列表有着举足轻重的地位,几乎所有的应用都有列表的身影,但是对于列表的交互体验一直是一个大问题.在性能比较好的设备上,列表滑动几乎看不出任何卡顿,但是放在低端机上,卡顿会比较明显,而且列表中经常会伴随图片的加载,卡顿会更加严重,因此本章从手写分页加载组件入手,并对列表卡顿做出对应的优化 1 分页加载组件 为什么要分页加载,通常列表数据存储在服务端会超过100条

  • ListView异步加载图片实现思路

    在应用开发中,经常用到ListView去加载数据,加载图片和文字是比较常见的,文字还好,图片从网络请求加载速度比较慢,所以需要把图片的加载放到另一个线程中去执行,执行完了再更新UI线程.以下列出一个我在项目中使用到的异步加载图片的解决方案,代码没有上全,给出核心部分. 大致思路是这样: 1.利用软引用来缓存图片Bitmap,用图片的URL作为缓存查找的Key: 2.设两级缓存,一级是SoftReference,二级是本地SD卡: 3.如果两级缓存都没取到图片,则从服务器获取,并加入缓存: 4.加

  • iOS中的UITableView的重用机制与加载优化详解

    UITableView可以说是UIKit中最重要的一个组件,用来展示数据列表,还可以灵活使用进行页面的布局.UITableView的使用遵循MVC模式,数据模型(NSObject).视图(UIView)和控制器(UITableViewController)分离.UITableView继承自UIScrollView,可上下滑动,可以作为跟视图也可以作为子视图组件. reuseIdentifier顾名思义是一个复用标识符,是一个自定义的独一无二的字符串,用来唯一地标记某种重复样式的可复用UITabl

  • 几个优化WordPress中JavaScript加载体验的插件介绍

    WordPress 本身以及主题和插件通常需要加载一些 JavaScript 来实现某些特殊功能.为了最大限度地保证兼容性,不至于出现 JavaScript 失效的情况,所以一般在页头加载 JavaScript 文件.但是根据 Yahoo 开发者论坛的建议,加载 JavaScript 应该尽量在页尾以提高页面的显示(响应.渲染)速度.本文根据作者的使用经验介绍几个相关插件,并说明如何在某些特殊页面仍然在页头加载 JavaScript. 下面先简单介绍几个相关的优化 JavaScript 的 Wo

  • Android中ListView分页加载数据功能实现

    熟悉Android的朋友们都知道,不管是微博客户端还是新闻客户端,都离不开列表组件,可以说列表组件是Android数据展现方面最重要的组件,我们今天就要讲一讲列表组件ListView加载数据的相关内容.通常来说,一个应用在展现大量数据时,不会将全部的可用数据都呈现给用户,因为这不管对于服务端还是客户端来说都是不小的压力,因此,很多应用都是采用分批次加载的形式来获取用户所需的数据.比如:微博客户端可能会在用户滑动至列表底端时自动加载下一页数据,也可能在底部放置一个"加载更多"按钮,用户点

随机推荐