android UI优化的基本原理和实战方法
任何Android应用都需要UI跟用户交互.UI是否好坏更是直接影响到用户的体验.如今UI的优化视乎是应用开发中一个绕不过去的话题。所以本篇文章小编带大家全面了解Android ui优化的主要知识和优化方法。
一、UI优化
UI优化知识点主要分为三部分:
第一部分,系统为我们做的优化。由于前端中UI展示的特殊性和重要性,Android团队也是在不断想办法提高UI方面的渲染速度,所以也是更新了很多系统优化方案,比如:
硬件加速、黄油计划、RenderThread。
第二部分,我们可以具体实施的优化方案。主要包括:
java代码布局、View重用、异步创建View、xml布局优化、异步布局框架Litho、屏幕适配、Flutter、Jetpack Compose
第三部分,工具使用,主要包括:
Choreographer、monitor、Systrace
1.1 系统做的优化
1.1.1 硬件加速
之前我们说过,一个图形的绘制是CPU,GPU和屏幕三方合作的结果。
在Android3.0之前,还没有硬件加速,都是通过CPU进行数据计算,然后通过Skia库进行软件绘制,但是CPU对于图形处理并不高效。
于是从3.0开始,Android支持了硬件加速,到Android4.0默认开启硬件加速。
开启硬件加速后,就是由CPU进行图形缓存数据的绘制。这样CPU和GPU就能比较好的分工,各司其职了。CPU用于控制复杂绘制逻辑、构建或更新DisplayList(基础元素);GPU用于完成图形计算、渲染DisplayList(基础元素)。
但是硬件加速也是有缺点的:
启用硬件加速需要更多资源,因此应用会占用更多内存。
比较低的版本,由于有些Canvas API还没有支持,所以使用硬件加速可能会有问题。那么我们就可以手动关闭某个view的硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Project Butter
黄油计划,你有可能没怎么听说,但是其实之前两章内容都提到过,Android4.1之后,Google提出了黄油计划,主要包括两个内容:
- VSYNC
- Triple Buffering(三重缓存)
这些都熟悉了吧,上两节都说过的,这里再简单提一下:
- VSYNC
垂直同步信号,每当收到这个信号后,CPU就开始准备Buffer数据,并在16ms之内和GPU把屏幕需要的缓存数据准备好。
- Triple Buffering(三重缓存)
在Android4.1之前,是双缓存机制,大部分是没问题的。但是当CPU/GPU绘制过程较长,超过一个vsync信号周期,一般是16ms,就会导致丢帧,CPU无法使用被GPU或者屏幕占用的缓存区。如果下一帧绘制如果又超时,那么又会丢帧。
所以再加上一个缓存区,这样,CPU、GPU、Display三者都有各自的缓存区,互不影响,就能保证时间的最大化利用,也就能减少上述的情况了。
RenderThread
RenderThread是在Android5.0提出的,从这个名字就能知道,它是一个线程,一个专门执行GL命令的线程,也就是一部分的绘制工作。
有了它之后,当CPU处理数据给GPU后,就不需要等GPU渲染完毕了,而是将一些绘制任务交给RenderThread,这样就能减少主线程的工作,保证画面的流畅。同时还提供了RenderNode,用来做view的属性封装,渲染帧的信息等等。
1.2 优化方案
1.2.1 java代码布局
我们一般都是用XML文件进行布局,但是XML解析也是很耗时的,并在这个解析过程在主线程进行。
所以我们有的时候也许可以通过Java代码或者kotlin进行View的创建?
理论中,这样确实能减少布局加载的消耗时间,但是Java代码创建View太麻烦了,而且无法可视化。
当然,也有一些库可以帮助我们将xml代码转换成java代码,比如X2C(github.com/iReaderAndr… ),但是它并不支持所有的情况,比如merge标签,appCompat兼容控件等等。
所以我们需要在这之中找到平衡点,有的时候,UI简单并且要求高性能的前提下,我们可以试试用这样的方法,即用java代码代替XmL代码。
new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {@Overridepublic void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) {//回调给主线程setContentView(view);}
});
1.2.2 View重用
参照Recyclerview的做法,我们也可以将一些常用的view保存到缓存池中,这样在不同的界面中就能复用缓存池里面的view。
1.2.3 异步创建view
这是Google提出的一个方案——AsyncLayoutInflater。它可以异步加载布局文件,并且回调给主线程,从而减少主线程耗时。简单贴下主要代码:
new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {@Overridepublic void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) {//回调给主线程setContentView(view);}
});
1.2.4 xml布局优化
在写xml布局文件的时候,我们要做的也有很多,比如:
减少布局嵌套。多使用ViewStub、Merge、ConstraintLayout来代替。
优化开销。RelativeLayout和 使用weight的LinearLayout 开销比较大,建议使用ConstraintLayout,LinearLayout代替。
1.2.5 异步布局框架Litho
Litho是Facebook开源的一款在Android上高效建立UI的声明式框架。
主要有以下特点:
1)声明式:它使用了声明式的API来定义UI组件。
2)异步布局:它把 measure 和 layout 都放到了后台线程,只留下了必须要在主线程完成的 draw,这大大降低了 UI 线程的负载
3)视图扁平化:由于 Litho 使用了自有的布局引擎(Yoga),在布局阶段就可以检测不必要的层级、减少 ViewGroups,来实现 UI 扁平化。
4)优化 RecyclerView:Litho 还优化了 RecyclerView 中 UI 组件的缓存和回收方法。
1.2.6 屏幕适配
关于屏幕适配问题,也是老生常谈了。主要有以下几种方案:
- dp适配方案。
这是系统自带的适配单位,dp是基于屏幕物理分辨率一个抽象的单位,用于说明与密度无关的尺寸和位置。所以它能在不同分辨率的手机上有相对大小的适配性。计算公式是:px=dp * (dpi/160)。但是dpi有可能会被人为调整(比如几部相同分辨率不同尺寸的手机的ppi可能分别是是430,440,450,那么在Android系统中,可能dpi会全部指定为480),所以还是有可能在一些设备上出现适配问题。
- 宽高限定符适配方案。
简单地说,这个方案就是穷举市面上所有的Android手机的宽高像素值。然后找到对应的文件夹使用下面的资源文件所对应的px值。
但是这方案有个缺陷,就是必须精确命中才行。比如1920x1080的手机就一定要找到1920x1080的限定符,否则就只能用统一的默认的dimens文件了。
所以容错性太低,不推荐。
- smallestWidth适配方案。
这个方案就是通过手机的宽度值来找到对应限定符文件夹下的资源文件,可以看做宽高限定符屏幕适配方案的升级版。
假如我们的设计图宽为360dp,那么就创建values-sw360dp文件夹,并添加资源文件:
<?xml version="1.0" encoding="UTF-8"?>
<resources><dimen name="dp_1">1dp</dimen><dimen name="dp_2">2dp</dimen><dimen name="dp_3">3dp</dimen>...<dimen name="dp_359">359dp</dimen><dimen name="dp_360">360dp</dimen>
</resources>
很简单,分为360份,然后我们实际写布局文件的时候,直接引用对应的dimen值即可。
然后新建其他设备宽度的文件夹,并在每个文件夹里添加对应的资源文件,这里以400dp为例:
├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├──values-sw320dp
│ ├── ├──values-sw400dp
│ ├── ├──...
│ ├── ├──values-sw640dp
<?xml version="1.0" encoding="UTF-8"?>
<resources><dimen name="dp_1">1.1111dp</dimen><dimen name="dp_2">2.2222dp</dimen><dimen name="dp_3">3.3333dp</dimen><dimen name="dp_4">4.4444dp</dimen>...<dimen name="dp_359">398.8889dp</dimen><dimen name="dp_360">400.0000dp</dimen>
</resources>
也就是说,所有的设备都分为360份了,这样就能保证在不同宽度设备上都能有差不多的效果。
如果我们的设备宽度为400dp,那么就会调用values-sw400dp对应的资源文件,如果找不到,就会向下查找。比如我们宽度是402dp,找不到对应的,就会向上找到400dp对应的资源文件,所以也有比较好的容错性。也是一个比较好的适配方案。
当然这种重复性工作肯定不需要我们自己手动去实现,有专门的插件可以生成相应的文件和文件夹。
今日头条适配方案。
这个大家应该都很熟悉了,主要是通过动态修改density值来保证所有设备的屏幕宽度都是固定的dp值。用到的公式就是px = density * dp。
比如设计图宽为360dp,我们只要保证所有设备的宽度都是360dp就能适配了。而宽度的px值我们是已知的,所以就是要修改这个 density 值来完成我们的适配目的。具体代码我就不贴了,网上很多。
这种方案侵入性低,使用方便,是个不错的适配方案。
1.2.7 Flutter
Flutter是 Google 推出并开源的移动应用开发框架,开发者可以通过 Dart 语言开发 App,一套代码同时运行在 iOS 和 Android 平台。
Flutter框架现在也是特别火,实际运用到很多的大厂项目,比如闲鱼今日头条。它相对于Android其实是另外一套APP架构了,它没有基于系统本身的渲染引擎,而是app中自带Skia引擎,虚拟机也是使用的Dart虚拟机。
主要有以下几个特点:
跨平台:现在flutter至少可以跨5种平台,常见的平台:MacOS,Windows ,Linux ,Android ,iOS ,到目前为止,Flutter算是支持平台最多的框架了。良好的跨平台性,大大减少了开发成本。
丝滑般的体验:使用Flutter内置的Material Design(android风格)和Cupertino(ios风格)风格组件,以及丰富的motion API,平滑而自然的滑动效果和平台感知,为用户带来全新的体验。
响应式框架:使用一系列基础组件和响应式框架,可以轻松构建用户界面。使用功能强大且灵活的API可以实现复杂的界面效果。
支持插件:使用插件可以访问平台本地API,如相机,蓝牙,WIFI等等。借助现有的Java,swift ,object c , c++代码实现对原生系统的调用。
60fps超高性能:Flutter编写的应用可以达到60fps(每秒传输帧数)。Flutter采用GPU渲染技术,所以性能很好。完全可以胜任游戏开发。
1.2.8 Jetpack Compose
Jetpack Compose 是用于构建原生 Android 界面的新工具包
原来我们的布局文件都是写在xml文件中的,现在提供了一种新的view构建方式,也就是Compose。
它是一种声明式UI,不再使用xml,而是使用kotlin进行UI布局。其实就跟我们之前提到的一点,用java代码去构建view一样的效果。这样就减少了xml解析的时间,提高了效率。
声明式UI。指的是只需要把界面声明出来,不需要手动更新。比如我们这里的Compose只需要写一遍,后续的UI改变会随着变量自动更新。而传统的xml布局方式就无法做到这一点,属于命令式UI,需要我们手动命令纸牌屋UI的修改。
官方也是宣称有以下几点优势:
更少更直观的代码,更强大的功能,能提高开发速度。
最后贴一段代码,感受下Compose的写法:
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Greeting("Android")}}
}
@Composable
fun Greeting(name: String) {Text (text = "Hello $name!")
}
1.3 工具篇
1.3.1 Choreographer
Choreographer其实也是一个监控应用帧率的工具。它主要有以下特性:
- 能获取整体的帧率。
- 能在线上使用。
- 获取的帧率几乎是实时的。
主要原理就是利用postFrameCallback计算两次绘制的间隔时间,简单贴下代码:
private long mLastFrameTime;
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {@Overridepublic void doFrame(long frameTimeNanos) {if (mLastFrameTime == 0) {mLastFrameTime = frameTimeNanos;}float diff = (frameTimeNanos - mLastFrameTime) / 1000000.0f;//得到毫秒,正常是 16.66 msif (diff > 500) {double fps = (((double) (mFrameCount * 1000L)) / diff);mFrameCount = 0;mLastFrameTime = 0;Log.d("doFrame", "doFrame: " + fps);} else {++mFrameCount;}Choreographer.getInstance().postFrameCallback(this);}
});
1.3.2 LayoutInspector/Android Device Monitor
LayoutInspector是AndroidStudio种的一个布局检查器,可以通过Tools > Layout Inspector找到,他可以检查应用中的某个界面的视图结构,但是无法查看非调式状态的应用。
如果要看其他应用的布局情况,可以使用Android Device Monitor,在Android Studio 3.1 以后,需要单独从文件夹打开了:
android-sdk/tools/monitor
1.3.3 Systrace
Systrace是分析Android性能问题的神器,获取Systrace文件的方式有两种:
一是AndroidSDK/tools目录下,通过monitor.bat用Android Device Monitor可视化工具得到。
二是通过python脚本获取。
以上就是关于UI优化的详细内容,希望对大家有所帮助。更多关于Android性能优化的知识可参考App性能优化手册。
相关文章:

android UI优化的基本原理和实战方法
任何Android应用都需要UI跟用户交互.UI是否好坏更是直接影响到用户的体验.如今UI的优化视乎是应用开发中一个绕不过去的话题。所以本篇文章小编带大家全面了解Android ui优化的主要知识和优化方法。 一、UI优化 UI优化知识点主要分为三部分: 第一部分,…...

指针的进阶【中篇】
文章目录📀4.数组参数💿4.1.一维数组传参💿4.2.二维数组传参📀5.指针参数💿5.1.一级指针传参💿5.2.二级指针传参📀6.函数指针💿6.1. 代码1💿6.2. 代码2📀7.函…...

华为OD机试题,用 Java 解【删除字符串中出现次数最少的字符】问题
最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…...

【C语言每日一题】猜名次
【C语言每日一题】—— 猜名次😎😎😎 💡前言🌞: 💛猜名次题目💛 💪 解题思路的分享💪 😊题目源码的分享😊 👉 本菜鸡…...

89. 格雷编码
89. 格雷编码题目数学公式动态规划回溯题目 传送门:https://leetcode.cn/problems/gray-code/ 数学公式 int gray(int n) { // 计算第n位格雷码公式return n ^ (n >> 1); }然后你写一个for循环,计算从1到n的所有格雷码,添加到答…...

