性能优化之绘制优化

系统显示原理

显示过程

安卓应用通过测量,布局,绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到显示屏幕上,通过安卓刷新机制来刷新数据。

扩展测量、布局、绘制原理

卡顿原因

理想情况下,60FPS(每秒传递的帧数)感觉不到卡,也就是每次绘制时长应该在16ms以内。安卓系统每隔16ms发出VSYNC信号,触发对UI进行渲染。

如果某个操作花费24ms,系统在得到VSYNC信号就无法正常渲染,就会发生丢帧现象。如果在动画或者滑动等场景,就会感觉到卡顿不顺畅。

卡顿原因:

  • 绘制任务太重,绘制一帧内容耗时太长
  • 主线程太忙,导致VSYNC信号来时还没准备好数据导致丢帧

主线程主要做以下几个方面的工作

  • 系统事件处理
  • 消息处理
  • 界面布局
  • 界面绘制
  • 界面刷新

性能分析工具

Profile GPU Rendering(GPU呈现模式分析)

功能:卡顿检测

开启方式:开发人员选项-GPU呈现模式分析-在屏幕上显示为条状图

图示:


image.png
  • 蓝色:绘制时间
  • 共色:执行时间
  • 橙色:处理时间

超过绿色警戒线,就可能丢帧。所以保存UI流畅关键是让这些垂直柱状条尽可能保存在绿线以下。

Android Profiler分析器

功能:分析cpu、内存 、网络使用情况

开启方式:View > Tool Windows > Android Profiler

使用教程:https://blog.csdn.net/niubitianping/article/details/72617864

Analyze

功能:代码、布局检查

配置:File-Settings-Inspections-Android Lint

检测:Analyze-Inspect Code

布局优化

include

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".performance.LayoutOptimizationActivity">

    <include android:id="@+id/include"
        layout="@layout/include_layout"/>
</LinearLayout>

原理:在解析xml布局的时候,如果检测到include标签,那么直接将该布局下的根布局添加到include下的父视图中。

注意:include布局下的根布局id会动态修改,会被设置成include标签中的id值。

merge

子布局和父布局都是同一种类型,如FrameLayout,那么可以使用merge进行布局优化

作用:合并UI布局,使用merge标签能降低UI布局的嵌套层次

    <include android:id="@+id/include"
        layout="@layout/include_layout"/>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".performance.LayoutOptimizationActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是include_layout"/>

</merge>

注意事项

merge只能用在布局xml文件的根元素
使用merge加载一个布局时,必须制定一个viewGroup作为其父元素,并且要设置加载的attachToRoot参数是true(参照inflate(int,viewGroup,boolean))
不能在ViewStub中使用Merge标签,因为ViewStub的inflate方法中根本就没有attachToRoot的设置

ViewStub

ViewStub是一个不可见和能在运行期间延迟加载目标视图的、宽高都为0的View,在使用inflate()或者设置visible之前,是占用布局空间和系统资源的,它只是一个为目标视图占了个位置而已

原理:当手动调用inflate()或者设置visible(实际上也是调用inflate函数),会将ViewStub从父控件移除,并加装目标控件,然后将目标控件添加到ViewStub父控件中去,这就完成了视图动态替换,也就是延迟加载功能

    <ViewStub
        android:id="@+id/stub"
        android:layout_marginTop="50dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/view_stub_layout"
        />
        mStub.inflate();
//    mStub.setVisibility(View.VISIBLE);

注意事项

ViewStub只加载一次,之后会被置空,如果要控制某个布局的显示或者隐藏,只能使用View的可见性控制

减少布局层次原则:

布局层次越少、控件越少加载速度越快,属性越少,解析越快。

优化总结:

  • 尽量多使用RelativeLayout,避免使用LinearLayout的layout_weight属性,不要使用绝对布局AbsoluteLayout
  • 将可复用的组件抽取出来并通过<include/>标签使用
  • <ViewStub/>来加载不常用的布局
  • <merge/>减少布局层次
  • 尽可能少用wrap_content,wrap_content会增加布局measure时的计算成本
  • 删除控件中无用属性(属性越少,解析越快)

避免过度绘制

导致过度绘制的主要原因

  • xml布局,控件有重叠且都有设置背景
  • View自绘,onDraw里面对同个区域绘制多次

检测工具

开发人员选项-调试GPU过度绘制-显示过度绘制区域

image.png
  • 无色-每个像素绘制1次
  • 蓝色-每个像素多绘制1次
  • 绿色-每个像素多绘制2次
  • 粉色-每个像素多绘制3次,不超过1/4可接受
  • 红色-每个像素多绘制4次或者更多,需优化

