Android 界面介绍与绘制优化

Andorid用户界面框架

Android 界面介绍与绘制优化_第1张图片
  Android的用户界面框架(Android UI Framework)采用MVCModel-View-Controller)模型,为用户界面提供了处理用户输入的控制器(Controller)和显示界面内容的视图(View)。其中模型层(Model)是应用程序的核心,数据和代码都被保存在模型中。

MVC模型中的视图将应用程序的信息反馈给用户,可能的反馈方法包括视觉、听觉或者触觉等,最常用的就是通过屏幕显示反馈信息。

Android界面的基本架构

Android中,界面是由一颗View树来定义的。View树的叶子结点是视图组件,而其他非叶子结点则是容器组件。View树的如下:
  
Android 界面介绍与绘制优化_第2张图片

View树的这种树型结构也为组件的事件管理,如触摸屏、键盘点击事件,提供了很多的便利。可以说组件的事件传递机制就是沿着树,从高层向底层传递的。

从上图中可以看出,一个ViewGroup可以拥有多个View,也可以包含多个ViewGroupAndroid这种非常灵活的View层次结构可以形成非常复杂的布局,开发者可以利用这个特性开发出特别精致的界面。

当调用ActivitysetContentView()方法的时候,可以将一棵树的根节点或者其子树的根节点,设置是叶子节点作为参数传入此方法,这样系统就获得了该结点的参数,并将该节点作为根节点来测距和绘制,最终将以该结点为根节点的整棵树都绘制在界面上。绘制过程中,父节点负责请求其子节点绘制它们自己,子节点可能会请求他们在父节点中的大小和位置,父节点对每个子节点的大小和位置由最终的决定权。

View和ViewGroup

Android的用户界面都是由视图组件(View)和容器组件(ViewGroup)组成的。为了便于理解,可以将容器看成是屏幕上的一个矩形的区域,是视图则是位于该矩形区域内的一个组件。

Android 界面介绍与绘制优化_第3张图片

View类是Android API中定义的类,位于android.view包中。一个View类或者其子类的对象中存储与该对象相关的布局和内容属性等,并且可以实现处理包括测距、布局、绘图、焦点变换、滚动条以及屏幕区域内的该对象的按键和手势在内的多种功能。View还为Android中的Widget提供服务。WidgetAndroid的系统包,包含了一组View类的可实例化子类。这些子类可以用于绘制交互式屏幕元素。使用Widget,我们可以快速地创建用户界面。

ViewGroup类也是Android API中的类,位于android.view包中。值得一提的是,ViewGroup虽然是View的子类,但是与View类有着完全不同的任何和功能。ViewGroup,其实是一组View类对象的集合。实际上,ViewGroup的功能也确实是这样的,它负责装载和管理一组View,包括View类的对象和ViewGroup本身。

ViewGroup的一个重要的子类是布局(Layout)。Layout可以为一组View创建一个结构。

Android界面的组成元素是ViewViewGroup的子类和简介子类。视图组件的作用是显示,而容器组件的作用是管理

坐标系

Android 界面介绍与绘制优化_第4张图片

View自身的坐标

方法 说明
getTop() 获取View自身顶边到其父控件布局顶边的距离。
getLeft() 获取View自身左边到其父控件布局左边的距离。
getTop() 获取View自身右边到其父控件布局左边的距离。
getTop() 获取View自身底边到其父控件布局顶边的距离。

MotionEvent提供的方法

上图中的圆点,就是我们的触摸点(假定)。触摸事件最终都会由onTouchEvent(MotionEvent event)方法来处理。

方法 说明
getX() 获取点击事件距离控件左边的距离,即视图坐标
getY() 获取点击事件距离控件顶边的距离,即视图坐标
getRawX() 获取点击事件距离整个屏幕左边的距离,即绝对坐标
getRawY() 获取点击事件距离整个屏幕顶边的距离,即绝对坐标

绘制过程

视图展示的系统架构,由视图根结点(RootView)开始,视图根结点负责连接窗口管理器(WindowManage)与装饰视图(DecorView),窗口管理器用于响应用户事件,而装饰视图用于展示特定图像。

  • performTravesals()中包含了以下三个方法:
    • performMeasure()测量
    • performLayout布局
    • performDraw绘制

Created with Raphaël 2.2.0 开始 performTravesals() performMeasure() performLayout() performDraw() 结束
  • 以下图片转载自:Android应用层View绘制流程与源码分析

Android系统显示原理

Android的显示过程可以简单概括为:Android应用程序把经过测量、布局绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到显示屏幕上,通过Android的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕。

绘制原理

绘制任务是由应用发起的,最终通过系统层绘制到硬件屏幕上。也就是说,应用进程绘制好后,通过跨进程通信机制把需要显示的内容传递到系统层,由系统层的SurfaceFlinger服务绘制到屏幕上。

应用层

Android中每个View绘制中有三个核心步骤,通过MeasureLayout来确定当前需要绘制的View所在的大小和位置,通过绘制(Draw)到Surface,在Android系统中整体的绘图源码是在ViewRootImpperformTraversals()方法,通过这个方法可以看出MeasureLayout都是通过递归来获取View的大小和位置的,并且以深度作为优先级。可以看出,层级越深,元素越多,耗时也越长优化点)。

