Android 性能优化之布局优化

在 Android 开发的过程中,对性能进行优化是必不可少的一步。而在布局上的优化并不像其他优化方式那么复杂,通过 Android SDK 提供的 Hierarchy Viewer 可以很直接地看到冗余的层级,去除这些多余的层级将使我们的UI变得更加流畅。

如何使用 Hierarchy Viewer ?
  1. 在 Android studio 菜单栏上,点击 Tools --> Android --> Android Device Monitor

  2. 在 Android Device Monitor 菜单栏上,点击 Window --> Open perspective --> Hierarchy Viewer 即可。

然而,一般在真机上无法使用 Hierarchy Viewer,只能在运行开发版 Android 系统的设备进行交互(一般来说,使用模拟器上即可),着实有点遗憾。但是,也可以在真机上通过 root 等一系列操作之后,便可以使用 Hierarchy Viewer 。这部分教程就需要大家在网上另行查阅了。

下面是一些常用的布局优化方式:

一、include 布局

当多个页面公用了一些UI组件时,就可以使用 include 布局。Android 提供了 include 标签,让我们可以将子布局引入到一个布局文件中,这样一来,公用布局就可以独立成为一个布局 xml,其他页面只要 include 引用这个布局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="wrap_content">

    <ImageButton
        android:id="@+id/title_back_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:src="@drawable/back" />

    <TextView
        android:id="@+id/title_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Title" />
</RelativeLayout>

将该标题栏引入一个Activity的xml中

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.jerry.myapplication.MainActivity">

    <include
        android:id="@+id/top_title"
        layout="@layout/common_title" />

    <TextView
        android:id="@+id/username_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

通过 include 标签引入 common_title 这个布局(注意:include 布局中指定布局 xml 是使用 layout 属性,而不是 android:layout 属性)。这样一来,就复用了 common_title 的标题栏效果,不必在每个页面中重复定义标题栏布局。大大降低了我们维护 xml 的成本,也提升了代码复用率。

include 标签的原理:

在解析 xml 布局时,如果检测到 include 标签,那么就直接把该布局下的根视图添加到 include 所在的父视图中。对于布局 xml 的解析最终都会调用到 LayoutInflaterinflate 方法,该方法最终又会调用 rInflate 方法。这个方法就是遍历 xml 中的所有元素,然后逐个进行解析。

如何获取 include 布局里的控件?

以上面例子为例,获取标题栏内的 Textview

 private TextView mTextView;

 mTextView = findViewById(R.id.top_title).findViewById(R.id.title_textview);

二、merge 标签

merge 标签适用于子布局的根视图与它的父视图是同一类型。它的作用是合并UI布局,降低UI布局的嵌套层次。使用场景是存在多层使用同一种布局类的嵌套视图,这种情况下用merge标签作为子视图的顶级视图来解决多余的层级。


如上图,child_view 与 parent_view 都是FrameLayout类型,那么 child_view 下的两个控件可以直接使用 parent_view 来布局,这样就可以去除 child_view 这个层级。

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

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</FrameLayout>

上面这个布局相当于是 child_view,而 Activity 内容视图的顶层布局也 FrameLayout,因此产生了视图冗余,可以用 merge 标签去掉这层冗余。

下面是screen_title.xml文件的源代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <!-以下就是开发人员设置的布局所填充的位置-->
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
<?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:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</merge>

与 include 一样,merge 的解析也在 LayoutInflater 的 inflate() 函数中。在 inflate() 函数中循环解析 xml 中 的 tag,如果解析到 merge 标签则会调用 rinflate 函数。

注意事项:
  1. merge 必须作为布局文件的根节点标签。

  2. merge 并不是一个 ViewGroup,也不是一个 View,它相当于声明了一些视图,等待被添加。

  3. 因为 merge 标签不是 View,所以对 merge 标签设置的所有属性都是无效的。

  4. 因为 merge 标签不是 View,所以在通过 LayoutInflate.inflate 方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为 true,也就是必须为 merge 下的视图指定一个父亲节点。

  5. 如果 Activity 的布局文件根节点是 FrameLayout,可以替换为 merge 标签,这样,执行 setContentView 之后,会减少一层 FrameLayout 节点。

