Android开发Android开发经验谈Android开发

阿里一、二、三次技术面都被问到Android布局优化,全面复盘一

2020-10-15  本文已影响0人  10块钱new一个对象

前言

目录

1. 对性能的影响

主要影响Android应用中页面显示的速度。1个页面通过递归 完成测量 & 绘制过程 = measure、layout 过程,而这个过程过长则会给用户带来卡顿的视觉效果。

2.优化思路

布局优化的思路其实很简单,就是尽量减少布局文件的层级。布局的层级少了,这就意味着Android绘制时工作量少了,那么程序的性能自然就高了。

3. 具体优化方案

3.1 删除布局中无用的控件和层级

3.2 选择耗费性能较少的布局

如果布局中即可使用LinearLayout也可以使用RelativeLayout,那就采用LinearLayout。因为RelativeLayout在绘制时需要对子View分别进行了竖直和水平方向的两次测量,而Linearlayout在绘制时是根据我们设置的方向分别调用不同的测量方法。注意一点如果LinearLayout中子View使用了layout_weight属性时同样需要对子View进行两次测量以确定最终大小(对此不了解的小伙伴们可以查看源码中onMeasureonLayout方法本文就不多贴源码)。LinearLayoutFrameLayout都是一种性能耗费低的布局。但是很多时候单纯通过一个LinearLayoutFrameLayout无法实现产品的效果,需要通过嵌套的方式来完成。这种情况下建议使用RelativeLayout,因为嵌套就相当于增加了布局的层级,同样会降低程序的性能。

评论多次提到Constraintlayout,由于笔者用的较少忘了说,疏忽了,疏忽了[手动哭笑]。面对复杂度高的布局(比RelativeLayoutLinearLayout多次嵌套)Constraintlayout确实更简单,绘制时间更短。但面对复杂度较低的布局,RelativeLayoutConstraintLayoutonMesaure阶段快数倍。下图为Hierarchy Viewer的测试结果(里面一个TextView,一个ImageView,关于Hierarchy Viewer的使用会在下文布局调优工具中):

3.3 提高布局的复用性(使用 <include> 布局标签)

使用 <include>标签提取布局间的公共部分,通过提高布局的复用性从而减少测量 & 绘制时间

<!--抽取出的公共布局:include_title.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"
    android:background="@color/colorAccent">
 
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:paddingLeft="15dp"
        android:src="@mipmap/ic_titilebar_back"/>
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/title"
        android:textColor="@color/white"
        android:textSize="18sp"/>
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:gravity="center"
        android:padding="15dp"
        android:text="@string/more"
        android:textColor="@color/white"
        android:textSize="16sp"/>
 
</RelativeLayout>
 
 
<!--布局:activity_main.xl引用公共布局include_title.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 
     <include layout="@layout/include_title"/>
 
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Hello World!" />
 
</LinearLayout>
 

<include> 标签只支持以android:layout_开头的属性(android:id除外),需要注意一点如果使用了android:layout_这种属性,那么要求android:layout_width 和android:layout_height必须存在,否则其他android:layout_形式的属性无法生效,下面是一个指定了android:layout_*属性的示例

<include
    android:id="@+id/include_title"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    layout="@layout/include_title"/>

3.4 减少布局的层级(使用 <merge> 布局标签)

<merge> 布局标签一般和 <include> 标签一起使用从而减少布局的层级。例如当前布局是一个竖直方向的LinearLayout,这个时候如果被包含的布局也采用了竖直方向的LinearLayout,那么显然被包含的布局文件中的LinearLayout是多余的,这时通过 <merge> 布局标签就可以去掉多余的那一层LinearLayout。如下所示:

<!--抽取出的公共布局:include_title.xml-->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />
 
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />
 
</merge>
 
<!--布局:activity_main.xl引用公共布局include_title.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 
    <include layout="@layout/include_title"/>
 
</LinearLayout>
 

3.5 减少初次测量 & 绘制时间

3.5.1使用 <ViewStub> 标签