线性回归算法和逻辑斯谛回归算法详细介绍及其原理详解
相关文章 K近邻算法和KD树详细介绍及其原理详解朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解决策树算法和CART决策树算法详细介绍及其原理详解线性回归算法和逻辑斯谛回归算法详细介绍及其原理详解 文章目录相关文章前言一、线性回归二、逻辑斯谛回归总结前言 今天给大家…...

【网络原理8】HTTP请求篇
在上一篇文章当中,我们也提到了什么是HTTP。 每一个HTTP请求,都会对应一个HTTP响应。 下面这一篇文章,将聊一下HTTP请求的一些内容 目录 一、URL 第一部分:协议名称 第二部分:认证信息(新的版本已经没有了) 第三部分…...

Playbook的用法
目录 Playbook Playbook 与 Ad-Hoc 对比 YAML 语言特性 YAML语法简介 支持的数据类型 写法格式 1 scalar 标量 建议缩进两个空格,可多 2 Dictionary 字典 3 List 列表 三种常见的数据格式 Playbook 核心组件 不要用 tab 可以#注释 hosts remote_us…...

APP优化 —— MMAP内存映射
mmap 一种内存映射文件的方法 mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。 头文件 <sys/mman.h> 函数原型 v…...

paddle.vision 与 torchvision 中的box NMS使用方式
torchvision 中有多个用于计算 BBox NMS 的 API, 在本篇氵文中, 使用 torchvision.ops.boxes.batched_nmspaddle.vision 中通过 paddle.vision.ops.nms 来进行多个 Box 的 NMS 操作 1. torchvision 中 batched_nms 操作 torchvision batched_nms def batched_nms(boxes: to…...

php mysql校园帮忙领取快递平台
1、后台管理员用户名hsg 密码hsg 2、开发语言:PHP,数据库为MySql 3、数据库连接字符串在conn.php中修改 4、运行环境wamp5.1.7或者appserv2.5.9 5.程序编码gbk.不支持php5.3以上版本 6.本人发布的程序一律享有免费运行一次…...

C/C++开发,无可避免的内存管理(篇二)-约束好跳脱的内存
一、养成内存管理好习惯 1.1 养成动态对象创建、调用及释放好习惯 开发者手动接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。如果你的项目是c项目&am…...

【Java】让我们对多态有深入的了解(九)
目录 (1)接口的基本介绍编辑 (2)接口的注意事项和细节 1.接口不能被实例化 2.接口中所有方法是public方法,接口中的抽象方法,可以不用abstract修饰 3.一个普通类实现接口,必须将接口所有…...

12 个适合做外包项目的开源后台管理系统
1.D2admin 开源地址:https://github.com/d2-projects/d2-admin 文档地址:https://d2.pub/zh/doc/d2-admin/ 效果预览:https://d2.pub/d2-admin/preview/#/index 开源协议:MIT 2.vue-element-admin 开源地址:https…...

鼠标更换指针图案和更改typora的主题
鼠标更换指针图案 由此偶然看见好几个朋友都使用了新的图案替换掉了原有的鼠标图案,今天寻思自己也换一个图案 主要是觉得鼠标大一点儿会好看一些,所以就找了一些教程 官方教程,小的变动 当然最多的是官方教程,如果你只是想要…...

【洛谷 P1563】[NOIP2016 提高组] 玩具谜题(模拟+结构体数组+指针)
[NOIP2016 提高组] 玩具谜题 题目背景 NOIP2016 提高组 D1T1 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业。 有一天, 这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图: 这时 singer 告诉小南一个谜…...

阿里测试经验7年,从功能测试到自动化测试,我整理的超全学习指南
做测试七年多,有不少人问过我下面问题: 现在的手工测试真的不行了吗? 测试工程师,三年多快四年的经验,入门自动化测试需要多久? 自学自动化测试到底需要学哪些东西? 不得不说,随着…...

Educational Codeforces Round 143 (Rated for Div. 2)
Educational Codeforces Round 143 (Rated for Div. 2) D. Triangle Coloring 思路: 每个环都需要取最大值,那么我们讨论一个环获得最大值选的两条边的可能取法: 显然:如果三边相等,这个环有3种取法。如…...

业务代码编写过程中如何「优雅的」配置隔离
思考 不同的处理方式 1.常规的处理方式,通过某种规则判断区分代码环境 // 获取环境标识 const env getCurrentEnv();if (env dev) {// do something } else if (env test) {// do something } else if (env prod) {// do something } 分析: 1.此种…...

English Learning - L2-2 英音地道语音语调 2023.02.23 周四
English Learning - L2-2 英音地道语音语调 2023.02.23 周四查音标的工具怎么练习效果好准备工作大小声练习大元音开口度的对比舌位对比复习后元音 /ɑː/ /ɔː/ /uː//ɑː//ɔː//uː/前元音 /iː/发音技巧对应单词的发音对应句子的发音常见的字母组合中元音 /ɜː/发音技巧…...

java:线程等待与唤醒 - Object的wait()和notify()
java:线程等待与唤醒 - Object的wait()和notify() 1 前言 java使用Object类的wait()和notify()方法,可以实现线程等待和唤醒(Object类为所有类的父类,即所有类天然具有线程等待和唤醒的方法,一般使用Object类的wait(…...

实现弹窗功能并修改其中一个系数
把鼠标放在number-info上面,会是一个delon/chart的类库,可以在NG-ALAIN上找到阅读NG ALAIN的图表,以及number-info样式,数据文本 它拥有[title] [subtitle]两个可以是TemplateRef类型的,而template可以在里面放一些东西,比如按钮,所以可以放一个修改按钮 这里刚开始把template放…...

vue-draggable浏览器拖拽event事件对象拖动时 DragEvent path undefined
场景: 在做组件拖拽过程中,需要获取到触发元素冒泡过程中的所有元素,所以使用了event.path属性。在Chrome下正常运行,但是在FireFox下测试时发现,完犊子,失效了,通过问题排查,发现了…...

【云原生】搭建k8s高可用集群—20230225
文章目录多master(高可用)介绍高可用集群使用技术介绍搭建高可用k8s集群步骤1. 准备环境-系统初始化2. 在所有master节点上部署keepalived3.1 安装相关包3.2 配置master节点3.3 部署haproxy错误解决3. 所有节点安装Docker/kubeadm/kubelet4. 部署Kuberne…...

LeetCode121_121. 买卖股票的最佳时机
LeetCode121_121. 买卖股票的最佳时机 一、描述 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最…...

收割不易,五面Alibaba终拿Java岗offer
前言 前段时间有幸被阿里的一位同学内推,参加了阿里巴巴Java岗位的面试,本人19年双非本科软件工程专业,目前有一年半的工作经验,面试前就职于一家外包公司。如果在自己本人拿到offer之前,如果有人告诉我一年工作经验可…...

【离线数仓-4-数据仓库设计-分层规划构建流程】
离线数仓-4-数据仓库设计-分层规划&构建流程离线数仓-4-数据仓库设计-分层规划&构建流程1.数据仓库分层规划2.数据仓库构建流程1.数据调研1.业务调研2.需求分析3.总结2.明确数据域3.构建业务总线矩阵&维度模型设计4.明确统计指标1.指标体系相关概念1.原子指标2.派生…...

SQL零基础入门学习(十一)
SQL零基础入门学习(十) SQL NOT NULL 约束 NOT NULL 约束强制列不接受 NULL 值。 NOT NULL 约束强制字段始终包含值。这意味着,如果不向字段添加值,就无法插入新记录或者更新记录。 下面的 SQL 强制 “ID” 列、 “LastName” …...

排序基础之插入排序
目录 前言 一、什么是插入排序 二、实现插入排序 三、插入排序优化 四、插入排序的特性 前言 上一篇中我们说到了《排序基础之选择排序》,这一篇我们来学习一下排序算法中的另一种基础排序算法——插入排序。 一、什么是插入排序 简单来说就是:每…...

LabVIEW控制DO通道输出一个精确定时的数字波形
LabVIEW控制DO通道输出一个精确定时的数字波形如何使用数据采集板卡的DO通道输出一个精确定时的数字波形?解答:产生一个数字波形首先需要创建一个布尔数组,把波形序列信息放到该布尔数组中,然后通过一个布尔数组至数字转换vi来产生数字波形。…...