三、ViewStub 视图

ViewStub 是什么?

ViewStub 是一个不可见的和能在运行期间延迟加载目标视图的、宽高都为0的 View (ViewStub 继承 View)。当对一个 ViewStub 调用 inflate() 方法或设置它可见时,系统会加载在 ViewStub 标签中指定的布局,然后将这个布局的根视图添加到 ViewStub 的父视图中。换句话说,在对 ViewStub 调用 inflate() 方法或者设置 visible 之前,它是不占用布局空间和系统资源的,它只是为目标视图占了一个位置而已。

何时使用 ViewStub?

当我们只需要在某些情况下才加载一些耗资源的布局时,ViewStub 就成为我们实现这个功能的重要手段。

例如:有一个显示九宫格图片的 GridView 视图,我们想根据网络返回的数据来判断是否加载该 GridView ,因为默认加载的话会造成资源浪费,系统加载成本较高。这时使用 ViewStub 标签就可以很方便实现延迟加载。

以下是一个小小的演示,在一个 Activity 中使用 ViewStub 来动态加载
GridView。

<?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">

    <include
        android:id="@+id/top_title"
        layout="@layout/common_title" />

    <Button
        android:id="@+id/show_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示图片" />

    <ViewStub
        android:id="@+id/comment_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/layout_image_gv" />

</RelativeLayout>

布局的最后使用 ViewStub 来加载 layout_image_gv.xml 布局,下面是 layout_image_gv.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="#aaaaaa">

    <TextView
        android:id="@+id/image_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="显示九宫格GridView" />

    <GridView
        android:id="@+id/image_gc"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/image_desc"
        android:numColumns="3">

    </GridView>
</RelativeLayout>
public class StubLayoutActivity extends AppCompatActivity {

    private ViewStub gvStub;
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_activity_stub);

        gvStub = findViewById(R.id.comment_stub);

        mButton = findViewById(R.id.show_btn);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //加载目标视图,不能多次调用,否则会引发异常
                gvStub.inflate();
                //gvStub.setVisibility(View.VISIBLE);

            }
        });
    }
}
ViewStub 小结

当用户手动调用 ViewStub 的 inflate 或者 setVisibility 函数(实际上也是调用 inflate 函数)时,会将 ViewStub 自身从父控件中移除,并且加载目标布局,然后将目标布局添加到 ViewStub 的父控件中,这样就完成视图的动态替换,也就是延迟加载功能。

四、减少视图树层级

为什么要减少视图层级?

每一个视图在显示时会经历测量、布局、绘制的过程,如果我们的布局中嵌套的视图层次过多,就会造成额外测量、布局等工作,使得UI变得卡顿,影响用户的使用体验。

例如一个简单的列表 Item

<?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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

而使用 RelativeLayout 来布局这个 item view 就可以减少一层 LinearLayout 的渲染。

<?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">

    <ImageView
        android:id="@+id/profile_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/name_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/profile_image" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/profile_image" />
</RelativeLayout>

五、总结

在 Android UI 布局过程中,需要遵守的原则有以下几点:

  1. 尽量多使用 RelativeLayout ,不要使用绝对布局 AbsoluteLayout;

  2. 在 ListView 等列表组件中尽量避免使用 LinearLayout 的 layout_weight 属性(子视图使用了 layout_weight 属性的 LinearLayout 会对它的子视图进行两次测量。)

  3. 将可复用的组件抽取出来并通过 <include/> 标签使用;

  4. 使用 <ViewStub/> 标签来加载一些不常用的布局;

  5. 使用 <merge/> 标签来减少布局的嵌套层次。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容