ViewStub继承了View,它非常轻量级且宽和高都为0,因此它本身不参与任何的绘制过程,避免资源的浪费,减少渲染时间,在需要的时候才加载View。因此ViewStub的意义在于按需求加载所需的布局,在实际开发中,很多布局在正常情况下不会显示,比如加载数据暂无数据,网络异常等界面,这个时候就没必要在整个界面初始化的时候将其加载进来,通过ViewStub就可以做到在使用时在加载,提高了程序初始化时的性能。如下一个ViewStub的示例:

<!--暂无数据页:empty_data.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
 
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@mipmap/ic_empty_order"/>
 
    <TextView
        android:layout_below="@+id/iv_empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暂无数据"/>
 
</LinearLayout>
 
<!--布局activity_main.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
 
    //view_stub是ViewStub的id,empty是empty_data.xml这个布局根元素的id
    <ViewStub
        android:id="@+id/view_stub"            
        android:inflatedId="@+id/empty"         
        android:layout="@layout/empty_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
 
</LinearLayout>
 
<!--MainActivity-->
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        //加载ViewStub中的布局的两种方式setVisibility或inflate
        mViewStub.setVisibility(View.VISIBLE);
        mViewStub.inflate();
        
    }
  

使用ViewStub需注意:当ViewStub通过setVisibility或inflate方法加载后,ViewStub就会被它内部的布局替换掉,这个时候ViewStub就不再是布局结构中的的一部分。目前ViewStub中的layout还不支持使用<merge> 标签。

3.5.2尽可能少用布局属性 wrap_content

布局属性 wrap_content 会增加布局测量时计算成本,应尽可能少用

3.6 减少控件的使用(善用控件属性)

在绘制布局中,某些情况下我们可以省去部分控件的使用。下文介绍几种常见的情况:

3.6.1 TextView文字加图片

上图布局,通常想到的是一个相对布局里包含一个TextView和两个ImageView。事实上我们只需要一个TextView就可以实现

<TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="20dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"   // 设置左边显示的icon  
        android:drawablePadding="10dp"                  // 设置icon和文本的间距  
        android:drawableRight="@mipmap/icon_right"      // 设置右边显示的icon
        android:text="@string/account_unlock"
        />

3.6.2 LinearLayout分割线

上图布局,通常是用高为1dp的View来显示,实际上LinearLayout本身就能实现

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="@drawable/divider_line"
    android:dividerPadding="16dp"
    android:showDividers="middle"
    android:orientation="vertical">
 
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"
        android:drawablePadding="10dp"
        android:drawableRight="@mipmap/icon_right"
        android:background="@color/color_FFFFFF"
        android:text="@string/account_unlock"
        />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"
        android:drawablePadding="10dp"
        android:drawableRight="@mipmap/icon_right"
        android:background="@color/color_FFFFFF"
        android:text="@string/account_unlock"
        />
 
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="center_vertical"
        android:drawableLeft="@mipmap/icon_my_unlock"
        android:drawablePadding="10dp"
        android:drawableRight="@mipmap/icon_right"
        android:background="@color/color_FFFFFF"
        android:text="@string/account_unlock"
        />
 
<!--Shape:divider_line.xml-->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
 
    <solid android:color="@color/colorPrimary"/>
    <size android:height="1dp"/>
 
</shape>
 
</LinearLayout>

核心代码就是对LinearLayout设置divider

3.6.3 TextView的行间距和占位符的使用

<TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:lineSpacingExtra="10dp"
        android:text="@string/test_text"
        android:textSize="20sp"
        android:textColor="@color/colorPrimary"/>

通过lineSpacingExtra设置行间隔,test_text内容为:

<string name="test_text">标题:%1$s\n时间:%2$s\n内容:%3$s</string>

在MainActivity中的使用

mText.setText(String.format(getResources().getString(R.string.test_text), "测试", "2018-8-9","测试测试"));

占位符的使用方法:%n表示第n位要被替换的,s**表示字符串类型占位符,**d表示整型占位符,$f表示浮点型占位符

4. 布局调优工具

在实际开发中哪怕注意了上述的优化方案,难免还是会出现布局性能的问题。这时我们可使用布局调优工具来分析问题。本文将介绍常用的几种工具。

4.1 Lint