优化途径

xml上的优化

  • 减少背景叠加,移除xml中不必要的背景,或者根据条件设置
  • 移除Window默认背景
  • 按需显示占位背景图片(???)

例如移除window默认背景:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setBackgroundDrawable(null);
    }

自定义View优化

关键在于canvas.clipRect()和canvas.quickreject()的使用

(在自定义View复习阶段补充学习)

启动优化

要分析启动新能,需要熟悉Application和Activity的工作流程和生命周期

Application

启动Application时,系统会创建一个PID,即进程ID,其生命周期是最长的,等于这个应用程序的生命周期,因为它是全局单例。可以避免使用静态变量来存储永久保存值,而是作为Application全局变量保存。

相关抽象接口

  • attachBaseContext 得到应用上下文的Context,在应用创建时首次调用
  • onCreate 晚于attachBaseContext调用,在应用创建时首次调用
  • onTerminate 应用结束时调用
  • onConfigurationChanged 系统配置发生变化时调用
  • onLowMemory 系统低内存时调用
  • onTrimMemory 系统要求应用释放内存时调用

启动流程

启动-Application-attachBaseContext()-onCreate-Activity生命周期

启动分类

  • 冷启动:创建一个新的进程,创建和初始化Application类
  • 热启动:在已有进程中启动,不会再创建和初始化Application类,Application只初始化一次

优化方案

  • 在闪屏页,做数据准备,如底层模块的初始化、数据的预拉取
  • UI优化,减少布局层次,避免过度绘制
  • 加载逻辑优化
    1. 必要且耗时:启动初始化,考虑用线程来初始化
    2. 必要且不耗时:首页绘制
    3. 非必要且耗时:数据上报,插件初始化(考虑用线程)
    4. 非必要且不耗时:直接去掉,需要时再加载

合理刷新

减少刷新次数

  • 进度条,数据变化没有1%,没必要刷新
  • 不可见的控件,没必要刷新

避免后台有高CPU线程运行

如果后台线程开销过大,占用GPU过高,导致系统GC频繁和CPU时间片资源紧张,还是有可能导致页面卡顿。

例如:列表边滑动,边加载图片,可进行优化成:滑动时停止加载图片,滑动结束继续加载图片

缩小刷新区域

  • invalidate(Rect dirty)
  • 列表item发生变化,调用notifyDataSetChanged()刷新

提升动画性能

性能:属性动画>补件动画>帧动画

硬件加速

硬件加速可渲染提高动画性能,使动画更平滑、流畅

硬件加速控制级别

并不是所有2D绘制的操作都支持硬件加速,所以我们要合理选择硬件加速控制级别

Application级别

 <application
        ...
        android:hardwareAccelerated="true"
       ...
 >

Activity级别

 <activity
            android:name=".MainActivity"
            android:hardwareAccelerated="true">

Window级别

getWindow().setFlags(
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

View级别

view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  • LAYER_TYPE_NONE 默认行为,正常渲染,不会在off-screen buffer上备份
  • LAYER_TYPE_HARDWARE 如果应用开启了硬件加速view渲染在硬件上,否则与LAYER_TYPE_SOFTWARE行为一样
  • LAYER_TYPE_SOFTWARE 用软件渲染在一个bitmap上

设计一个动画流程

  • LayerType设置成LAYER_TYPE_HARDWARE
  • 更新view属性
  • 动画结束将LayerType设置成LAYER_TYPE_NONE

硬件加速注意事项

硬件加速属于双缓冲机制,使用显存进行页面渲染(使用较少的物理内存),导致更频繁的显存操作,低版本手机可能引起以下现象:
白屏、花屏、闪屏;

注意事项
  • 软件渲染,可以使用重用Bitmap的方法节省内存,硬件加速不适用
  • 开启硬件加速的View在前台运行,会耗费额外的内存,切换到内存,产生的额外内存有可能不释放
  • 减少过度绘制
不支持硬件加速的相关接口

Canvas不支持硬件加速的二维绘图接口:

  • clipPath()
  • clipRegion()
  • drawPicture()
  • drawPosText()
  • drawTextOnPath()
  • drawVertices()

Paint不支持硬件加速的接口:

  • setLinearText()
  • setMaskFilter()
  • setRasterizer()

卡顿监控方案

利用Looper中的Printer来实现监控,重写Printer方法(判断start和end的时间差值),通过Looper.getMainLooper().setMessageLogging(LogPrinter)设置自定义的Printer

参考:Android应用性能优化最佳实践

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355