方法 说明
Measure 深度优先原则递归得到所有视图(View)的宽、高;获取当前View的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec之后,可以调用它的成员函数Measure来设置它的大小。如果当前正在测量的子视图child是一个视图容器,那么它又会重复执行操作,直到它的所有子孙视图的大小都测量完成为止。
Layout 深度优先原则递归得到所有视图(View)的位置;当一个子View在应用程序窗口左上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在应用程序窗口中的布局。
Draw 目前Android支持了两种绘制方式:软件绘制(CPU)和硬件加速(GPU),其中硬件加速在Android3.0开始已经全面支持,很明显,硬件加速在UI的显示和绘制的效率远远高于CPU绘制。

GPU的缺点:
耗电:GPU的功耗与CPU高。
兼容问题:某些接口和函数不支持硬件加速。
内存大:使用OpenGL的接口至少需要8MB的内存。

布局优化

Android的界面绘制是通过递归来绘制界面,因此通过减少Layout层级,减少测量、绘制时间,提高复用性三个方面来优化布局的时间是非常重要的。

  1. 尽量使用RelativeLayoutLinearLayout
  2. 在布局层相同的情况下,使用LinearLayout
  3. LinearLayout有时会使嵌套层级变多,应该使用RelativeLayout,使界面尽量扁平化
  4. 使用merge标签消除多余的层级。
  5. 如果需要在两个View之间添加空白,可以使用Space标签。
    • space标签是一个空白的标签,主要用于在两个View之间添加空白。
    • 而且Space不占用绘制资源,因为Space控件onDraw方法进行了一个空的实现。

Merge使用注意事项

  1. Merge只能用在布局XML文件的根元素。
  2. 使用Merge来加载一个布局,必须指定一个ViewGroup作为其父元素,并且要设置加载的attachToRoot参数为true(参照inflat(int, ViewGroup, boolean)
  3. 不能在ViewStub中使用Merge标签。原因就是ViewStubinflate方法中根本没有attachToRoot的设置。

ViewStub

使用ViewStub标签提高显示速度。

在开发中,我们经常会遇到一个问题,在某一个布局中的子布局非常多,但是在App中,又不是所有的布局都需要显示(部分显示),打开这个界面的时候,会根据不同的场景和属性显示不同的Layout。例如:一个页面对不同的用户,比如未登录用户、普通用户、VIP用户会显示不同的页面。又或者说,有些用户喜欢对APP界面中的某些元素进行隐藏,比如微信的朋友圈入口,这个时候可能就用到了INVISIBLE或者GONE属性。但是INVISIBLE或者GONE属性的效率非常的低,原因是即使将元素隐藏了,它们仍然在布局中,仍会测试和解析这些布局。

这个时候,就可以使用ViewStub来解决。

ViewStub是一个轻量级的View,它是一个看不见的,并且不占布局位置,占用资源非常小的视图对象。可以为ViewStub指定一个布局,加载布局的使用,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或者调用了ViewStub.inflate()的时候,ViewStub所指向的布局才会被加载和实例化,然后ViewStub的布局属性都会传给它指向的布局。这样,就可以使用ViewStub来设置是否显示某个布局。


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

    <include
        android:id="@+id/topBar"
        layout="@layout/common_top_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/top_bar_height" />

    <ViewStub
        android:id="@+id/viewstub_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout="@layout/viewstub_first" />

    <ViewStub
        android:id="@+id/viewstub_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout="@layout/viewstub_second" />

    <ViewStub
        android: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实现延迟加载的功能。

当调用ViewStubsetVisbility()函数设置为可见或者调用inflate初始化该View的时候,ViewStub引用的资源开始初始化,然后引用的资源填充在ViewStub所在的位置上。在没有调用setVisibility(int)函数或者inflate()函数之前,ViewStub一直存在组件树层级结构中。但是由于ViewStub非常轻量级,所以对性能的影响非常小。

影响布局效率的主要原因

提高布局效率的方法总体来说就是减少层级,提高绘制速度和布局复用。
  影响布局效率的主要原因如下:

  • 布局的层级越少,加载速度越快。
  • 减少同一级控件的数量,加载速度会变快。
  • 一个控件的属性越少,解析越快。

Android绘制优化

  • 尽量多使用RelativeLayout或者LinearLayout,不要使用绝对布局AbsoluteLayout。
  • 将可复用的组件抽取出来并通过标签使用。
  • 使用标签加载一些不常用的布局。
  • 使用标签减少布局的嵌套层次。
  • 尽可能少用wrap_contentwrap_content会增加布局Measure时的计算成本,已知宽高为固定值的时候,不用wrap_content
  • 删除控件中的无用属性。

附录

  • 《Android应用性能优化最佳实践》
    • 罗彧成
  • 《煮酒论Android》
    • 原始人工作室
  • 《Android进阶之光》
    • 刘望舒
  • 《Android应用开发实战详解》
    • 王翠萍

你可能感兴趣的:(Android进阶)