Android界面介绍与绘制优化

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的子类和简介子类。视图组件的作用是显示,而容器组件的作用是管理

Android界面的基本架构

  在Android中,界面是由一颗View树来定义的。View树的叶子结点是视图组件,而其他非叶子结点则是容器组件。View树的如下:

这里写图片描述

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

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

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

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

布局优化

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

  1. 尽量使用RelativeLayout和LinearLayout。
  2. 在布局层相同的情况下,使用LinearLayout。
  3. 用LinearLayout有时会使嵌套层级变多,应该使用RelativeLayout,使界面尽量扁平化。
  4. 使用merge标签消除多余的层级。

Merge使用注意事项

  1. Merge只能用在布局XML文件的根元素。
  2. 使用Merge来加载一个布局,必须指定一个ViewGroup作为其父元素,并且要设置加载的attachToRoot参数为true(参照inflat(int, ViewGroup, boolean)
  3. 不能在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">

    <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绘制优化

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

附录

  • 《Android应用性能优化最佳实践》
    • 罗彧成
  • 《煮酒论Android》
    • 原始人工作室
相关文章
相关标签/搜索