Android渲染优化解析

本文已同步发表到我的技术微信公众号,扫一扫文章底部的二维码或在微信搜索 “程序员驿站”即可关注,每天都会更新优质技术文章。

这篇文章是继“Android电量优化全解析”与“Android内存优化全解析”之后关于Android性能优化的第三篇原创文章,主要讲解了Android渲染优化相关知识点,希望对大家有所帮助。今天我讲述的内容按照下面的结构进行。

卡顿现象

渲染功能是应用程序最普遍的功能,开发任何应用程序都是这样,一方面,设计师要求为用户展现可用性最高的超然体验,另一方面,那些华丽的图片和动画,并不是在所有的设备上都能流畅地运行。我们先来了解一下什么是渲染性能。

首先,我们要知道Android系统每隔16ms就重新绘制一次Activity,也就是说,我们的应用必须在16ms内完成屏幕刷新的全部逻辑操作,这样才能达到每秒60帧,然而这个每秒帧数的参数由手机硬件所决定,现在大多数手机屏幕刷新率是60赫兹(赫兹是国际单位制中频率的单位,它是每秒中的周期性变动重复次数的计量),也就是说我们有16ms(1000ms/60次=16.66ms)的时间去完成每帧的绘制逻辑操作,如果错过了,比如说我们花费34ms才完成计算,那么就会出现我们称之为丢帧的情况。

安卓系统尝试在屏幕上绘制新的一帧,但是这一帧还没准备好,所以画面就不会刷新。如果用户盯着同一张图看了32ms而不是16ms,用户会很容易察觉出卡顿感,哪怕仅仅出现一次掉帧,用户都会发现动画不是很顺畅,如果出现多次掉帧,用户就会开始抱怨卡顿,如果此时用户正在和系统进行交互操作,例如滑动列表或者输入数据,那么卡顿感就会更加明显,用户会毫不留情地对我们的应用进行吐槽,现在我们对绘制每帧花费的时间有了更清晰的了解,再来看看是什么原因导致了卡顿,如何去解决应用中的这些问题。

渲染管线

Android系统的渲染管线分为两个关键组件:CPU和GPU,它们共同工作,在屏幕上绘制图片,每个组件都有自身定义的特定流程。我们必须遵守这些特定的操作规则才能达到效果。

在CPU方面,最常见的性能问题是不必要的布局和失效,这些内容必须在视图层次结构中进行测量、清除并重新创建,引发这种问题通常有两个原因:一是重建显示列表的次数太多,二是花费太多时间作废视图层次并进行不必要的重绘,这两个原因在更新显示列表或者其他缓存GPU资源时导致CPU工作过度。

在GPU方面,最常见的问题是我们所说的过度绘制(overdraw),通常是在像素着色过程中,通过其他工具进行后期着色时浪费了GPU处理时间。

接下来我们将讲解更多关于失效布局和重绘的内容,以及如何使用SDK中的工具找出拖累应用性能的原因

CPU和 GPU

想要开发一款性能优越的应用,我们必须了解底层是如何运行的。有一个主要问题就是,Activity是如何绘制到屏幕上的?那些复杂的XML布局文件和标记语言,是如何转化成用户能看懂的图像的?

实际上,这是由栅格化操作来完成的,栅格化就是将例如字符串、按钮、路径或者形状的一些高级对象,拆分到不同的像素上在屏幕上进行显示,栅格化是一个非常费时的操作。我们所有人的手机里面都有一块特殊硬件,它就是图像处理器(GPU显卡的处理器),目的就是加快栅格化的操作,GPU在上个世纪90年代被引入用来帮助加快栅格化操作。

GPU使用一些指定的基础指令集,主要是多边形和纹理,也就是图片,CPU在屏幕上绘制图像前会向GPU输入这些指令,这一过程通常使用的API就是Android的OpenGL ES,这就是说,在屏幕上绘制UI对象时无论是按钮、路径或者复选框,都需要在CPU中首先转换为多边形或者纹理,然后再传递给GPU进行格栅化。

