优化layout的层级
- 一个常见的误区是,用最基础的 Layout 结构可以提高 Layout 的 性能。然而,因为程序的每个组件和 Layout 都需要经过初始化、布局和绘制的过程,如果布局嵌套导致层级过深,上面的初始化,布局和绘制操作就更加耗时。例如,使用嵌套的 LinearLayout 可能会使得 View 的层级结构过深,此外,嵌套使用了 layout_weight 参数的 LinearLayout 的计算量会尤其大,因为每个子元素都需要被测量两次。这对需要多次重复 inflate 的 Layout 尤其需要注意,比如嵌套在 ListView 或 GridView 时。
- 使用 layout_weight(权重)时要慎重,会增加测量、布局、绘制的时间。
- 画布局的时候将 Layout 层级扁平化 - 变浅变宽,而不是又窄又深。
使用 Lint
- 大部分叫做 lint 的编程工具,都是类似于代码规范的检测工具。比如JSLint,CSSLinkt, JSONLint 等等。
- 运行 Lint 工具来检查 Layout 可能的优化方法,是个很好的实践。Lint 已经取代了 Layoutopt 工具,它拥有更强大的功能。Lint 中包含的一些检测规则有:
- 使用compound drawable — 用一个compound drawable 替代一个包含 ImageView 和 TextView 的 LinearLayout 会更有效率。
- 合并根 frame — 如果 FrameLayout 是 Layout 的根节点,并且没有使用 padding 或者背景等,那么用 merge 标签替代他们会稍微高效些。
- 没用的子节点 — 一个没有子节点或者背景的 Layout 应该被去掉,来获得更扁平的层级
- 没用的父节点 — 一个节点如果没有兄弟节点,并且它不是 ScrollView 或根节点,没有背景,这样的节点应该直接被子节点取代,来获得更扁平的层级
- 太深的 Layout — Layout 的嵌套层数太深对性能有很大影响。尝试使用更扁平的 Layout ,比如 RelativeLayout 或 GridLayout 来提高性能。一般最多不超过10层。
另一个使用 Lint 的好处就是,它内置于 Android Studio 中。Lint 在你导编译程序时自动运行。Android Studio 中,你可以为单独的 build variant 或者所有 variant 运行 lint。
- 你也可以在 Android Studio 中管理检测选项,在 File > Settings > Project Settings 中。检测配置页面会显示支持的检测项目。
- Lint 有自动修复、提示建议和直接跳转到问题处的功能。
使用标签
- 标签在你嵌套 Layout 时取消了 UI 层级中冗余的 ViewGroup 。比如,如果你有一个 Layout 是一个竖直方向的 LinearLayout,其中包含两个连续的 View 可以在别的 Layout 中重用,那么你会做一个 LinearLayout 来包含这两个 View ,以便重用。不过,当使用一个 LinearLayout 作为另一个 LinearLayout 的根节点时,这种嵌套 LinearLayout 的方式除了减慢你的 UI 性能外没有任何意义。
为了避免这种情况,你可以用 元素来替代可重用 Layout 的根节点。例如:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/add"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/delete"/>
</merge>
- 现在,当你要将这个 Layout 包含到另一个 Layout 中时(并且使用了 标签),系统会忽略 标签,直接把两个 Button 放到 Layout 中 的所在位置。
定义 ViewStub
- ViewStub 是一个轻量的视图,不需要大小信息,也不会在被加入的 Layout 中绘制任何东西。每个 ViewStub 只需要设置 android:layout 属性来指定需要被 inflate 的 Layout 类型。
- 以下 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" />
载入 ViewStub Layout
- 当你要载入用 ViewStub 声明的 Layout 时,要么用 setVisibility(View.VISIBLE) 设置它的可见性,要么调用其 inflate() 方法。
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
- Notes:inflate() 方法会在渲染完成后返回被 inflate 的视图,所以如果你需要和这个 Layout 交互的话, 你不需要再调用 findViewById() 去查找这个元素,。
- 一旦 ViewStub 可见或是被 inflate 了,ViewStub 就不再继续存在View的层级机构中了。取而代之的是被 inflate 的 Layout,其 id 是 ViewStub 上的 android:inflatedId 属性。(ViewStub 的 android:id 属性仅在 ViewStub 可见以前可用)
- Notes:ViewStub 的一个缺陷是,它目前不支持使用 标签的 Layout 。
使得ListView滑动顺畅
- 保持程序流畅的关键,是让主线程(UI 线程)不要进行大量运算。你要确保在其他线程执行磁盘读写、网络读写或是 SQL 操作等。
使用后台线程
- 你应该把主线程中的耗时间的操作,提取到一个后台线程(也叫做“worker thread工作线程”)中,使得主线程只关注 UI 绘画。很多时候,使用 AsyncTask 是一个简单的在主线程以外进行操作的方法。系统会自动把execute()的请求放入队列中并线性调用执行。这个行为是全局的,这意味着你不需要考虑自己定义线程池的事情。
- 在下面的例子中,一个 AsyncTask 被用于在后台线程载入图片,并在载入完成后把图片显示到 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);
- 从 Android 3.0 (API level 11) 开始, AsyncTask 有个新特性,那就是它可以在多个 CPU 核上运行。你可以调用 executeOnExecutor()而不是execute(),前者可以根据CPU的核心数来触发多个任务同时进行。
在 ViewHolder 中填入视图对象
- 你的代码可能在 ListView 滑动时经常使用 findViewById(),这样会降低性能。即使是 Adapter 返回一个用于回收的 inflate 后的视图,你仍然需要查看这个元素并更新它。避免频繁调用 findViewById() 的方法之一,就是使用 ViewHolder(视图占位符)的设计模式。
- 一个 ViewHolder 对象存储了他的标签下的每个视图。这样你不用频繁查找这个元素。第一,你需要创建一个类来存储你会用到的视图。比如:
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
- 然后,在 Layout 的类中生成一个 ViewHolder 对象:
ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
holder.text = (TextView) convertView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) convertView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) convertView.findViewById(R.id.progress_spinner);
convertView.setTag(holder);
- 这样你就可以轻松获取每个视图,而不是使用 findViewById() 来不断查找子视图,节省了宝贵的运算时间。