Androd 勇闯高阶性能优化之布局优化篇
作者:帅次 发布时间:2023-01-22 12:12:15
Android的布局管理器本身就是个UI组件,所有的布局管理器都是ViewGroup的子类,而ViewGroup是View的子类,所以布局管理器可以当成普通的UI组件使用,也可以作为容器类使用,可以调用多个重载addView()向布局管理器中添加组件,并且布局管理器可以互相嵌套,当然不推荐过多的嵌套 (如果要兼容低端机型,最好不要超过5层)。
🔥 布局层级管理
让咱们一起了解一下每当系统绘制一个布局时,都会发生一些什么。这一过程由两个步骤完成:
💥 绘制(Measurement)
1:根布局测量自身。
2:根布局要求它内部所有子组件测量自身。
3:所有自布局都需要让它们内部的子组件完成这样的操作,直到遍历完视图层级中所有的View。
💥 摆放(Positioning)
1:当布局中所有的View都完成了测量,根布局则开始将它们摆放到合适的位置。
2:所有子布局都需要做相同的事情,直到遍历完视图层级中所有的View。
💥 背景设置产生的过度绘制
组件背景:每个组件每设置一次背景, 该组件的区域就会增加一层绘制 , 如 LinearLayout 设置背景颜色 , 里面的 TextView 设置背景颜色 , 都会增加该组件区域内的过渡绘制 ;
主题背景:Activity 界面的主题背景,会增加一次 GPU 绘制 ;
不要随意给布局中的 UI 组件设置背景 。如 ImageView 设置一张图片,会增加一次绘制 ,再给该 ImageView 组件设置背景颜色, 那么又会增加一次绘制, 那么该 ImageView 组件肯定过渡绘制了。
💥 小结
当某个View的属性发生变化(如:TextView内容变化或ImageView图像发生变化),View自身会调用View.invalidate()方法(必须从 UI 线程调用),自底向上传播该请求,直到根布局(根布局会计算出需要重绘的区域,进而对整个布局层级中需要重绘的部分进行重绘)。布局层级越复杂,UI加载的速度就越慢。因此,在编写布局的时候,尽可能地扁平化是非常重要的。
FrameLayout和TableLayout有各自的特殊用途,LinearLayout 和 RelativeLayout 是可以互换的,ConstraintLayout和RelativeLayout类似。也就是说,在编写布局时,可以选择其中一种,咱们可以以不同的方式来编写下面这个简单的布局。
🔥 小实验(多种方式实现同一布局)
💥 LinearLayout
第一种方式是使用LinearLayout,虽然可读性比较强,但是性能比较差。由于嵌套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:orientation="vertical">
<View
android:id="@+id/view_top_1"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/color_666666"/>
<View
android:id="@+id/view_top_2"
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@color/teal_200"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<View
android:id="@+id/view_top_3"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/color_FF773D"/>
<View
android:id="@+id/view_top_4"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/purple_500"/>
</LinearLayout>
</LinearLayout>
LinearLayout视图层级如下所示:
💥 使用RelativeLayout
第二种方法使用RelativeLayout,在这种情况下,你不需要嵌套其他ViewGroup,因为每个子View可以相当于其他View,或相对与父控件进行摆放。
<?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">
<View
android:id="@+id/view_top_1"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/color_666666"/>
<View
android:id="@+id/view_top_2"
android:layout_width="200dp"
android:layout_below="@id/view_top_1"
android:layout_height="100dp"
android:background="@color/teal_200"/>
<View
android:id="@+id/view_top_3"
android:layout_width="100dp"
android:layout_below="@id/view_top_2"
android:layout_height="100dp"
android:background="@color/color_FF773D"/>
<View
android:id="@+id/view_top_4"
android:layout_width="100dp"
android:layout_below="@id/view_top_2"
android:layout_toRightOf="@id/view_top_3"
android:layout_height="100dp"
android:background="@color/purple_500"/>
</RelativeLayout>
RelativeLayout视图层级如下所示:
通过两种方式,可以很容易看出,第一种方式LinearLayout需要3个视图层级和6个View,第二种方式RelativeLayout仅需要2个视图层级和5个View。
当然,虽然RelativeLayout效率更高,但不是所有情况都能通过相对布局的方式来完成控件摆放。所以通常情况下,这两种方式需要配合使用。
注意:为了保证应用程序的性能,在创建布局时,需要尽量避免重绘,布局层级应尽可能地扁平化,这样当View被重绘时,可以减少系统花费的时间。在条件允许的情况下,尽量的使用RelativeLayout和ConstraintLayout,而非LinearLayout,或者用GridLayoutl来替换LinearLayout。
咱们最常使用的是ViewGroup是LinearLayout,只是因为它很容易看懂,编写起来简单,所以它就成了Android开发的首选。出于这个原因,Google推出了一个全新的ViewGroup。在适当的时候时候使用它,可以减少冗余,它就是网格布局GridLayout下。
🔥 布局复用(<include/>和 <merge/> )
Android 提供了一个非常有用的标签。在某些情况下,当你希望在其他布局中用一些已存在的布局时, 标签可通过制定相关引用ID,将一个布局添加到另一个布局。
比如自定义一个标题栏,那么可以按照下面的方式,创建一个可重复用的布局文件。
<?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">
<View
android:id="@+id/view_top_1"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/color_666666"/>
</RelativeLayout>
接着,将标签放入相应的布局文件中,替换掉对应的 View:
<?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 layout="@layout/include_layout"/>
...
</RelativeLayout>
这么一来,当你希望重用某些View时,就不用复制/粘贴的方式来实现,只需要定义一个layout文件,然后通过 引用即可。
但是这样做,可能会引入一个冗余的ViewGroup(重用的布局文件的根视图)。为此,Android 提供了另一个标签,用来帮我们减少布局冗余,让层级变得更加扁平化。我们只需要将可重用的根视图,替换为 标签即可。如下所示:
<?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="wrap_content">
<View
android:id="@+id/view_top_1"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/color_666666"/>
</merge>
如此一来,就没有了冗余的视图控件,因为系统会忽略标签,并将标签中的视图直接放置在相应的布局文件中,替换标签。
注意:使用此标签时,需要记住它的两个主要限制:
1、它只能作为布局文件的跟来使用。
2、每次调用LayoutInflater.inflate()时,必须为布局文件提供一个View,作为它的父容器:LayoutInflater.from(this).inflate(R.layout.merge_layout,parent,true);
🔥 ViewStub
ViewStub是一个不可见的零大小View,可以作为一个节点被加入布局文件,但他关联的布局,知道运行时通过调用 ViewStub.inflate() 或 View.setVisibility(View.VISIBLE) 方法,才会被绘制。
先看效果图:
💥 ViewStub 设置
<ViewStub android:id="@+id/viewStub"
android:inflatedId="@+id/subTree"
android:layout="@layout/activity_imageview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
💥 显示
上方 ViewStub 所关联的布局 activity_imageview 并不会被实例化(不要调用布局内的控件,因为还没加载会报空指针异常),只有程序在运行期间调用了以下方法:
findViewById(R.id.viewStub).setVisibility(View.VISIBLE);
((ViewStub)findViewById(R.id.viewStub)).inflate();
在这期间不要调用关联布局内的控件,因为还没呗加载没有
一旦 ViewStub 变成 visible 或者被 inflate,它便不再可用(Id:viewStub没了),因为它在布局层级中的位置已经实例化出来的布局所替代,因为不能被访问,而应该使用 android:inflatedId 属性中的ID。如下:
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_view:
break;
case R.id.btn_scheme:
//加载,选择一种即可。
findViewById(R.id.v_stud).setVisibility(View.VISIBLE);
//((ViewStub)findViewById(R.id.v_stud)).inflate();
//加载后layout所用ID
subTree = findViewById(R.id.subTree);
findViewById(R.id.btn_iv_basis).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"我是 ViewStud 加载的控件",Toast.LENGTH_SHORT).show();
}
});
break;
case R.id.btn_invisible:
subTree.setVisibility(View.INVISIBLE);
break;
case R.id.btn_visible:
subTree.setVisibility(View.VISIBLE);
break;
case R.id.btn_init:
//ViewStub变为可见后再次调用会报空指针,因为id:viewStub 已经不存在了。
View viewStub = findViewById(R.id.viewStub);
viewStub.setVisibility(View.GONE);
break;
}
}
💥 小结
ViewStub 非常有用,我们可以通过 ViewStub 来延迟部分 View 的加载,缩短首次加载时间,以及减少一些不必要的内存分配。
🔥 自定义组件优化
在自定义View时需要注意,避免犯以下的性能错误:
在非必要时,对View进行重绘。
绘制一些不被用户所看到的的像素,也就是过度绘制。(被覆盖的地方)
在绘制期间做了一些非必要的操作,导致内存资源的消耗。
💥 优化
View.invalite()是最最广泛的使用操作,因为在任何时候都是刷新和更新视图最快的方式。
在自定义View时要小心避免调用非必要的方法,因为这样会导致重复强行绘制整个视图层级,消耗宝贵的帧绘制周期。检查清楚View.invalite()和View.requestLayout()方法调用时间位置,因为这会影响整个UI,导致GPU和它的帧速率变慢。
避免过渡重绘。为了避免过渡重绘,我们可以利用Canvas方法,只绘制控件中所需要的部分。整个一般在重叠部分或控件时特别有用。相应的方法是Canvas.clipRect()(指定要被绘制的区域);
在实现View.onDraw()方法中,不应该在方法内及调用的方法中进行任何的对象分配。在该方法中进行对象分配,对象会被创建和初始化。而当View.onDraw()方法执行完毕时。垃圾回收器会释放内存。如果View带动画,那么View在一秒内会被重绘60次。所以要避免在View.onDraw()方法中分配内存。
永远不要在View.onDraw()方法中及调用的方法中进行内存分配,避免带来负担。垃圾回收器多次释放内存,会导致卡顿。最好的方式就是在View被首次创建出来时,实例化这些对象。
布局优化到这里就结束了,还是那句话后面如果有更好的方案会及时的添加进去,如果有老大有其他方案也可以留言哈,感谢!
来源:https://blog.csdn.net/g984160547/article/details/120823663
猜你喜欢
- 一、SpringBoot中的main方法注入service在springboot中使用main方法常规无法注入service,因为以后也可能
- 1、环境搭建创建一个SpringBoot项目,普通的web项目就可以了,我这里使用的是start.aliyun引入依赖:(1)老演员了不多说
- 提到数组大家肯定不会陌生,但我们也知道数组有个缺点就是在创建时就确定了长度,之后就不能更改长度。所以Java官方向我们提供了ArrayLis
- 参数设置java程序启动参数 -D是用来做什么的呢? 官方解释如下:Set a system property value. If valu
- 原因分析@Anysc注解会开启一个新的线程,主线程的Request和子线程是不共享的,所以获取为null在使用springboot的自定带的
- C# 中同样支持多维数组(也可称为矩形数组),它可以是二维的,也可以是三维的,多维数组中的数据以类似表格(行、列)的形式存储,因此也被称为矩
- 主要为以下实现步骤:1.绑定域名先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。(特别提示不需要加上http或
- 本文我们将讲解一下对于“大对象”的优化。这里的“大对象”,是
- 定义Builder模式是一步步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细的控制对象的构建过程。该模式是
- 概述从今天开始, 小白我将带大家开启 Java 数据结构 & 算法的新篇章.优先队列优先队列 (Priority Queue) 和队
- DeferredResult的超时处理,采用委托机制,也就是在实例DeferredResult时给予一个超时时长(毫秒),同时在onTime
- 背景SpringBoot的应用监控方案比较多,SpringBoot+Prometheus+Grafana是目前比较常用的方案之一。它们三者之
- 这篇文章主要介绍了SpringBoot如何读取war包jar包和Resource资源,文中通过示例代码介绍的非常详细,对大家的学习或者工作具
- intellij idea是一款非常优秀的软件开发工具,它拥有这强大的插件体系,可以帮助开发者完成很多重量级的功能。今天,我们来学习一下如何
- 一,引入dll1.ServiceStack.Common.dll2.ServiceStack.Interfaces.dll3.Service
- 目录基于Java的guava开源库工具类1、guava的maven配置引入 2、LoadingCache3、Multimap 和
- 前言:在 Java 语言中,提高程序的执行效率有两种实现方法,一个是使用线程、另一个是使用线程池。而在生产环境下,我们通常会采用后者。为什么
- 写在前面作为程序员,多多少少都会遇到一些内存溢出的场景,如果你还没遇到,说明你工作的年限可能比较短,或者你根本就是个假程序员!哈哈,开个玩笑
- java 事务详解一、什么是事务事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取。事务的正确执行使得数据库从一种
- 问题现象今天使用mybatis遇到个很奇怪的问题,我使用一个参数@param("threshold"),类型是java的