我们要知道,一个UI对象转换为一系列多边形和纹理的过程肯定相当耗时,从CPU上传处理数据到GPU同样也很耗时。所以很明显,我们需要尽量减少对象转换的次数,以及上传数据的次数,幸亏,OpenGL ES API允许数据上传到GPU后可以对数据进行保存,当我们下次绘制一个按钮时,只需要在GPU存储器里引用它,然后告诉OpenGL如何绘制就可以了,一条经验之谈:渲染性能的优化就是尽可能地上传数据到GPU,然后尽可能长地在不修改的情况下保存数据,因为每次上传资源到GPU时,我们都会浪费宝贵的处理时间,Android系统的Honeycomb版本发布之后,整个UI渲染系统就在GPU中运行,之后各个版本都在渲染系统性能方面有更多改进。

Android系统在降低、重新利用GPU资源方面做了很多工作,这方面完全不用担心,举例说,任何我们的主题所提供的资源,例如Bitmaps、Drawables等都是一起打包到统一的纹理当中,然后使用网格工具上传到GPU,例如Nine Patches等,这样每次我需要绘制这些资源时,我们就不用做任何转换,他们已经存储在GPU中了,大大加快了这些视图类型的显示。然而随着UI对象的不断升级,渲染流程也变得越来越复杂,例如说绘制图像,就是把图片上传到CPU存储器,然后传递到GPU中进行渲染。路径使用时完全另外一码事,我们需要在CPU中创建一系列的多边形,甚至在GPU中创建掩蔽纹理来定义路径。绘制字符就更加复杂一些,首先我们需要在CPU中把字符绘制制成图像,然后把图像上传到GPU进行渲染再返回到CPU,在屏幕上为字符串的每个字符绘制一个正方形。

现在Android系统已经解决了大多数性能问题,除非我们还有更高要求,我们基本不会发现与GPU相关的问题,然后还有一个GPU性能问题瓶颈,这个问题困扰着每个程序员,这就是过度绘制。

GPU的主要问题 -过度绘制(overdraw)

如果我们曾经粉刷过房子,我们应该知道,给墙壁粉刷工作量非常大,如果我们需要重新粉刷,第一次的粉刷就白干了。同样的道理,我们的应用程序会因为过度绘制,从而导致性能问题,如果我们想兼顾高性能和完美的设计,往往会碰到一种性能问题,即过度绘制。过度绘制是一个术语,指的是屏幕上的某个像素点在同一帧的时间内被绘制了多次。假如我们有一堆重叠的UI卡片,最接近用户的卡片在最上面,其余卡片都藏在下面,也就是说我们花大力气绘制的那些下面的卡片基本都是不可见的。

问题就在于此,因为每次像素经过渲染后,并不是用户最后看到的部分,这就是在浪费GPU的时间。目前流行的一些布局是一把双刃剑,带给我们漂亮视觉感受的同时,也造成过度绘制的问题,为了最大限度地提高应用程序的性能,我们必须尽量减少过度绘制。幸运的是,Android手机提供了查看过度绘制情况的工具,在开发者选项中打开"Show GPU overdraw"选项,手机屏幕显示会出现一些异常不用过于惊慌,Android在屏幕上使用不同颜色,标记过度绘制的区域,如果某个像素点只渲染了一次,我们看到的是它原来的颜色,随着过度绘制的增多,标记颜色也会逐渐加深,例如1倍过度绘制会被标记为蓝色,2倍、3倍、4倍过度绘制遵循同样的模式。所以当我们调试应用程序的用户界面时,目标就是尽可能的减少过度绘制,将红色区块转变成蓝色区块,为了完成目标有两种清楚过度绘制的方法,首先要从视图中清楚那些,不必要的背景和图片,他们不会在最终渲染图像中显示,记住,这些都会影响性能。其次,对视图中重叠的屏幕区域进行定义,从而降低CPU和GPU的消耗,接下来我们深入了解过度绘制。

