与所谓“战斗民族”一团队合作开发一项目,最后收尾阶段开始优化,app真特么慢,先重adapter开始
Android在UI优化方面可以从以下五个方面入手:
◆Adapter优化
◆背景和图片优化
◆绘图优化
◆视图和布局优化
◆内存分配优化
Adapter优化
什么是Adapter?
Adapter在Android中占据一个重要的角色,它是数据和UI(View)之间一个重要的纽带。在常见的View(ListView,GridView)等地方都需要用到Adapter。如图1直观的表达了Data、Adapter、View三者的关系。
图1 Adapter、数据、UI三者关系
一、Android中Adapter
图2:Android中Adapter类型层级图
由图2我们可以看到在Android中与Adapter有关的所有接口、类的完整层级图。在我们使用过程中可以根据自己的需求实现接口或者继承类进行一定的扩展。比较常用的有 BaseAdapter,ArrayAdapter,SimpleCursorAdapter等。
BaseAdapter是一个抽象类,继承它需要实现较多的方法,所以也就具有较高的灵活性;
ArrayAdapter支持泛型操作,通常需要实现getView方法,特殊情况下(结合数据row id),为了让ui事件相应处理方便点最好重写getItemId;
SimpleCursorAdapter可以适用于简单的纯文字型ListView,它需要Cursor的字段和UI的id对应起来。如需要实现更复杂的UI也可以重写其他方法。
二、一个继承BaseAdapter的类的代码段
1.1: /**2.2:* 歌曲列表适配器3.3:*4.4:* @version 2010-11-24 下午05:13:335.5:* @author Hal6.6:*/7.7: public class AudioListAdapter extends BaseAdapter {8.8:9.9:private Context mContext;10. 10:11. 11:// 歌曲集合12. 12:private ArrayList mAudios;13. 13:14. 14:public AudioListAdapter(Context mContext, ArrayList mAudios) {15. 15:this.mContext = mContext;16. 16:this.mAudios = mAudios;17. 17:}18. 18:19. 19:@Override20. 20:public int getCount() {21. 21:return mAudios != null ? mAudios.size() : 0;22. 22:}23. 23:24. 24:@Override25. 25:public Object getItem(int position) {26. 26:if ((mAudios != null && mAudios.size() > 0) && (position >= 0 && position27. 27:return mAudios.get(position);28. 28:}29. 29:return null;30. 30:}31. 31:32. 32:/**33. 33:* 如果集合中的对象数据来自数据库,建议此方法返回该对象在数据库中的ID34. 34:*/35. 35:@Override36. 36:public long getItemId(int position) {37. 37:if ((mAudios != null && mAudios.size() > 0) && (position >= 0 && position38. 38:return mAudios.get(position).getId();39. 39:}40. 40:return position;41. 41:}42. 42:43. 43:@Override44. 44:public View getView(int position, View convertView, ViewGroup parent) {45. 45://TODO 返回自定的View46. 46:}
Adapter与View的连接主要依靠getView这个方法返回我们需要的自定义view。ListView是Android app中一个最最最常用的控件了,所以如何让ListView流畅运行,获取良好的用户体验是非常重要的。对ListView优化就是对Adapter中的getView方法进行优化。09年的Google IO大会给出的优化建议如下:
Adapter优化示例代码:
1. @Override2. public View getView(int position, View convertView, ViewGroup parent) {3.Log.d("MyAdapter", "Position:" + position + "---"4.+ String.valueOf(System.currentTimeMillis()));5.ViewHolder holder;6.if (convertView == null) {7.final LayoutInflater inflater = (LayoutInflater) mContext8..getSystemService(Context.LAYOUT_INFLATER_SERVICE);9.convertView = inflater.inflate(R.layout.list_item_icon_text, ull);10.holder = new ViewHolder();11.holder.icon = (ImageView) convertView.findViewById(R.id.icon);12.holder.text = (TextView) convertView.findViewById(R.id.text);13.convertView.setTag(holder);14.} else {15.holder = (ViewHolder) convertView.getTag();16.}17.holder.icon.setImageResource(R.drawable.icon);18.holder.text.setText(mData[position]);19.return convertView;20. }21.22. static class ViewHolder {23.ImageView icon;24.25.TextView text;
以上是Google io大会上给出的优化建议,经过尝试ListView确实流畅了许多。
1. @Override2. public View getView(int position, View convertView, ViewGroup parent) {3.Log.d("MyAdapter", "Position:" + position + "---"4.+ String.valueOf(System.currentTimeMillis()));5.final LayoutInflater inflater = (LayoutInflater) mContext6..getSystemService(Context.LAYOUT_INFLATER_SERVICE);7.View v = inflater.inflate(R.layout.list_item_icon_text, null);8.((ImageView) v.findViewById(R.id.icon)).setImageResource(R.drawable.icon);9.((TextView) v.findViewById(R.id.text)).setText(mData[position]);10.return v;11.}
以上是不建议的做法!!
不过我们还是要怀疑一下,SO,我们还是来测试对比一下。
测试说明:
大家可以看到在getView的时候我们通过log打印出position和当前系统时间。我们通过初始化1000条数据到Adapter显示到ListView,然后滚动到底部,计算出position=0和position=999时的时间间隔。
测试机子:HTC Magic
测试实录:打开测序,让ListView一直滚动底部。
测试结果:
两种情况在操作过程中体验明显不同,在优化的情况下流畅很多很多!
1、优化建议测试结果
1. 12-05 10:44:46.039: DEBUG/MyAdapter(13929): Position:0---12915170860432. 12-05 10:44:46.069: DEBUG/MyAdapter(13929): Position:1---12915170860723. 12-05 10:44:46.079: DEBUG/MyAdapter(13929): Position:2---12915170860854.5. ……6.7. 12-05 10:45:04.109: DEBUG/MyAdapter(13929): Position:997---12915171041128. 12-05 10:45:04.129: DEBUG/MyAdapter(13929): Position:998---12915171041359. 12-05 10:45:04.149: DEBUG/MyAdapter(13929): Position:999---129151710415410.11. 耗时:1796712.
2、没优化的测试结果
1. 12-05 10:51:42.569: DEBUG/MyAdapter(14131): Position:0---12915175025732. 12-05 10:51:42.589: DEBUG/MyAdapter(14131): Position:1---12915175025903. 12-05 10:51:42.609: DEBUG/MyAdapter(14131): Position:2---12915175026174.5. ……6.7. 12-05 10:52:07.079: DEBUG/MyAdapter(14131): Position:998---12915175270828. 12-05 10:52:07.099: DEBUG/MyAdapter(14131): Position:999---12915175271089.10. 耗时:2453511.
在1000条记录的情况下就有如此差距,一旦数据nW+,ListView的Item布局更加复杂的时候,优化的作用就更加突出了!
------------------------------
ListView作为Android开发中使用频率最高的一个控件,保证ListView的流畅运行,对用户体验的提高至关重要。Adapter是ListView和数据源之间的中间人,当每条数据进入可见区时,Adapter 的 getView() 会被调用,返回代表具体数据的视图,在成百上千条数据触摸滚动时频繁调用,因此如何优化Adapter是提高ListView性能的关键。
1. 使用ViewHolder模式,重复利用convertView,减少频繁查找
在2009年 Google IO开发者大会中已做说明,看一下使用不同实现方式之间的差距:
Adapter 显示每条数据的 XML 布局文件如下:
1. 最慢最不实用的方式
public View getView(int position, View convertView, ViewGroup parent) {
View item = mInflater.inflate(R.layout.list_item_icon_text, null);
((TextView) item.findViewById(R.id.text)).setText(DATA[position]);
((ImageView) item.findViewById(R.id.icon)).setImageBitmap(
(position & 1) == 1 ? mIcon1 : mIcon2);
return item;
}
2. 使用 convertView 回收视图, 效率提高 200%
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item, null);
}
((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]);
((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap(
(position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
3. 使用 ViewHolder 模式, 效率再提高 50%
static class ViewHolder {
TextView text;
ImageView icon;
}
public View getView(int pos, View convertView, ViewGroup parent){
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text));
holder.icon = (ImageView) convertView.findViewButId(R.id.icon));
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[pos]);
holder.icon.setImageBitmap((pos & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
更新率比较如下图:
2. 使用工作线程加载数据,减轻UI主线程负担,使UI主线程只专注于UI绘制
// Using an AsyncTask to load the slow images in a background thread
new AsyncTask() {
private ViewHolder v;
@Override
protected Bitmap doInBackground(ViewHolder... params) {
v = params[0];
return mFakeImageLoader.getImage();
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (v.position == position) {
// If this item hasn't been recycled already, hide the
// progress and set and show the image
v.progress.setVisibility(View.GONE);
v.icon.setVisibility(View.VISIBLE);
v.icon.setImageBitmap(result);
}
}
}.execute(holder);
3. 优化item布局,尽量优化子view布局不被过渡重绘,每一点子view的优化都能提高整体的性能
优化布局层次结构
一个普遍的误解就是,使用基本的布局结构会产生高效的布局性能。然而每一个添加到应用的控件和布局,都需要初始化,布局位置和绘制。比如,使用一个嵌套的LinearLayout会导致过深的布局层次结构。此外,嵌套多个使用layout_weight属性的LinearLayout实例会花费更大的代价,因为每一个子布局都要测量两次。当某个布局被频繁渲染时,比如它在ListView或GridView中使用,就显得尤为重要。
在这节课中,将学会使用Hierachy Viewer和Layoutopt工具对布局结构进行检测和优化。
检测你的布局
在Android SDK tools中包含一个叫做HierchyViewer工具,它可以在你运行应用时候帮助你分析你的布局性能。通过它你可以发现你的布局中性能比较差的那些地方。
HierchyViewer需要你选择一个已链接的设备或者模拟器中的一个运行的线程,显示出布局的树结构。每个块上的红绿灯代表它的测量,布局,以及绘图性能,帮助你找出潜在的问题。
比如,图1显示了一个用于ListView中的Item的布局。这个布局的左边显示了一幅图片,两个叠在一起的文字item放在右边。那些被重复加载的布局在优化时候显得有为重要。
图1. 一个ListView内item的概念设计
hierchyviewer 工具可以在/tools/中找到。当打开给工具后,就会显示可用的设备列表一个这些设备中运行的部分。点击“Load View Hierchy”选项查看被选中部分的布局层次图。比如,图2显示了图1中布局结构图。
图2. 图1的布局层次结构图,使用内嵌的LinearLayout实例布局。