与所谓“战斗民族”一团队合作开发一项目,最后收尾阶段开始优化,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:33
5. 5: * @author Hal
6. 6: */
7 . 7 : public class AudioListAdapter extends BaseAdapter {
8 . 8 :
9 . 9 : private Context mContext;
10 . 10 :
11 . 11 : // 歌曲集合
12 . 12 : private ArrayList < Audio > mAudios;
13 . 13 :
14 . 14 : public AudioListAdapter(Context mContext, ArrayList < Audio > mAudios) {
15 . 15 : this .mContext = mContext;
16 . 16 : this .mAudios = mAudios;
17 . 17 : }
18 . 18 :
19 . 19 : @Override
20 . 20 : public int getCount() {
21 . 21 : return mAudios != null ? mAudios.size() : 0 ;
22 . 22 : }
23 . 23 :
24 . 24 : @Override
25 . 25 : public Object getItem( int position) {
26 . 26 : if ((mAudios != null && mAudios.size() > 0 ) && (position >= 0 && position < mAudios.size())) {
27 . 27 : return mAudios.get(position);
28 . 28 : }
29 . 29 : return null ;
30 . 30 : }
31 . 31 :
32 . 32 : /**
33. 33: * 如果集合中的对象数据来自数据库,建议此方法返回该对象在数据库中的ID
34. 34: */
35 . 35 : @Override
36 . 36 : public long getItemId( int position) {
37 . 37 : if ((mAudios != null && mAudios.size() > 0 ) && (position >= 0 && position < mAudios.size())) {
38 . 38 : return mAudios.get(position).getId();
39 . 39 : }
40 . 40 : return position;
41 . 41 : }
42 . 42 :
43 . 43 : @Override
44 . 44 : public View getView( int position, View convertView, ViewGroup parent) {
45 . 45 : // TODO 返回自定的View
46 . 46 : }
Adapter与View的连接主要依靠getView这个方法返回我们需要的自定义view。ListView是Android app中一个最最最常用的控件了,所以如何让ListView流畅运行,获取良好的用户体验是非常重要的。对ListView优化就是对Adapter中的getView方法进行优化。09年的Google IO大会给出的优化建议如下:
Adapter优化示例代码:
1 . @Override
2 . 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) mContext
8 . .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 . @Override
2 . 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) mContext
6 . .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 --- 1291517086043
2 . 12 - 05 10 : 44 : 46.069 : DEBUG / MyAdapter( 13929 ): Position: 1 --- 1291517086072
3 . 12 - 05 10 : 44 : 46.079 : DEBUG / MyAdapter( 13929 ): Position: 2 --- 1291517086085
4 .
5 . ……
6 .
7 . 12 - 05 10 : 45 : 04.109 : DEBUG / MyAdapter( 13929 ): Position: 997 --- 1291517104112
8 . 12 - 05 10 : 45 : 04.129 : DEBUG / MyAdapter( 13929 ): Position: 998 --- 1291517104135
9 . 12 - 05 10 : 45 : 04.149 : DEBUG / MyAdapter( 13929 ): Position: 999 --- 1291517104154
10 .
11 . 耗时: 17967
12 .
2、没优化的测试结果
1 . 12 - 05 10 : 51 : 42.569 : DEBUG / MyAdapter( 14131 ): Position: 0 --- 1291517502573
2 . 12 - 05 10 : 51 : 42.589 : DEBUG / MyAdapter( 14131 ): Position: 1 --- 1291517502590
3 . 12 - 05 10 : 51 : 42.609 : DEBUG / MyAdapter( 14131 ): Position: 2 --- 1291517502617
4 .
5 . ……
6 .
7 . 12 - 05 10 : 52 : 07.079 : DEBUG / MyAdapter( 14131 ): Position: 998 --- 1291517527082
8 . 12 - 05 10 : 52 : 07.099 : DEBUG / MyAdapter( 14131 ): Position: 999 --- 1291517527108
9 .
10 . 耗时: 24535
11 .
在1000条记录的情况下就有如此差距,一旦数据nW+,ListView的Item布局更加复杂的时候,优化的作用就更加突出了!
------------------------------
ListView作为Android开发中使用频率最高的一个控件,保证ListView的流畅运行,对用户体验的提高至关重要。Adapter是ListView和数据源之间的中间人,当每条数据进入可见区时,Adapter 的 getView() 会被调用,返回代表具体数据的视图,在成百上千条数据触摸滚动时频繁调用,因此如何优化Adapter是提高ListView性能的关键。
1. 使用ViewHolder模式,重复利用convertView,减少频繁查找
在2009年 Google IO开发者大会中已做说明,看一下使用不同实现方式之间的差距:
Adapter 显示每条数据的 XML 布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal"> <ImageView android:id="@+id/icon" android:layout_width="48dip" android:layout_height="48dip" /> <TextView android:id="@+id/text" android:layout_gravity="center_vertical" android:layout_width="0dip" android:layout_weight="1.0" android:layout_height="wrap_content" /> </LinearLayout>
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<ViewHolder, Void, Bitmap>() {
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 工具可以在<sdk>/tools/中找到。当打开给工具后,就会显示可用的设备列表一个这些设备中运行的部分。点击“Load View Hierchy”选项查看被选中部分的布局层次图。比如,图2显示了图1中布局结构图。
图2. 图1的布局层次结构图,使用内嵌的LinearLayout实例布局。
图3. 点击层次图中一个节点,显示它的运行时间
图2中,你可以看到一个3层的布局结构图,并且在布局text的items里面有一些问题。点击这些items显示进程中每个阶段所花费的时间。它显示的很清楚,哪些items在测试,布局中花费时间最长,哪些地方需要花费时间去优化。
使用该布局加载所有item所花费的时间如下:
-
Measure: 0.977ms
-
Layout: 0.167ms
-
Draw: 2.717ms
修改布局
因为上述布局性能较低的原因主要是由一个内嵌的LinearLayout所引起,将该布局使用浅而广的扁平化结构代替深而窄的树形结构化设计,从而提高性能。在这些布局中,将RelativeLayout作为一个根节点,这样,你将会看到该布局变为一个2层的结构,修改后的布局如下:
图4. 使用RelativeLayout的图1的布局。
修改后加载item所花费的时间:
-
Measure: 0.598ms
-
Layout: 0.110ms
-
Draw: 2.146ms
虽然看起来提高度很小,但是这布局提高是被重复操作的,因此,这个布局是在listview中的每一个item里面。
更多情况的一个时间差异,是在使用了layout_weight属性的LinearLayout设计里面,这样的设计会降低测量的速度。这只是一个示例说明每个布局是否被适当的使用,在使用layout weight属性时候,你应该谨慎考虑是否必要。
使用Lint
这是一个好习惯,在你的布局文件内运行Lint工具,寻找那些可能要优化的布局结构。Lint工具代替Layoutopt工具,并且有更大的功能。如下是Lint的一些示例:
- 使用复杂的图片:在LineraLayout布局中包含一个ImageView和一个TextView,可以使用一个复杂的drawable代替,性能会更好。
- 合并根框架:假如一个FrameLayout作为一个布局的根视图,不提供背景或者填充,它可以被一个带有<merge/>标志的布局代替。
- 无用的树叶:对于一个扁平结构中一个布局没有孩子,没有背景,可以被删掉。
- 无用的父类:一个布局不是ScrollView或者不是一个根布局,也没有背景,只有一个孩子节点,可以被删掉,孩子节点直接放入到这个扁平的父类里面。
- 深度布局:布局若有太多内嵌,则性能很差。考虑使用RelativeLayout 以及GridLayout等扁平化布局代替。默认布局最大深度是10.
使用Lint另一个好处是,它被内嵌到ADT16+.当你在导入apk,编辑或者保存一个xml文件,Lint都会自动运行。点击Eclipse工具栏中Lint按钮,会人为强制运行Lint.
在Eclipse内使用Lint,它能自动修复一些问题,为问题提供修改建议,直接掉转到问题代码位置。如果你不是用Eclipse开发,也可以使用命令行启动Lint。更多信息请参照tools.android.com.
参考:
http://kb.cnblogs.com/page/84589/
http://developer.android.com/training/improving-layouts/optimizing-layout.htmlhttp://www.cnblogs.com/purediy/p/3267913.html