可视化方式解决过度绘制

下面以一个列表界面做为例子讲解

现在我们看到了示例代码的应用程序,现在就想象我们自己开发了一款聊天应用,我们想了解应用程序在过度绘制性能上的表现如何,首先要做的就是搜集信息,在这一步我们需要打开手机上的GPU过度绘制调试。

看看这些过度绘制的地方,我们需要减少这些过度绘制,尤其是红色区域,这里说明一下各个颜色代表的意思。

现在深入了解一下UI是如何创建的,看看能否做一些清理,减少过度绘制,办法一就是清除不必要的背景和图片。例如我们想把Chatum背景中的这块区域变成绿色或者2倍过度绘制区域,为什么能实现这个效果呢?这是由于Chatum的BaseActivity采用了不透明白色背景的布局填充整个屏幕,我们喜欢这样,但是却与Android的材料主题默认设置相冲突,特别是窗口背景图片,这些都导致了不必要的过度绘制,作为一个开发者我们必须做一个决定,我们希望保留白色背景,材料主题其实没有任何意义,我们能做的一个优化就是把Activity的背景图片设置为null,我向大家展示一下在代码中如何实现,打开Chatum的BaseActivity我们看一下onCreate方法,使用下列声明取消原来的背景,就是这样,通过取消背景我们将过度绘制区域的颜色,由绿色变成了蓝色,变成了1倍过度绘制。

优化后:

我们看一下XML标记文件看看能否再做一些调整,我们可能已经注意到有三个XML文件,指定了Chatum的用户界面,有Chatum Latinum的BaseActivity,XML聊天片段还有聊天记录的单个XML,前面已经提过,我们想在这里保留白色背景,在这里我们什么都不做,但是其他两个XML文件能否做一些调整.

activity_chatum_latinum

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    android:id="@+id/activity_chatum_latinum_container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context=".MainActivity"
    tools:ignore="MergeRootFrame"/>
复制代码

fragment_chats.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    android:background="@android:color/white"
    tools:context=".MainActivity$ChatListFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/narrow_space"
        android:textSize="@dimen/large_text_size"
        android:layout_marginBottom="@dimen/wide_space"
        android:text="@string/header_text" />

    <ListView
        android:id="@+id/listview_chats"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:divider="@android:color/transparent"
        android:dividerHeight="@dimen/divider_height" />
</LinearLayout>
复制代码

chat_item.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="horizontal"
    android:paddingBottom="@dimen/chat_padding_bottom">

    <ImageView
        android:id="@+id/chat_author_avatar"
        android:layout_width="@dimen/avatar_dimen"
        android:layout_height="@dimen/avatar_dimen"
        android:layout_margin="@dimen/avatar_layout_margin" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#78A"
            android:background="@android:color/white"
            android:orientation="horizontal">

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:padding="@dimen/narrow_space"
                android:gravity="bottom"
                android:id="@+id/chat_author_name" />

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:textStyle="italic"
                android:padding="@dimen/narrow_space"
                android:id="@+id/chat_datetime" />
        </RelativeLayout>

        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="@dimen/narrow_space"
            android:background="@android:color/white"
            android:id="@+id/chat_text" />
    </LinearLayout>
</LinearLayout>
复制代码

在剩下的文件中应该可以找到4个不必要的背景,我们来查看一下,在BaseActivity中记住我们希望保留白色背景,现在我们来看聊天片段XML文件我们在这里声明了一个不必要的白色背景,我们并不需要这个声明因为可以使用BaseActivity的白色背景(fragment_chats.xml)。

现在来看聊天记录单个XML文件(chat_item.xml),这里有三个不必要的背景,我们来删除它们。

好了,现在我们来看看,过度绘制的情况有没有改善,我们的屏幕就应该现在这样,恰当的删除这些背景,干净多了,对不对?

