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

Android性能优化(三):布局优化

2017-03-07 15:10 375 查看
Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性、复杂度也会随之增长,这样使得UI布局的优化,显得至关重要,UI布局不慎,就会引起过度绘制,从而造成UI卡顿的情况。

1.使用布局标签优化布局

(1)[b]
<include>
标签

[/b]

include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,这在布局编写方便提供了大大的便利。例如我们在进行App开发时基本每个页面都会有标题栏,在不使用include的情况下你在每个界面都需要重新在xml里面写一个顶部标题栏,工作量无疑是巨大的,使用include标签,我们只需要把这个会被多次使用的顶部栏独立成一个xml文件,然后在需要使用的地方通过include标签引入即可。


下面以在一个布局main.xml中用include引入另一个布局foot.xml为例。main.mxl代码如下:


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

<ListView
android:id="@+id/simple_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="80dp" />

<include
android:id="@+id/my_foot_ly"
layout="@layout/foot" />

</RelativeLayout>

其中include引入的foot.xml为公用的页面底部,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_above="@+id/title_tv"/>

<TextView
android:id="@+id/title_tv"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_alignParentBottom="true"
android:text="@string/app_name" />

</RelativeLayout>
<include>
标签唯一需要的属性是layout属性,指定需要包含的布局文件。可以定义android:id和android:layout_*属性来覆盖被引入布局根节点的对应属性值。注意重新定义android:id后,子布局的顶结点i就变化了。
[b]注意:

使用include最常见的问题就是findViewById查找不到目标控件,这个问题出现的前提是在include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。例如上述例子中,include时设置了该布局的id为my_foot_ly

,而my_foot_ly.xml中的根视图的id为my_foot_parent_id。此时如果通过findViewById来找my_foot_parent_id这个控件,然后再查找my_foot_parent_id下的子控件则会抛出空指针。代码如下 :
[/b]

View titleView = findViewById(R.id.my_foot_parent_id) ; // 此时 titleView 为空,找不到。此时空指针
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ; titleTextView.setText("new Title");
其正确的使用形式应该如下:
// 使用include时设置的id,即R.id.my_title_ly
View titleView = findViewById(R.id.my_foot_ly) ;
// 通过titleView找子控件 TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ; titleTextView.setText("new Title");

或者更简单的直接查找它的子控件:
TextView titleTextView = (TextView)findViewById(R.id.title_tv) ; titleTextView.setText("new Title");

(2)
<viewstub>
标签

viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。

viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。

下面以在一个布局main.xml中加入网络错误时的提示页面network_error.xml为例。main.mxl代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

……
<ViewStub
android:id="@+id/network_error_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/network_error" />

</RelativeLayout>

其中network_error.xml为只有在网络错误时才需要显示的布局,默认不会被解析,示例代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<Button
android:id="@+id/network_setting"
android:layout_width="@dimen/dp_160"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@string/network_setting" />

<Button
android:id="@+id/network_refresh"
android:layout_width="@dimen/dp_160"
android:layout_height="wrap_content"
android:layout_below="@+id/network_setting"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/dp_10"
android:text="@string/network_refresh" />

</RelativeLayout>

Java中通过(ViewStub)findViewById(id)找到ViewStub,通过stub.inflate()展开ViewStub,然后得到子View,如下:
private View networkErrorView;

private void showNetError() {
// not repeated infalte
if (networkErrorView != null) { networkErrorView.setVisibility(View.VISIBLE); return; }

ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
if(stub !=null){

networkErrorView = stub.inflate();
Button networkSetting = (Button)networkErrorView.findViewById(R.id.network_setting);
Button refresh = (Button)findViewById(R.id.network_refresh);
}
}

private void showNormal() {
if (networkErrorView != null) {
networkErrorView.setVisibility(View.GONE);
}
}

在上面showNetError()中展开了ViewStub,同时我们对networkErrorView进行了保存,这样下次不用继续inflate。这就是后面第三部分提到的减少不必要的infalte。

