关于React Native项目在android上UI性能调试实践
我们尽最大的努力来争取使UI组件的性能如丝般顺滑,但有的时候这根本不可能做到。要知道,Android有超过一万种不同型号的手机,而在框架底层进行软件渲染的时候是统一处理的,这意味着你没办法像iOS那样自由。不过有些时候,你还是可以想办法提升应用的性能(有的时候问题根本不是出在原生代码上!)
要想解决应用的性能问题,第一步就是搞明白在每个16毫秒的帧中,时间都去哪儿了。为此,我们会使用一个标准的Android性能分析工具systrace
,不过在此之前……
请先确定JS的开发者模式已经关闭!
你应该在应用的日志里看到
__DEV__ === false, development-level warning are OFF, performance optimizations are ON
等字样(你可以通过adb logcat来查看应用日志)
使用Systrace进行性能分析
Systrace是一个标准的基于标记的Android性能分析工具(如果你安装了Android platform-tool包,它也会一同安装)。被调试的代码段在开始和结束处加上标记,在执行的过程中标记会被记录,最后会以图表形式展现统计结果。包括Android SDK自己和React Native框架都已经提供了标准的标记供你查看。
收集一次数据
注意:
Systrace从React Native
v0.15
版本开始支持。你需要在此版本下构建项目才能收集相应的性能数据。
首先,把你想分析的、运行不流畅的设备使用USB线链接到电脑上,然后操作应用来到你想分析的导航/动画之前,接着这样运行systrace:
$ <AndroidSDK所在目录>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <你的应用包名>
对于此命令做一个简单的说明:
time
参数控制本次数据收集的持续时间,单位是秒。schd
,gfx
, 和view
是我们所关心的Android SDK内置的tag(标记的集合):schd
提供了你的设备的每个CPU核心正在做什么的信息,gfx
提供了你的图形相关信息,譬如每帧的时间范围,而view
提供了一些关于视图布局和渲染相关性能的信息。-a <你的应用包名>
启用了针对应用的过滤。在这里填写你用React Native创建的应用包名。你的应用包名
可以在你应用中的AndroidManifest.xml
里找到,形如com.example.app
译注:实际上,AndroidManifest.xml里的应用包名会被app/build.gradle
里的applicationId
取代。如果二者不一致,应当以app/build.gradle
里的为准。
一旦systrace开始收集数据,你可以操作应用执行你所关心的动画和操作。在收集结束后,systrace会给你提供一个链接,你可以在浏览器中打开这个链接来查看数据收集的结果。
查看性能数据
在浏览器中打开数据页面(建议使用Chrome),你应该能看到类似这样的结果:
提示: 你可以使用WSAD键来滚动和缩放性能数据图表。
启用垂直同步高亮
接下来你首先应该启用16毫秒帧区间的高亮。在屏幕顶端点击对应的复选框:
然后你应该能在屏幕上看到类似上图的斑马状条纹。如果你无法看到这样的条纹,可以尝试换一台设备来进行分析:部分三星手机显示垂直同步高亮存在已知问题,而Nexus系列大部分情况都相当可靠。
找到你的进程
滚动图表直到你找到你的应用包名。在上面的例子里,我正在分析com.facebook.adsmanager
,由于内核的线程名字长度限制,它会显示成book.adsmanager
。
在左侧,你应该能看到一系列线程对应着右边的时间轴。有3到4个线程是我们必须关注的:UI线程(名字可能是UI Thread
或者是你的包名), mqt_js
和mqt_native_modules
。如果你在Android 5.0以上版本运行,我们还需要关注Render
(渲染)线程。
UI 线程
标准的Android布局和绘制都在UI线程里发生。右侧显示的线程名字会是你的包名(在我的例子里是book.adsmanager)或者UI Thread.你在这个线程里看到的事件可能会是一些Choreographer
, traversals
或者DispatchUI
:
JS线程
这是用于执行JavaScript代码的线程。根据Android系统版本或者设备的不同,线程名可能是mqt_js
或者<...>
。如果看不到对应的名字的话,寻找类似JSCall
,Bridge.executeJSCall
这样的事件。
原生模块线程
这里是用于原生模块执行代码(譬如UIManager
)的线程,线程名可能是mqt_native_modules
或<...>
。在后一种情况下,寻找类似NativeCall
, CallJavaModuleMethod
, 还有onBatchComplete
这样的事件名:
额外的:渲染线程
如果你在使用Android L(5.0)或者更高版本,你应该还会在你的应用里看到一个渲染线程。这个线程真正生成OpenGL渲染序列来渲染你的UI。这个线程的名字可能为RenderThread
或者<...>
,在后一种情况下,寻找类似DrawFrame
或queueBuffer
这样的事件:
寻找导致卡顿的罪魁祸首
一个流畅的动画应该看起来像这样:
每个背景颜色不同的部分我们称作“一帧”——记住要渲染一个流畅的帧,我们所有的界面工作都需要在16毫秒内完成。注意没有任何一个线程在靠近帧的边界处工作。类似这样的一个应用程序就正在60FPS(帧每秒)的情况下流畅表现。
如果你发现一些起伏的地方,譬如这样:
注意在上图中JS线程基本上一直在执行,并且超越了帧的边界。这个应用就没法以60FPS渲染了。在这种情况下,问题出在JS中。
你还有可能会看到一些类似这样的东西:
在这种情况下,UI和渲染线程有一些重负荷的工作,以至于超越了帧的边界。这可能是由于我们每帧试图渲染的UI太多了导致的。在这种情况下,问题出在需要渲染的原生视图上。
并且,你还应该能看到一些可以指导接下来优化工作的有用的信息。
JS的问题
如果你发现问题出在JS上,在你正在执行的JS代码中寻找线索。在上面的图中,我们会发现RCTEventEmitter
每帧被执行了很多次。这是上面的数据统计放大后的内容:
这看起来不是很正常,为什么事件被调用的如此频繁?它们是不同的事件吗?具体的答案取决于你的产品的代码。在许多情况下,你可能需要看看shouldComponentUpdate的介绍。
TODO: 我们还在准备更多的JS性能分析的工具,会在将来的版本中加入。
原生UI问题
如果你发现问题出在原生UI上,有两种常见的情况:
- 你每帧在渲染的UI给GPU带来了太重的负载,或者:
- 你在动画、交互的过程中不断创建新的UI对象(譬如在scroll的过程中加载新的内容)
GPU负担过重
在第一种情况下,你应该能看到UI线程的图表类似这样:
注意DrawFrame
花费了很多时间,超越了帧的边界。这些时间用来等待GPU获取它的操作缓存。
要缓解这个问题,你应该:
- 检查
renderToHardwareTextureAndroid
的使用,有这个属性的View的子节点正在进行动画或变形会导致性能大幅下降(譬如Navigator
提供的滑动、淡入淡出动画)。 - 确保你没有使用
needsOffscreenAlphaCompositing
,这个默认是关闭的,因为它在大部分情况下都会带来GPU消耗的大幅提升。
如果这还不能帮你解决问题,你可能需要更深入的探索GPU到底在做什么。参见Tracer for OpenGL ES。
在UI线程创建大量视图
如果是第二种情况,你可能会看到类似这样的结果:
注意一开始JS线程工作了很久,然后你看到原生模块线程干了些事情,最后带来了UI线程的巨大开销。
这个问题并没有什么简单直接的优化办法,除非你能把创建UI的步骤推迟到交互结束以后去进行,或者你能直接简化你所要创建的UI。React Native小组正在架构层设法提供一个方案,使得新的UI视图可以在主线程之外去创建和配置,这样就可以使得交互变得更加流畅。
关于React Native项目在android上UI性能调试实践的更多相关文章
- react native项目在ios上运行测试,亲测
参考文章:https://segmentfault.com/a/1190000014416132 说明:参考文章中有对AppDelegate.m文件的操作,我的RN版本是0.57.8未设置,也可成功运 ...
- 安装android Studio和运行react native项目(基础篇)
ANDROID_HOME环境变量 确保ANDROID_HOME环境变量正确地指向了你安装的Android SDK的路径. 打开控制面板 -> 系统和安全 -> 系统 -> 高级系统设 ...
- 【腾讯Bugly干货分享】React Native项目实战总结
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/577e16a7640ad7b4682c64a7 “8小时内拼工作,8小时外拼成长 ...
- React Native 项目运行在 Web 浏览器上面
React Native 的出现,让前端工程师拥有了使用 JavaScript 编写原生 APP 的能力.相比之前的 Web app 来说,对于性能和用户体验提升了非常多. 但是 React Nati ...
- React Native 项目实战-Tamic
layout: post title: React Native 项目实战 date: 2016-10-18 15:02:29 +0800 comments: true categories: Rea ...
- React Native移植原生Android
(一)前言 之前已经写过了有关React Native移植原生Android项目的文章,不过因为RN版本更新的原因吧,跟着以前的文章可能会出现一些问题,对于初学者来讲还是会有很多疑难的困惑的,而且官方 ...
- React Native 项目整合 CodePush 全然指南
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/y4x5M0nivSrJaY3X92c/article/details/81976844 作者 | 钱 ...
- Expo大作战(三)--针对已经开发过react native项目开发人员有针对性的介绍了expo,expo的局限性,开发时项目选型注意点等
简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...
- 技术实践丨React Native 项目 Web 端同构
摘要:尽管 React Native 已经进入开源的第 6 个年头,距离发布 1.0 版本依旧是遥遥无期."Learn once, write anywhere",完全不影响 Re ...
随机推荐
- [JSOI2007]建筑抢修
Description 小刚在玩JSOI提供的一个称之为“建筑抢修”的电脑游戏:经过了一场激烈的战斗,T部落消灭了所有z部落的 入侵者.但是T部落的基地里已经有N个建筑设施受到了严重的损伤,如果不尽快 ...
- [AHOI2016初中组]迷宫
题目描述 小雪和小可可被困在了一个无限大的迷宫中. 已经知道这个迷宫有 N 堵环状的墙,如果把整个迷宫看作是一个二维平面,那么每一堵墙都是平面上一个圆.任意两个圆不相交,不重合,也不会相切, 但有可能 ...
- bzoj 4545: DQS的trie
Description DQS的自家阳台上种着一棵颗粒饱满.颜色纯正的trie. DQS的trie非常的奇特,它初始有n0个节点,n0-1条边,每条边上有一个字符.并且,它拥有极强的生长力:某个i时刻 ...
- epoll源码分析(转)
在create后会创建eventpoll对象保存在一个匿名fd的file struct的private指针中,然后进程睡在等待队列上面. 对于等待的fd,通过poll机制在准备好之后会调用相应的cal ...
- 51 nod 1023 石子归并 V3(GarsiaWachs算法)
1023 石子归并 V3基准时间限制:2 秒 空间限制:131072 KB 分值: 320 难度:7级算法题 N堆石子摆成一条线.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一 ...
- bzoj 4010: [HNOI2015]菜肴制作
Description 知名美食家小 A被邀请至ATM 大酒店,为其品评菜肴. ATM 酒店为小 A 准备了 N 道菜肴,酒店按照为菜肴预估的质量从高到低给予 1到N的顺序编号,预估质量最高的菜肴编号 ...
- Linux学习之CentOS(十二)----磁盘管理之 认识ext文件系统(转)
认识ext文件系统 硬盘组成与分割 文件系统特性 Linux 的 EXT2 文件系统(inode) 与目录树的关系 EXT2/EXT3 文件的存取与日志式文件系统的功能 Linux 文件系统的运行 挂 ...
- RxSwift 系列(七) -- Connectable Operators
前言 本篇文章将要学习RxSwift中连接操作符. Connectable Observable在订阅时不发射事件消息,而是仅当调用它们的connect()方法时才发射消息,这样就可以等待所有我们想要 ...
- 反射实现java深度克隆
一.克隆 有时想得到对象的一个复制品,该复制品的实体是原对象实体的克隆.复制品实体的变化不会引起原对象实体发生变化,这样的复制品称为原对象实体的克隆对象或简称克隆. 1.浅复制(浅克隆) 概念:被复制 ...
- 托管C++、C++/CLI、CLR
1.什么是托管C++? 在回答这个问题,首先要搞清楚什么是"托管"(Managed).托管是.NET的一个专门概念,它是融于通用语言运行时(CLR)中的一种新的编程理念,因此我们完 ...