很快就快大功告成了,但是我们还可以再做一个优化,注意头像区域仍存在过度绘制,因为我们绘制了一个方框然后再绘制头像图片,在没有获取到头像时我们才设置一个背景,我们可以使用一些条件码来实现,我们打开ChatAdapter.java,

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.mobileperf.render;

import android.content.Context;
import android.graphics.Color;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

import java.util.ArrayList;
import java.util.Date;

/**
 * A custom adapter that is backed by an array of Chat objects. References a TextView with the name
 * of a chat author (a Droid), a TextView with a chat's text, another TextView with the chat's
 * timestamp, and an ImageView for the chat author's avatar.
 */
public class ChatAdapter extends ArrayAdapter<Chat> {
    public ChatAdapter(Context context, ArrayList<Chat> chats) {
        super(context, 0, chats);
    }

    @Override
    public View getView(int position, View view, ViewGroup parent) {

        Chat chat = getItem(position);
        if (view == null) {
            view = LayoutInflater.from(getContext()).inflate(
                    R.layout.chat_item, parent, false);
        }

        // Find the UI widgets for a chat item.
        TextView chat_author_name = (TextView) view.findViewById(R.id.chat_author_name);
        TextView chat_text = (TextView) view.findViewById(R.id.chat_text);
        TextView chat_datetime = (TextView) view.findViewById(R.id.chat_datetime);
        ImageView chat_author_avatar = (ImageView) view.findViewById(R.id.chat_author_avatar);

        // Display the author's name using the color associated with the author.
        chat_author_name.setText(chat.getAuthor().getName());
        chat_author_name.setTextColor(chat.getAuthor().getColor());

        // Display the chat text.
        chat_text.setText(chat.getText());

        // Set the timestamp for the chat in "x minutes ago" format.
        chat_datetime.setText(DateUtils.getRelativeTimeSpanString(
                chat.getDatetime().getTime(),
                new Date().getTime(),
                DateUtils.MINUTE_IN_MILLIS,
                DateUtils.FORMAT_ABBREV_RELATIVE));


        // Display the chat author's avatar (a droid image) and a background color associated with
        // the author.
        if (chat.getAuthor().getAvatarId() != 0) {
            Picasso.with(getContext()).load(chat.getAuthor().getAvatarId()).into(
                    chat_author_avatar);
        }
        chat_author_avatar.setBackgroundColor(chat.getAuthor().getColor());

        return view;
    }
}
复制代码

这部分代码负责在个人聊天记录上传后进行填写,我们找到了getview方法,在底部这里我们找到一个逻辑操作,用来显示头像的同时设置背景颜色,我们来看看能否变得更智能一些,我们来写一段代码,在未获取到头像时仅用来设置背景颜色,然后我们将把背景颜色设置为透明,然后上传头像,好了,这就是我们更新后的代码。

修改前:

修改后:

注意,当为获取到头像时我们要做的是,在头像通常的位置加载透明色,然后,为头像设置真的背景色,剩下的就是获取到头像时的操作,我们恰当的加载头像,然后,我们将背景色设置为透明,这样我们就能将过度绘制最小化。好了,我们来看看情况有没有改善。

很好,我们可以看到头像区域,在更新代码后过度绘制减少了,好了,这就是我们最后的优化。

我们总结下,优化前过度绘制非常严重,首先要做的,就是将背景图片设置为null,其次,就是清除XML文件中,不必要的背景声明,最后,我们只在为获取到头像时,显示背景颜色。经过这些优化,我们再来看看,过度绘制的情况相比开始有了很大改善。

注意: 有些过度绘制对于运行性能,可能是必要的也是可以接受的,比如说Android的ActionBar,但是,如果我们希望应用体验更进一步,我们可以考虑尽可能地减少过度绘制。

clipRect和quickReject

