您的位置:首页 > 移动开发 > Android开发

Android 用户界面编程技巧和设计模式(性能优化)

2014-02-18 23:24 381 查看
1. 如何使用 Adapter 2. 背景和图像 3. 更新请求 4. 视图和布局 5. 内存分配

Adapter

Adapter 是 ListView 和数据源之间的中间人

----------------------- Page 1-----------------------

当每条数据进入可见区时

Adapter 的 getView() 会被调用

返回代表具体数据的视图

触摸滚动时, 频繁调用

支持成百上千条数据

----------------------- Page 1-----------------------

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>

----------------------- Page 1-----------------------

Adapter 最简单的方法, 最慢最不实用

public View getView (int pos, View convertView, ViewGroup parent){

View item = mInflater.inflate(R.layout.list_item, null);

((TextView) item.findViewById(R.id.text)). setText(DATA[pos]);

((ImageView) item.findViewButId(R.id.icon)). setImageBitmap((pos & 1) == 1 ? mIcon1 : mIcon2);

return item;

}

----------------------- Page 1-----------------------

Adapter 利用 convertView 回收视图, 效率提高 200%

public View getView (int pos, View convertView, ViewGroup parent){

if (convertView null) {

convertView = mInflater.inflate( R.layout.list_item, null);

}

((TextView) convertView.findViewById(R.id.text)). setText(DATA[pos]);

((ImageView) convertView.findViewButId(R.id.icon)). setImageBitmap((pos & 1) == 1 ? mIcon1 : mIcon2);

return convertView;

}

----------------------- Page 1-----------------------

Adapter 使用 ViewHolder 模式, 效率再提高 50%

static class ViewHolder {

TextView text; ImageView icon;

}

----------------------- Page 1-----------------------

Adapter 使用 ViewHolder 模式, 效率再提高 50%

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;

}

----------------------- Page 1-----------------------

Adapter 更新率比较

----------------------- Page 2--------------------—

背景和图像

----------------------- Page 2-----------------------

背景和图像

选择恰当的图像尺寸

视图背景图像总会填充整个视图区域

图像尺寸不合适会导致自动缩放

避免实时缩放 最好预先缩放到视图大小

originalImage = Bitmap.createScaledBitmap(

originalImage, // 被缩放图像

view.getWidth(), // 视图宽度

view.getHeight(), // 视图高度

true); // 双线性过滤器

----------------------- Page 2-----------------------

背景和图像 更新率比较

----------------------- Page 2-----------------------

背景和图像

窗口背景 默认情况下, 窗口有一个不透明的背景

有时可以不需要

最高层的视图是不透明的

最高层的视图覆盖整个窗口

layout_width = fill_parent layout_height = fill_parent

更新看不见的背景是浪费时间

----------------------- Page 2-----------------------

背景和图像 删除窗口背景 方法一:修改编码

@Override public void onCreate(Bundle icicle){ super.onCreate(icicle);

setContentView(R.layout.mainview); // 删除窗口背景

getWindow().setBackgroundDrawable(null);

... }

----------------------- Page 2-----------------------

背景和图像

删除窗口背景

方法二:修改 XML 声明

首先确定你的 res/values/styles.xml 有

<resources>

<style name="NoBackgroundTheme" parent="android:Theme">

<item name="android:windowBackground">@null</item>

</style>

</resources>

然后编辑 AndroidManifest.xml <activity android:name="MyApplication" android:theme="@style/NoBackgroundTheme"> ... </activity>

----------------------- Page 2-----------------------

背景和图像 更新率比较

----------------------- Page 3-----------------------

更新请求

----------------------- Page 3-----------------------

更新请求

当屏幕需要更新时, 调用 invalidate()

简单方便

但会更新整个视图, 太贵了

最好先找到无效区域, 然后调用

invalidate(Rect dirty);

invalidate(int left, int top, int right, int bottom);

----------------------- Page 3-----------------------

更新请求

应用实例:在屏幕上触摸移动小图标

最简单的编码在每次响应移动事件时调用 invalidate()

----------------------- Page 3-----------------------

更新请求 应用实例:在屏幕上触摸移动小图标 更有效的执行方法是只更新需要更新的区域

----------------------- Page 3-----------------------

更新请求 更新率比较

----------------------- Page 4----------------------

视图和布局

----------------------- Page 4-----------------------

视图和布局

越简单越好

如果一个窗口包含很多视图

启动时间长

测量时间长

布局时间长

绘制时间长

如果视图树深度太深

StackOverflowException

用户界面反应速度很慢

----------------------- Page 4-----------------------

视图和布局

解决方法

使用 TextView 的复合 drawables 减少层次

使用 Viewtub 延迟展开视图 使用 <merge> 合并中间视图

使用 elativeLayout 减少层次

使用自定义视图

使用自定义布局

----------------------- Page 4-----------------------

视图和布局 使用 TextView 的复合 drawables 减少层次

----------------------- Page 4-----------------------

视图和布局 使用 TextView 的复合 drawables 减少层次

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello" android:drawableLeft="@drawable/icon"/>