Lint 是Android Studio 提供的 代码扫描分析工具,它可以帮助我们发现代码结构/质量问题,同时提供一些解决方案,而且这个过程不需要我们手写测试用例。 Lint 的使用路径: 工具栏 -> Analyze -> Inspect Code

默认是检查整个项目,我们可以点击 Custom scope 自定义检查范围

当然你也可以选择特定的类进行检查点击下图红色箭头标识的地方

点击“+”号新增一个检查范围:

选择Shared,默认按项目显示,检查的文件数为 0 。下图红色框中4个按钮表示要操作的类型

我们左击想扫描的文件,点击右边对应的按钮。可以看到文件边色了,红框显示需扫描24个文件,点击OK→OK

稍等一会儿,会弹出 Inspection 对话框,显示检查结果。

Lint 的警告严重程度有以下几种:

本文对Lint的介绍就到此如果对Lint想了解更多的小伙伴可以点击Lint

4.2 Hierarchy Viewer

Hierarchy Viewer 是Android Studio 提供的UI性能检测工具。可获得UI布局设计结构 & 各种属性信息,帮助我们优化布局设计 。

使用Hierarchy Viewer ,您的设备必须运行Android 4.1或更高版本。如果您是使用真机的话需注意以下两点:

Hierarchy Viewer 的使用路径: 工具栏 ->Tools->Android->Android Device Monitor(默认是显示DDMS窗口,更改可点击Open Perspective->Hierarchy Viewer,也可以直接点击 Hierarchy Viewer Button),如下图所示:

更改后显示Hierarchy Viewer的窗口如下图所示:

如果您看的的视图排列不一样,可选择 Window->Reset Perspective 返回默认布局。下面介绍下基本窗口:

上图图标的使用左到右介绍:

所选的节点上方会有个小窗口显示子View数和MeasureLayoutDraw绘制所需时间。
所选节点的每个子视图都有三个点,可以是绿色,黄色或红色。

这些点大致对应于处理管道的度量,布局和绘制阶段。点的颜色表示该节点相对于本地系列中所有其他配置节点的相对性能。

所测量的是每个节点相对于兄弟视图的性能,因此配置文件中始终存在红色节点,除非所有视图执行相同,并且它并不一定意味着红色节点执行得很差(仅限于它是最慢的视图)在本地视图组中)。

本文对Hierarchy Viewer的介绍就到此了,想了解更多的小伙伴可以点击Hierarchy Viewer

4.3 开发者选项(调试GPU过度绘制)

开发者选项是Android 系统为开发者提供的一个APP验证、调试、优化等各种功能的入口(需Android 4.1以上)。本文主要对GPU过度绘制菜单介绍,对其他菜单功能有兴趣的小伙伴可以参考探索Android手机开发者选项

GPU过度绘制是指在屏幕上某个像素在同一帧的时间内被绘制多次,产生的主要原因有两个:

4.3.1 开启开发者选项

不同手机开发商都对手机界面做了定制化处理,打开手机开发者选项的方式各不相同,但基本的功能菜单都是类似的。由于笔者现在用的是小米5s Plus,因此打开方式是设置->我的设备->全部参数->连续点击MIUI版本,提示开发者模式开启即可(不同MIUI版本开启路径也不一样)。然后设置->更多设置,高级设置或者其他设置等等菜单中即可看到开发者选项的菜单了。

4.3.2 开启GPU过度绘制

在开发者选项中点击调试GPU过度绘制,如下图所示

这时屏幕会出现各种颜色,每种颜色的定义为:

以上就是本文对布局调优工具的介绍,当然还有其他布局调优工具如Systrace本文就不一一介绍了。

结尾

本文主要讲解Android 性能优化中的 布局优化的技术考点。如对小伙伴们有帮助,麻烦给个喜欢,不足之处请留言私聊点出谢谢!



看完点赞,养成习惯,微信搜一搜「 程序猿养成中心 」关注这个喜欢写干货的程序员。

另外整理收集的Android一线大厂面试完整考点、资料【完整版】已更新在我的【Github】,有面试需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!

地址:【https://github.com/733gh/xiongfan】

上一篇下一篇

猜你喜欢

热点阅读