看到一篇关于不错的文章,简单翻译了下。原文地址:http://leftshift.io/6-ways-to-make-your-lists-scroll-faster-than-the-wind
快乐与悲伤共存。开发一款App总是很有趣,然而比较伤心的是拿着一款旧的三星测试机的你,得到一款新的MotoX却发现你的App在上面运行的总是不流畅。第一眼看到App如此卡顿,我真的流泪了。此时尽管夜已深,我依然决定找出fix的办法。
以下是我找出一些条目:
1>:在Adapter中getView方法里面,减少条件判断;
2>:减少GC操作;
3>:避免在滑动时加载图片;
4>:将scrollingCache跟animateCache设置为false;
5>:简化你的list View布局层次;
6>:使用ViewHolder模式;
如果这些你全都熟悉,可能你没有必要再读下文了。不管怎样,如果还不是完全了解,请接着往下看。(这里给出一些建议。这里给出的方法唯一的解决方案。有些时候它能够起作用,有时需要你尝试其它方案。这些都取决于你的代码。写这些方案的目的是帮助你的App更流畅)
1:在Adapter中getView方法里面,减少条件判断
(在你的Adapter中,可能需要清理某些状态信息。每次当你的屏幕上需要显示一项时这个方法都会被调用。因此这里定义的结构集,在每次App运行时都会进行大量调用。因此此处减少条件判断比较有意义。现在问题是不能在这里做,在哪儿又比较合适呢?答案很简单,如果你正在使用一个可序列化的类来获取其值,你只需要将条件判断移动到类里面,将值存在里面即可。通过这种方式,条件判断仅仅只需执行一次,下面是一个简单的例子)
这里是处理冗余之前的View代码(这里有很复杂的条件判断,我们移除了条件判断,使其更便于理解)
@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
Object current_event = mObjects.get(position);
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.row_event, null);
holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// If you have conditions like below then you may face problem
if (doesSomeComplexChecking()) {
holder.ThreeDimention.setVisibility(View.VISIBLE);
} else {
holder.ThreeDimention.setVisibility(View.GONE);
}
// This method is setting layout parameters each time when getView is getting called which is wrong.
RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
holder.EventPoster.setLayoutParams(imageParams);
return convertView;
}
下面是更新后的getView代码:
@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
Object current_event = mObjects.get(position);
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.row_event, null);
holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
// This will now execute only for the first time of each row
RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
holder.EventPoster.setLayoutParams(imageParams);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// This way you can simply avoid conditions
holder.ThreeDimension.setVisibility(object.getVisibility());
return convertView;
}
2:检查和减少你logs中GC的警告
当大量的创建跟销毁对象时,GC将频繁的出现。因此一般情况在getView中不创建大量的对象;更好的建议是-除了ViewHolder不创建任何对象。如果在Log中频繁出现“GC已经释放了内存”,那说明你的代码中存在严重的问题,你有必要按照下面的方法检查下:
1>:列表中每项的布局层次
2>:getView中的条件判断是否较多
3>:每项View的属性
3:避免在滑动的时候加载图片
当用户快速滑动的时候,应用程序正在下载图片,此时应该停止加载图片,保证滑动的流畅性;当滑动停止时再加载图片。这种方式的效果很明显。
同时,Google也给出了示例代码,参考https://code.google.com/p/iosched/
对于那些讨厌打开连接并下载代码的人,下面是一小段代码,代码说明了如何添加一个scroll listener来stop跟start加载图片队列
<span style="font-size:18px;">listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView listView, int scrollState) {
// Pause disk cache access to ensure smoother scrolling
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
imageLoader.stopProcessingQueue();
} else {
imageLoader.startProcessingQueue();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});</span>
ListView有很多的属性,以下是一些详细的说明,参考:http://stackoverflow.com/questions/15570041/scrollingcache
scrolling cache是一个基本的drawing cache
在Android中,你可以请求一个View将正在绘制的缓存存储在drawing cache中(比如Bitmap)。通常,drawing cache不可用,因为它会占用内存;但是你也可以通过setDrawingCacheEnabled或者hardware layers(setLayerType)来请求创建一个drawing cache。
为什么这个比较有用?因为在View框架中,使用drawing cache能够让你的View得以比较平滑的绘制。
这类动画也能够被硬件加速,因为渲染系统能够获取该Bitmap并且作为texture(如果使用了硬件层)传递给GPU,能够做更快的矩阵操作(比如:改变alpha值、旋转、移动)。
在ListView中,当你快速滑动时,其实本质上是对ListView中View做动画效果(不管是向上还是向下移动),ListView对可见的子View(靠近边缘的可见子View)使用drawing cache使其能够更快的进行动画操作。
使用drawing cache有什么缺点吗?答案是的,由于使用drawing cache需要消耗内存,这就是为什么默认是关闭的原因。对于ListView来说,只要你Touch 或者移动一点点(跟滑动做区别),缓存就自动创建了。换句话说,当ListView认为你要滑动 or 快速滑动时,它将为你创建一个scrolling cache来确保更快的scroll or fling 动画移动。
同样,有时AnimationCache也有不好的地方(个人认为它导致了频繁的GC)
对于子View,布局动画应该创建一个drawing cache。尽管动画缓存会占用更多的内存且需要更长初始化,但是它提供了更好的性能。动画缓存默认可用。动画缓存参考Google文档。
这里是我们将要在ListView中修改的。原始代码如下:
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/list_background_color"
android:dividerHeight="0dp"
android:listSelector="#00000000"
android:smoothScrollbar="true" />
下面是修改后的代码:
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/list_background_color"
android:dividerHeight="0dp"
android:listSelector="#00000000"
android:smoothScrollbar="true"
android:scrollingCache="false"
android:animationCache="false" />
5:简化LIstView每项布局的层次
你需要保证列表view布局尽可能的小。布局的层级跟深度直接跟view的测量、绘制相关(可以使用Hierarchy Viewer工具查看)。后续博客可能会继续讨论该知识点。
6:使用view holder模式
对于初级开发者,这是一个经常犯的错误。第一次你的App表现正常,当快速滑动时,你的App将crash,出现这个问题的原因是因为,当你快速滑动时,其View每次都需要inflate,导致性能降低。正确的做法是使用VIew Holder模式,将View存储起来。具体参考文章1,文章2。
如果有其它技巧,欢迎补充。谢谢~~~