注意这里我对ViewStub的实例进行了一个非空判断,这是因为ViewStub在XML中定义的id只在一开始有效,一旦ViewStub中指定的布局加载之后,这个id也就失败了,那么此时findViewById()得到的值也会是空

viewstub标签大部分属性同include标签类似。

上面展开ViewStub部分代码
ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
networkErrorView = stub.inflate();

也可以写成下面的形式
View viewStub = findViewById(R.id.network_error_layout);
viewStub.setVisibility(View.VISIBLE); // ViewStub被展开后的布局所替换
networkErrorView = findViewById(R.id.network_error_layout); // 获取展开后的布局

View 的可见性设置为 gone 后,在inflate 时,这个View 及其子View依然会被解析的。使用ViewStub就能避免解析其中指定的布局文件,从而节省布局文件的解析时间,及内存的占用。另外需要提醒大家一点,ViewStub所加载的布局是不可以使用
<merge>
标签的
(3)
<merge>
标签


在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢,不必要的节点和嵌套可通过hierarchy viewer(下面布局调优工具中有具体介绍)或设置->开发者选项->显示布局边界查看。merge标签可用于两种典型情况:

a. 布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容试图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。

b. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。

以(1) include标签的示例为例,用hierarchy viewer查看main.xml布局如下图:



可以发现多了一层没必要的RelativeLayout,将foot.xml中RelativeLayout改为merge,如下:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_above="@+id/text"/>

<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_alignParentBottom="true"
android:text="@string/app_name" />

</merge>

运行后再次用hierarchy viewer查看main.xml布局如下图:



去除不必要的嵌套和View节点

(1) 首次不需要使用的节点设置为GONE或使用viewstub

(2) 使用RelativeLayout代替LinearLayout

大约在Android4.0之前,新建工程的默认main.xml中顶节点是LinearLayout,而在之后已经改为RelativeLayout,因为RelativeLayout性能更优,且可以简单实现LinearLayout嵌套才能实现的布局。

4.0及以上Android版本可通过设置->开发者选项->显示布局边界打开页面布局显示,看看是否有不必要的节点和嵌套。4.0以下版本可通过hierarchy viewer查看。

减少不必要的infalte

(1)对于inflate的布局可以直接缓存,用全部变量代替局部变量,避免下次需再次inflate

如上面ViewStub示例中的

if (networkErrorView != null) {
networkErrorView.setVisibility(View.VISIBLE);
return;
}


2.布局调优工具
(1) hierarchy viewer

hierarchy viewer可以方便的查看Activity的布局,各个View的属性、measure、layout、draw的时间,如果耗时较多会用红色标记,否则显示绿色。

Hierarchy Viewer是随Android SDK发布的工具,位于Android SDK/tools/hierarchyviewer.bat (Windows操作系统,mac上显示的为hierarchyviewer),使用起来也是超级简单,通过此工具可以详细的理解当前界面的控件布局以及某个控件的属性(name、id、height等)。

1)连接设备真机或者模拟器

2)启动你要观察的应用。

3)打开Hierarchyviewer,点击hierarchyviewer文件即可。



4)双击最上面的,如下图的
<Focused Window>
,这个是当前窗口,加载完毕后会显示当前界面层次结构



5)观察层次结构图,这个图有点大,可以拖动。View Hierarchy窗口显示了Activity的所有View对象,选中某个View还可以查看View的具体信息,最好选择工具中的Show Extras选项。





View Hierarcy 同时能帮助你识别渲染性能比较低的部分。View节点中带有红色或黄色的点代表速度较慢的View对象。如单步运行应用程序那样,你可以这样来判断某个View 速度一直很慢,还是只在某个特定环境下速度才慢。

请注意,低性能并不表示一定有问题,特别像是ViewGroup对象,View的子节点越多,结构越复杂,性能越差。

View Hierarchy 窗口还可以帮助你找到性能问题。只要看每个View节点的性能指标(颜色点)就可以,你可以看到测量(布局或绘制)最慢的View对象是哪个,这样你就能快速确定,要优先察看哪个问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息