值得指出的是,Android系统知道过度绘制是个麻烦,Android会设法避免绘制,那些在最终图片中不显示的UI组件,这种优化类型,称作剪辑,它对UI性能非常重要。如果我们能确定某个对象会被完全阻挡,那就完全没有必要绘制它,事实上,这是最重要的性能优化方法之一,而且是有Android系统执行的,但是不幸的是,这一技术无法应对复杂的自定义的View,系统无法检测onDraw具体会执行什么操作。这些情况下,底层系统无法识别如何去绘制对象,系统很难将覆盖的View,从渲染管道中清除。例如,这叠牌只有最上面的牌是完全可见的,其他牌都被挡住了,这就意味着绘制那些重叠的像素就是浪费时间。

为了解决这个问题,我们可以使用Canvas类的一些特别方法去帮助Android系统识别被遮挡的不需要绘制的部分,最有用的办法是Canvas.clipRect,它可以帮助我们识别给定View的图片边界,边界之外区域的任何绘制操作会被忽视,如果碰到此类重叠的View,这个方法特别好用,就像例子中的纸牌。如果我们知道自定义View可见部分的范围,或者知道遮挡部分的范围,我们就可以定义ClipRect边界,可以避免遮挡区域的任何绘制操作,ClipRect API帮助系统识别出无需绘制的区域,对自定义View进行剪辑时,这个方法也很有用处。比如说,如果我们知道绘制对象在剪辑矩形之外,这个方法就非常好用,幸运的是,我们不必亲自搞清楚重叠逻辑,我们可以使用Canvas.quickReject方法,判定给定区域是否完全在剪辑矩形之外,这种情况下可以忽略全部绘制工作。现在我们来看一个相关案例,我们对它做一些改进。

布局优化

是时候来了解一下渲染管道中的CPU部分,为了在屏幕上绘制某个东西,Android通常将高级XML文件转换为GPU能够识别的对象,然后显示在屏幕上,这个操作是在DisplayList的帮助下完成的,DisplayList持有所有要交给GPU绘制到屏幕上的数据信息,包含GPU要绘制的全部对象的信息列表,还有执行绘制操作的OpenGL命令列表,在某个View第一次需要被渲染时,DisplayList会因此被创建,当这个View要显示到屏幕上时,我们将绘制指令提交给GPU来执行DisplayList,我们下次渲染这个View时,比如说位置发生了变化,我们仅仅需要执行DisplayList就够了,但是如果我们修改了View的某些可见组件的内容,那么之前的DisplayList就无法继续使用了,这时我们要重新创建一个DisplayList,重新执行渲染指令并更新到屏幕上。

请注意 :任何时候View的绘制内容发生变化,都需要重新创建DisplayList并重新执行指令更新到屏幕,这个流程的表现性能,取决于我们的View的复杂程度,取决于视觉变化的类型,同时对渲染管道也会产生一些影响。

举例说,假如某个文本框尺寸突然变成当前的两倍,在改变尺寸前,需要通过父View重新计算,并摆放其他子View的位置,在这种情况下我们改变了某个View,后面就会有很多工作要做,这些类型的视觉变化需要渲染管道的额外工作,当我们的View的尺寸变化时,触发了测量操作,会经过整个View Hierarchy,询问各个View的新尺寸,我们一旦改变了View的大小就会触发上述过程,无论是填充或者图片尺寸、设置文本大小、宽度、高度等等,如果我们是改变对象位置或者询问布局,或者某个View重新摆放子View都会触发布局操作,会触发整个Hierarchy重新计算对象在屏幕上的新位置。

现在Android运行系统已经非常善于处理记录并执行渲染管道,除非我们要处理自定义View或者同时需要绘制太多View,其他情况下一般不会耗费太多时间,测量和布局操作性能也很好,但是当我们的View Hierarchy失控时也更容易出现问题,执行这些功能的时间是和我们的View Hierarchy中需要处理的节点数成正比的,系统需要处理的View越多处理时间就越长。 某些View可能比其他View要耗费更多时间,造成这种浪费的首要原因是,View Hierarchy中包含太多的无用View,这些View根本不会显示在屏幕上,一旦触发测量操作和布局操作只会拖累应用程序的性能表现,幸好有一款叫做Hierarchy Viewer工具,它可以帮助我们查找并修复这些流氓View,我们来看看。