----------------------- Page 4-----------------------

视图和布局 使用 Viewtub 延迟展开视图

----------------------- Page 4-----------------------

视图和布局 使用 Viewtub 延迟展开视图

首先在 XML 布局文件中定义 ViewStub

<ViewStub android:id = "@+id/stub_import" android:inflatedId="@+id/panel_import" android:layout="@layout/progress_overlay"

android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom"/>

在需要展开视图时

findViewById(R.id.stub_import).setVisibility(View.VISIBLE);

// 或者

View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

----------------------- Page 4-----------------------

视图和布局 使用 Viewtub 延迟展开视图

----------------------- Page 4-----------------------

视图和布局 使用 <merge> 合并视图

----------------------- Page 4-----------------------

视图和布局 使用 <merge> 合并视图

默认情况下, 布局文件的根作为一个结点加 入到父视图中

如果使用 <merge> 可以避免根接点

<!-- The merge tag must be the root tag -->

<merge xmlns:android "http://schemas.android.com/apk/res/android">

<! -- Content --> </merge>

----------------------- Page 4-----------------------

视图和布局 使用 <merge> 合并视图

----------------------- Page 4-----------------------

视图和布局 使用 RelativeLayout 减少层次

很有功效

可以代替 LinearLayout

可以在水平 LinearLayout 中加入垂直 LinearLayout

反过来也可以

可以用单层次表达复杂布局

----------------------- Page 4-----------------------

视图和布局 使用 RelativeLayout 减少层次

----------------------- Page 4-----------------------

视图和布局 使用 elativeLayout 减少层次

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content">

<ImageView android:id="@+id/icon" android:layout_width="48dip" android:layout_height="48dip"

android:layout_alignParentLeft="true" android:layout_centerVertical="true"/>

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/text_line1"

android:layout_alignParentTop="true" android:layout_toRightOf="@id/icon"/>

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/text_line2"

android:layout_toRightOf="@id/icon" android:layout_below="@id/text_line1"/>

<Checkbox android:id="@+id/star" android:layout_width="48dip" android:layout_height="48dip"

android:layout_alignParentRight="true" android:layout_centerVertical="true"/>

</RelativeLayout>

----------------------- Page 5-----------------------

自定义视图

----------------------- Page 5-----------------------

自定义视图

class CustomView extends View {

@Override

protected void onDraw (Canvas canvas) {

// 加入你的 绘图编码

}

@Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {

// 计算视图的尺寸

setMeasuredDimension(widthSpecSize, heightSpecSize);

}

}

----------------------- Page 5-----------------------

自定义布局

----------------------- Page 5-----------------------

自定义布局

class GridLayout extends ViewGroup {

@Override

protected void onLayout (boolean changed, int l, int t, int r, int b) {

final int count = getChildCount(); for (int i=0; i < count; i++) {

final View child = getChildAt(i);

if (child.getVisibility() != GONE) {

// 计算子视图的位置

child.layout(left, top, right, bottom);

}

}

}

}

----------------------- Page 6-----------------------

内存分配

----------------------- Page 6-----------------------

内存分配

不要创建 Java 对象

在性能敏感的代码里, 尽量避免创建 Java 对象

测量: onMeasure()

布局: onLayout()

绘图: dispatchDraw(), onDraw()

事件处理: dispatchTouchEvent(), onTouchEvent()

Adapter: getView(), bindView()

GC, 垃圾回收

整个程序会暂停

慢 (大约几百个毫秒)

----------------------- Page 6-----------------------

内存分配

强行限制 (适用于调试模式)

int prevLimit = -1;

try { prevLimit = Debug.setAllocationLimit(0);

// 执行不分配内存的代码

} catch (dalvik.system.AllocationLimitError e) {

// 如果代 码分配内存, Java 虚拟机会抛出错误

Log.e(LOGTAG, e);

} finally {

Debug.setAllocationLimit(prevLimit);

}

----------------------- Page 6-----------------------

内存分配

管理好对象

使用软引用

内存缓存的最佳选择

使用弱引用

避免内存泄露

----------------------- Page 6-----------------------

内存分配

内存缓存实例

private final HashMap<String, SoftReference<T>> mCache;

public void put(String key, T value) {

mCache.put(key, new SoftReference<T>(value));

}

public T get(String key, ValueBuilder builder) {

T value = null;

SoftReferece<T> reference = mCache.get(key);

if (reference != null) {

value = reference.get();

} if (value null) {

value = builder.build(key);

mCache.put(key, new SoftReference<T>(value));

}

return value;

}

----------------------- Page 7-----------------------

总结

----------------------- Page 7-----------------------

用 ViewHolder 实现 Adapter 的 getView

为背景选择正好的图像

针对无效区做更新请求

视图和布局,越简单越好

避免在性能敏感路径上创建对象

----------------------- Page 8-----------------------

Learn more at http://code.google.com href="http://code.google.com" target=_blank>点击打开链接

----------------------- Page 8-----------------------


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: