Android UI优化全解析

点击上方蓝字关注我,天天一见,给你力量前端


前言

往期优化文章目录:java

启动优化python


今天继续优化方面的内容——UI(布局)优化。android

UI优化知识点主要分为三部分ios

  • 第一部分,系统为咱们作的优化。因为前端中UI展现的特殊性和重要性,Android团队也是在不断想办法提升UI方面的渲染速度,因此也是更新了不少系统优化方案,好比:

硬件加速、黄油计划、RenderThread。c++

  • 第二部分,咱们能够 具体实施的优化方案。主要包括:

java代码布局、View重用、异步建立View、xml布局优化、异步布局框架Litho、屏幕适配、Flutter、Jetpack Composegit

  • 第三部分,工具使用,主要包括:

Choreographer、monitor、Systracegithub

系统作的优化

硬件加速

以前咱们说过,一个图形的绘制是CPU,GPU和屏幕三方合做的结果。web

Android3.0以前,尚未硬件加速,都是经过CPU进行数据计算,而后经过Skia库进行软件绘制,可是CPU对于图形处理并不高效。面试

因而从3.0开始,Android支持了硬件加速,到Android4.0默认开启硬件加速。

开启硬件加速后,就是由CPU进行图形缓存数据的绘制。这样CPU和GPU就能比较好的分工,各司其职了。CPU用于控制复杂绘制逻辑、构建或更新DisplayList(基础元素)GPU用于完成图形计算、渲染DisplayList(基础元素)

这里也找了一张各类场景下,硬件加速先后的流程与加速效果(Android6.0背景):

可是硬件加速也是有缺点的:

  • 启用硬件加速须要更多资源,所以应用会占用更多内存。
  • 比较低的版本,因为有些 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的属性封装,渲染帧的信息等等。

优化方案

java代码布局

咱们通常都是用XML文件进行布局,可是XML解析也是很耗时的,并在这个解析过程在主线程进行。

因此咱们有的时候也许能够经过Java代码或者kotlin进行View的建立?

理论中,这样确实能减小布局加载的消耗时间,可是Java代码建立View太麻烦了,并且没法可视化。

固然,也有一些库能够帮助咱们将xml代码转换成java代码,好比X2C(https://github.com/iReaderAndroid/X2C ),可是它并不支持全部的状况,好比merge标签,appCompat兼容控件等等。

因此咱们须要在这之中找到平衡点,有的时候,UI简单而且要求高性能的前提下,咱们能够试试用这样的方法,即用java代码代替XmL代码。

View重用

参照Recyclerview的作法,咱们也能够将一些经常使用的view保存到缓存池中,这样在不一样的界面中就能复用缓存池里面的view。

异步建立view

这是Google提出的一个方案——AsyncLayoutInflater。它能够异步加载布局文件,而且回调给主线程,从而减小主线程耗时。简单贴下主要代码:

new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, nullnew AsyncLayoutInflater.OnInflateFinishedListener() {
        @Override
        public void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) {
        //回调给主线程
            setContentView(view);
    }
});

xml布局优化

在写xml布局文件的时候,咱们要作的也有不少,好比:

  • 减小布局嵌套。多使用ViewStub、Merge、ConstraintLayout来代替。
  • 优化开销。RelativeLayout和 使用weight的LinearLayout 开销比较大,建议使用ConstraintLayout,LinearLayout代替。

异步布局框架Litho

Litho是Facebook开源的一款在Android上高效创建UI的声明式框架。

主要有如下特色:

1)声明式:它使用了声明式的API来定义UI组件。

2)异步布局:它把 measurelayout 都放到了后台线程,只留下了必需要在主线程完成的 draw,这大大下降了 UI 线程的负载

3)视图扁平化:因为 Litho 使用了自有的布局引擎(Yoga),在布局阶段就能够检测没必要要的层级、减小 ViewGroups,来实现 UI 扁平化。

4)优化 RecyclerView:Litho 还优化了 RecyclerView 中 UI 组件的缓存和回收方法。

屏幕适配

关于屏幕适配问题,也是老生常谈了。主要有如下几种方案:

  • 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对应的资源文件,因此也有比较好的容错性。也是一个比较好的适配方案。

固然这种重复性工做确定不须要咱们本身手动去实现,有专门的插件能够生成相应的文件和文件夹,这里也推荐一个:https://github.com/ladingwu/dimens_sw

  • 今日头条适配方案。

这个你们应该都很熟悉了,主要是经过动态修改density值来保证全部设备的屏幕宽度都是固定的dp值。用到的公式就是px = density * dp

好比设计图宽为360dp,咱们只要保证全部设备的宽度都是360dp就能适配了。而宽度的px值咱们是已知的,因此就是要修改这个 density 值来完成咱们的适配目的。具体代码我就不贴了,网上不少。

这种方案侵入性低,使用方便,是个不错的适配方案。

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渲染技术,因此性能很好。彻底能够胜任游戏开发。

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!")
}
  

工具篇

Choreographer

嘿嘿,没想到吧,上期咱们说过的Choreographer其实也是一个监控应用帧率的工具。它主要有如下特性:

  • 能获取总体的帧率。
  • 能在线上使用。
  • 获取的帧率几乎是实时的。

主要原理就是利用postFrameCallback计算两次绘制的间隔时间,简单贴下代码:

private long mLastFrameTime;
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        if (mLastFrameTime == 0) {
            mLastFrameTime = frameTimeNanos;
        }
        float diff = (frameTimeNanos - mLastFrameTime) / 1000000.0f;//获得毫秒,正常是 16.66 ms
        if (diff > 500) {
            double fps = (((double) (mFrameCount * 1000L)) / diff);
            mFrameCount = 0;
            mLastFrameTime = 0;
            Log.d("doFrame""doFrame: " + fps);
        } else {
            ++mFrameCount;
        }
        Choreographer.getInstance().postFrameCallback(this);
    }
});

想细细研究的能够看看这个库(https://github.com/friendlyrobotnyc/TinyDancer )

LayoutInspector/Android Device Monitor

LayoutInspectorAndroidStudio种的一个布局检查器,能够经过Tools > Layout Inspector找到,他能够检查应用中的某个界面的视图结构,可是没法查看非调式状态的应用。

若是要看其余应用的布局状况,可使用Android Device Monitor,在Android Studio 3.1 之后,须要单独从文件夹打开了:

android-sdk/tools/monitor

Systrace

Systrace是分析Android性能问题的神器,获取Systrace文件的方式有两种:

  • 一是 AndroidSDK/tools目录下,经过 monitor.batAndroid Device Monitor可视化工具获得。
  • 二是经过 python脚本获取。

具体怎么分析这里就不细说了,下次能够专门一篇文章讲Systrace性能分析。

参考

https://www.jianshu.com/p/a4b8e4c5d9b0 https://time.geekbang.org/column/article/81049 https://juejin.cn/post/6844904047355363341 https://juejin.cn/post/6844904048068395015 https://blog.csdn.net/u013425527/article/details/97046401


感谢你们的阅读,有一块儿学习的小伙伴能够关注下公众号—码上积木❤️

每日三问知识点/面试题,聚沙成塔。



在看你最好看


本文分享自微信公众号 - 码上积木(Lzjimu)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索