Android 界面介绍与绘制优化
Andorid用户界面框架
Android
的用户界面框架(Android UI Framework
)采用MVC
(Model-View-Controller
)模型,为用户界面提供了处理用户输入的控制器(Controller
)和显示界面内容的视图(View
)。其中模型层(Model
)是应用程序的核心,数据和代码都被保存在模型中。
MVC
模型中的视图将应用程序的信息反馈给用户,可能的反馈方法包括视觉、听觉或者触觉等,最常用的就是通过屏幕显示反馈信息。
Android界面的基本架构
在Android
中,界面是由一颗View
树来定义的。View
树的叶子结点是视图组件,而其他非叶子结点则是容器组件。View
树的如下:
View树的这种树型结构也为组件的事件管理,如触摸屏、键盘点击事件,提供了很多的便利。可以说组件的事件传递机制就是沿着树,从高层向底层传递的。
从上图中可以看出,一个ViewGroup
可以拥有多个View
,也可以包含多个ViewGroup
。Android
这种非常灵活的View
层次结构可以形成非常复杂的布局,开发者可以利用这个特性开发出特别精致的界面。
当调用Activity
的setContentView()
方法的时候,可以将一棵树的根节点或者其子树的根节点,设置是叶子节点作为参数传入此方法,这样系统就获得了该结点的参数,并将该节点作为根节点来测距和绘制,最终将以该结点为根节点的整棵树都绘制在界面上。绘制过程中,父节点负责请求其子节点绘制它们自己,子节点可能会请求他们在父节点中的大小和位置,父节点对每个子节点的大小和位置由最终的决定权。
View和ViewGroup
Android
的用户界面都是由视图组件(View
)和容器组件(ViewGroup
)组成的。为了便于理解,可以将容器看成是屏幕上的一个矩形的区域,是视图则是位于该矩形区域内的一个组件。
View
类是Android API中定义的类,位于android.view
包中。一个View
类或者其子类的对象中存储与该对象相关的布局和内容属性等,并且可以实现处理包括测距、布局、绘图、焦点变换、滚动条以及屏幕区域内的该对象的按键和手势在内的多种功能。View还为Android中的Widget提供服务。Widget
是Android
的系统包,包含了一组View
类的可实例化子类。这些子类可以用于绘制交互式屏幕元素。使用Widget
,我们可以快速地创建用户界面。
ViewGroup
类也是Android API中的类,位于android.view
包中。值得一提的是,ViewGroup
虽然是View
的子类,但是与View类有着完全不同的任何和功能。ViewGroup
,其实是一组View
类对象的集合。实际上,ViewGroup
的功能也确实是这样的,它负责装载和管理一组View
,包括View
类的对象和ViewGroup
本身。
ViewGroup的一个重要的子类是布局(Layout)。Layout可以为一组View创建一个结构。
Android界面的组成元素是View
和ViewGroup
的子类和简介子类。视图组件的作用是显示,而容器组件的作用是管理。
坐标系
View自身的坐标
方法 | 说明 |
---|---|
getTop() | 获取View自身顶边到其父控件布局顶边的距离。 |
getLeft() | 获取View自身左边到其父控件布局左边的距离。 |
getTop() | 获取View自身右边到其父控件布局左边的距离。 |
getTop() | 获取View自身底边到其父控件布局顶边的距离。 |
MotionEvent提供的方法
上图中的圆点,就是我们的触摸点(假定)。触摸事件最终都会由onTouchEvent(MotionEvent event)
方法来处理。
方法 | 说明 |
---|---|
getX() | 获取点击事件距离控件左边的距离,即视图坐标。 |
getY() | 获取点击事件距离控件顶边的距离,即视图坐标。 |
getRawX() | 获取点击事件距离整个屏幕左边的距离,即绝对坐标。 |
getRawY() | 获取点击事件距离整个屏幕顶边的距离,即绝对坐标。 |
绘制过程
视图展示的系统架构,由视图根结点(RootView)开始,视图根结点负责连接窗口管理器(WindowManage)与装饰视图(DecorView),窗口管理器用于响应用户事件,而装饰视图用于展示特定图像。
performTravesals()
中包含了以下三个方法:performMeasure()
测量performLayout
布局performDraw
绘制
- 以下图片转载自:Android应用层View绘制流程与源码分析
Android系统显示原理
Android
的显示过程可以简单概括为:Android
应用程序把经过测量、布局绘制后的surface
缓存数据,通过SurfaceFlinger
把数据渲染到显示屏幕上,通过Android
的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕。
绘制原理
绘制任务是由应用发起的,最终通过系统层绘制到硬件屏幕上。也就是说,应用进程绘制好后,通过跨进程通信机制把需要显示的内容传递到系统层,由系统层的SurfaceFlinger
服务绘制到屏幕上。
应用层
在Android
中每个View
绘制中有三个核心步骤,通过Measure
和Layout
来确定当前需要绘制的View
所在的大小和位置,通过绘制(Draw)到Surface
,在Android系统中整体的绘图源码是在ViewRootImp
类performTraversals()
方法,通过这个方法可以看出Measure
和Layout
都是通过递归来获取View
的大小和位置的,并且以深度作为优先级。可以看出,层级越深,元素越多,耗时也越长(优化点)。
方法 | 说明 |
---|---|
Measure |
用深度优先原则递归得到所有视图(View)的宽、高;获取当前View的正确宽度childWidthMeasureSpec 和高度childHeightMeasureSpec 之后,可以调用它的成员函数Measure来设置它的大小。如果当前正在测量的子视图child是一个视图容器,那么它又会重复执行操作,直到它的所有子孙视图的大小都测量完成为止。
|
Layout | 用深度优先原则递归得到所有视图(View)的位置;当一个子View在应用程序窗口左上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在应用程序窗口中的布局。 |
Draw |
目前Android支持了两种绘制方式:软件绘制(CPU)和硬件加速(GPU),其中硬件加速在Android3.0开始已经全面支持,很明显,硬件加速在UI的显示和绘制的效率远远高于CPU绘制。
GPU的缺点: |
布局优化
Android的界面绘制是通过递归来绘制界面,因此通过减少Layout层级,减少测量、绘制时间,提高复用性三个方面来优化布局的时间是非常重要的。
- 尽量使用
RelativeLayout
和LinearLayout
。 - 在布局层相同的情况下,使用
LinearLayout
。 - 用
LinearLayout
有时会使嵌套层级变多,应该使用RelativeLayout
,使界面尽量扁平化。 - 使用merge标签消除多余的层级。
- 如果需要在两个View之间添加空白,可以使用Space标签。
- space标签是一个空白的标签,主要用于在两个View之间添加空白。
- 而且Space不占用绘制资源,因为
Space
控件onDraw
方法进行了一个空的实现。
Merge使用注意事项
Merge
只能用在布局XML文件的根元素。- 使用
Merge
来加载一个布局,必须指定一个ViewGroup
作为其父元素,并且要设置加载的attachToRoot
参数为true
(参照inflat(int, ViewGroup, boolean)
) - 不能在
ViewStub
中使用Merge
标签。原因就是ViewStub
的inflate
方法中根本没有attachToRoot
的设置。
ViewStub
使用ViewStub
标签提高显示速度。
在开发中,我们经常会遇到一个问题,在某一个布局中的子布局非常多,但是在App中,又不是所有的布局都需要显示(部分显示),打开这个界面的时候,会根据不同的场景和属性显示不同的Layout。例如:一个页面对不同的用户,比如未登录用户、普通用户、VIP用户会显示不同的页面。又或者说,有些用户喜欢对APP界面中的某些元素进行隐藏,比如微信的朋友圈入口,这个时候可能就用到了INVISIBLE
或者GONE
属性。但是INVISIBLE
或者GONE
属性的效率非常的低,原因是即使将元素隐藏了,它们仍然在布局中,仍会测试和解析这些布局。
这个时候,就可以使用ViewStub来解决。
ViewStub是一个轻量级的View,它是一个看不见的,并且不占布局位置,占用资源非常小的视图对象。可以为ViewStub指定一个布局,加载布局的使用,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或者调用了ViewStub.inflate()
的时候,ViewStub所指向的布局才会被加载和实例化,然后ViewStub的布局属性都会传给它指向的布局。这样,就可以使用ViewStub来设置是否显示某个布局。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><includeandroid:id="@+id/topBar"layout="@layout/common_top_bar"android:layout_width="match_parent"android:layout_height="@dimen/top_bar_height" /><ViewStubandroid:id="@+id/viewstub_first"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout="@layout/viewstub_first" /><ViewStubandroid:id="@+id/viewstub_second"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout="@layout/viewstub_second" /><ViewStubandroid:id="@+id/viewstub_default"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout="@layout/layout_default" /></LinearLayout>
在调用的时候,根据不同的需求切换不同的Layout,这样可以提高页面初始化的速度,使用代码如下:
View view = view.inflate(R.layout.activity_viewstub, container, false);
switch (choice) {case First:ViewStub stub1 = view.findViewById(R.id.viewstub_first);stub1.inflate();break;case Second:ViewStub stub2 = view.findViewById(R.id.viewstub_second);stub2.inflate();break;default:ViewStub stub = view.findViewById(R.id.viewstub_default);stub.inflate();break;
}
ViewStub显示有两种方式,上面的代码使用的是inflate方法,也可以直接使用ViewStub.setVisibility(View.Visible)
方法。
注意事项
- 使用
ViewStub
只能加载一次,之后ViewStub对象会被置为空。换句话说,某个被ViewStub指定的布局被加载后,就不能再通过ViewStub来控制它了。所以它不适用于需要按需显示隐藏的情况。 ViewStub
只能用来加载一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。如果想操作一个具体的View,还是使用visibility属性。- ViewStub中不能嵌套Merge标签。
使用场景
- 在程序运行期间,某个布局在加载后,就不会有变化,除非销毁该页面重新加载。
- 想要控制显示与隐藏的是一个布局文件,而非某个View。
- 因为ViewStub只能Inflate一次,之后就会被置空,无法继续使用ViewStub来控制布局。所以需要在运行时不止一次显示和隐藏某个布局的时候,使用ViewStub是无法实现的。这时只能使用View的可见性来控制。
延迟加载
延迟加载的功能非常重要,特别是界面中显示的内容比较多并且所占空间比较大的时候。在Android应用程序中,可以使用ViewStub
实现延迟加载的功能。
当调用ViewStub
的setVisbility()
函数设置为可见或者调用inflate
初始化该View的时候,ViewStub
引用的资源开始初始化,然后引用的资源填充在ViewStub
所在的位置上。在没有调用setVisibility(int)
函数或者inflate()
函数之前,ViewStub
一直存在组件树层级结构中。但是由于ViewStub
非常轻量级,所以对性能的影响非常小。
影响布局效率的主要原因
提高布局效率的方法总体来说就是减少层级,提高绘制速度和布局复用。
影响布局效率的主要原因如下:
- 布局的层级越少,加载速度越快。
- 减少同一级控件的数量,加载速度会变快。
- 一个控件的属性越少,解析越快。
Android绘制优化
- 尽量多使用RelativeLayout或者LinearLayout,不要使用绝对布局AbsoluteLayout。
- 将可复用的组件抽取出来并通过
<include/>
标签使用。 - 使用
<ViewStub/>
标签加载一些不常用的布局。 - 使用
<merge/>
标签减少布局的嵌套层次。 - 尽可能少用
wrap_content
,wrap_content
会增加布局Measure时的计算成本,已知宽高为固定值的时候,不用wrap_content
。 - 删除控件中的无用属性。
附录
- 《Android应用性能优化最佳实践》
- 罗彧成
- 《煮酒论Android》
- 原始人工作室
- 《Android进阶之光》
- 刘望舒
- 《Android应用开发实战详解》
- 王翠萍
Android 界面介绍与绘制优化相关推荐
- 【Android 性能优化】布局渲染优化 ( GPU 过度绘制优化总结 | CPU 渲染过程 | Layout Inspector 工具 | View Tree 分析 | 布局组件层级分析 )
文章目录 一. GPU 过度绘制优化总结 二. CPU 渲染过程 三. CPU 渲染性能调试工具 Layout Inspector 四. Layout Inspector 组件树 DecorView ...
- Android系统性能优化(68)---绘制优化
Android性能优化: 绘制优化 前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的绘制优化,希望你们会喜欢. 目录 1. 影响的性能 绘制性能的好坏 主要影响 :And ...
- Android 绘制优化
绘制原理 View 的绘制流程有3个步骤,分别是measure .layout 和draw ,它们主要运行在系统的应用框架层,而真正将数据渲染到屏幕上的则是系统Native层的SurfaceFling ...
- Android:最全面详细的性能优化攻略(含内存优化、内存泄漏、绘制优化、布局优化、图片优化、APK优化、多线程优化、列表优化等)
前言:佛教中有一句话:初学者的心态,拥有初学者心态是件了不起的事情.真正的大师永远怀有一颗学徒的心. 一.概述 在Android中,性能优化是细分领域中最难且也是知识面涉及最深和最广的方向之一. 更快 ...
- Android 绘制优化总结1
Android性能优化分为很多种,比较常用的有绘制优化.内存优化.耗电优化和稳定性优化等,这个系列我们就来学习性能优化中的绘制优化. 1.绘制原理 Android绘制View有三个主要的步骤,分别是m ...
- Android开发界面显示慢-过度绘制优化
本文原文链接:http://www.cnblogs.com/liuling/p/2015-10-08-2.html 作者名:残剑 如果一个布局十分复杂,那么就需要来排查是否出现了过度绘制,如果出现了, ...
- Android渲染画面,Android界面渲染优化
上篇介绍了android界面渲染主要是Display的过程,只要在一个时间段也就是16ms中,CPU和GPU不能正常处理完数据就会产生卡顿. 而 CPU(中央处理器) :多缓存多分支,适用于复杂的逻辑 ...
- Android 过度绘制优化
Android 从一诞生到现在已经发布的 7.0 版本,卡顿和不流畅问题却一直被人们所诟病.客观地来讲,Android 的流畅性确实一直不给力,哪怕是某些大厂的 App ,也都不同程度地存在卡顿问题. ...
- Android性能优化(三)绘制优化
前面两篇博客我总结了启动优化和内存优化的相关技术和工具.这篇博客,我总结一下绘制优化,也可以叫布局优化,我更倾向于称之为绘制优化.因为,我会从Android屏幕绘制的基本原理开始说起,进而到布局的加载 ...
最新文章
- tcpdump基础教程
- Jetty 开发指南:嵌入式开发示例
- 【EventBus】Subscribe 注解分析 ( Subscribe 注解属性 | threadMode 线程模型 | POSTING | MAIN | MAIN_ORDERED | ASYNC)
- Jenkins持续集成环境之Maven的安装和配置
- WSSv3 Technical Articles 系列文章
- 不同时期的同学的聚会
- unity 中文_Unity无情大爆料时间Unity3D的脚本语言
- vnpy学习_06回测结果可视化改进
- java for 循环中if判断只进入第一个_「每天三分钟跟我学Java」之条件语句和循环结构...
- oracle列表分区ADD VALUES或DROP VALUES包含数据变化
- Hyper-V应用指南之5-导出、导入虚拟机
- linux读写磁盘文件寿命,linux下TF卡测试寿命的测试程序编写
- 英语数字听力学习软件操作
- 苹果vpn是下一件大事
- MySQL的 初步认识 - 细节狂魔
- 点微同城系统整站源码 带pc端与小程序端+49款插件
- C# Dictionary多线程安全访问问题
- html5图片并排显示图片,HTML5 - 并排显示项目
- (半转载)光在介质中传播,颜色没有变,那到底是频率变了还是波长变了?为什么描述光的颜色时多用波长表示,而很少用频率表示呢?我们平时所说的光的波长到底指的是真空中的波长还是空气中的波长呢?
- 初学者一学就会的小程序开发——推箱子
热门文章
- 语音合成TTS论文优选:A HYBRID TEXT NORMALIZATION SYSTEM USING MULTI-HEAD SELF-ATTENTION FOR MANDARIN
- 线性代数学习笔记——行列式的性质及拉普拉斯定理——3. 根据定义计算简单的行列式
- 线性代数学习笔记——行列式的性质及拉普拉斯定理——2. n阶行列式的定义
- Chrome浏览器中如何将开发者工具(F12)语言从英文设置成中文
- 怎么高效的通过爬虫获取数据?
- 增强学习之入门 这个很明白
- html编辑四则运算,denglu.html
- mysql 表情 转义_如何转义emoji表情,让它可以存入utf8的数据库?
- java怎样实现下载模板
- 校安行 | 电子学生证真的有用吗?