Hierarchy Viewer工具

关于Hierarchy Viewer工具的介绍和使用不在本次内容讲解之内,关于这块的资料网上已经很多了。

可以参考:Android UI 优化——使用HierarchyViewer工具(www.cnblogs.com/xyzlmn/p/36…

嵌套结构的性能评测

我们可能已经知道,在我们创建Android用户界面时,应该让我们的布局尽可能简单和扁平化,我有一些很好的建议,请记住庞大的布局十分浪费资源,每个附加嵌套布局和内置视图都会直接影响我们的应用程序的性能和响应灵敏性,因此请记住,我们应该了解我们的应用程序的行为模式,现在我们要返回到Android Device Monitor,我已经打开Hierarchy Viewer视窗,和以前一样我们跳转到这里的窗格,选择我们的设备然后选择想要查看的活动。

在这个例子中我们要看一下这个根节点,这是我们的线性布局,这是跟视图群组将会显示这两行,我们在这里可以看到它们,请注意来自于这个父级线性布局的两个不同子元素,其中一个表示我们聊天界面第一行,但是它使用嵌套线性布局实现,第二个对应于布局中的第二行,这次不使用嵌套设计,我们使用扁平化设计,使用相对布局视图群组,这对应于XML中的代码,进入Android Studio查看我们的源代码,打开activity_compare_layout.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="wrap_content"
              android:orientation="vertical">
    <!--Version 1 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="16dp">

        <ImageView
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:src="@drawable/alex"
            android:layout_margin="10dp"/>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Line 1:悲观者说,希望是地平线,就算看得见,也永远走不到;"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Line 2:乐观者说,希望是启明星,即使摘不到,也能告诉人们曙光就在前头。"/>
        </LinearLayout>

    </LinearLayout>

    <!--Version 2 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp">

        <ImageView
            android:id="@+id/iv_icon"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:src="@drawable/chris"
            android:layout_margin="10dp"/>

        <TextView
            android:id="@+id/tv_line1"
            android:layout_toRightOf="@id/iv_icon"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Line 1:忙碌是一种幸福,让我们没时间体会痛苦;"/>

        <TextView
            android:id="@+id/tv_line2"
            android:layout_toRightOf="@id/iv_icon"
            android:layout_below="@id/tv_line1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Line 2:奔波是一种快乐,让我们真实的感受生活;疲惫是一种享受,让我们无暇空虚。"/>
            
    </RelativeLayout>

</LinearLayout>

复制代码

现在重新在屏幕上显示以便于进行比较,我们看到一个父级容器它是一个由垂直方向组成的线性布局,因此这些控件将会从上到下排列,现在我想让我们注意这里,这是我们的第一个聊天行,我们的实现方法不是使用结构化或嵌套布局,这种方法更加直观,逻辑性很强,例如,我们从一个水平性质的父级线性布局开始,在左侧我们将设置一个ImageView,在右侧创建另外一个嵌套线性布局以容纳我们的文本,但是在这个例子中,方向是垂直的而不是水平的,它代表第一个条目,接下来我们可以看到聊天模板的第二行,与使用嵌套结构不同我们决定采用扁平化布局,使用相对位置来描述它们。这样做对于性能有什么影响?让我们返回Hierarchy Viewer

从渲染过程的角度来说线性布局设计比相对布局更慢一些,与相对布局比较,线性布局需要更多的资源开销,这里全部是绿色。如果我们有机会采取扁平化布局,我们应该想办法尽可能使用它。

Chatum Latinum优化前:

Chatum Latinum优化后:

关注我的技术公众号"程序员驿站",每天都有优质技术文章推送,微信扫一扫下方二维码即可关注:

image

猜你喜欢

转载自juejin.im/post/5c7a60b6e51d453d60532554