腾讯跨端框架 Hippy 经常使用调试方法和问题案例详解

​导读 | 近日,腾讯开源跨端框架 Hippy,一周即吸引3000+star。在腾讯内部,Hippy 已运行3年之久,跨 BG 共有 18 款线上业务正在使用 Hippy,日均 PV 过亿,且已创建一套完整生态。html

相较于其余跨端框架,Hippy 对前端开发者更友好:紧贴 W3C 标准,听从网页开发各项规则,使用 JavaScript 为开发语言,同时支持 React 和 Vue 两种前端主流框架。本文将为你们介绍Hippy 经常使用调试方法和常见问题案例,但愿可以帮助开发者快速上手。前端

1、常见调试方法

1.调试服务

前端调试在官网 [1] 已经有专门章节进行描述,就很少说,这里具体说一下调试常见问题、案例和一些基本原理。vue

Hippy 已经在 hippy-debug-server [2] 中集成了一套基于 Chrome DevTools Protocol [3] 的调试服务器,启动后在终端进入本地调试界面,即可以进入远程调试模式。react

目前 iOS 和 Android 都已经支持了真机调试,Android 经过 adb reverse 命令直接实现了本地调试端口的转发。就是指在手机上访问 localhost:38989 的调试端口时,访问的实际是开发机上的 38989 端口。webpack

可是 iOS 须要终端和前端的双方面配合修改端口才能够作到真机调试,因此建议先经过 iOS 模拟器进行调试工做。ios

启动调试服务、进入终端的本地调试环境后,JavaScript 代码将会经过调试服务加载到真机中运行。若是代码没问题应该能正常运行,但有时候会碰到启动就 Crash 的状况,能够参考常见案例最后一条“iOS 版本低于 9 时模拟器报告 SyntaxError”。git

一样的,iOS 上某些特性有的能用 Polyfill 解决,但有的不行(例如 Proxy、正则表达式的 Sticky Flag 等就须要 iOS 10 以上才可使用,并且没法 Polyfill),因此若是要兼容低版本 iOS,要注意不能使用到太新的 JS 特性。github

2.整合到终端内的前端 jsbundle 包调试

有的 App 调试模式下运行很正常,可是打完包集成进去之后发现不行,这时候咱们须要用到整合后的 jsbundle 包调试大法了。值得注意的是:该方案暂时只适用于 iOS。web

其实很是简单,Hippy 在 iOS 中是经过自带的 JavaScriptCore 运行的,因此能够经过自带的 Safar 进行调试,在 Safari 的设置 -> 高级打开开发者菜单后 ,启动 Hippy 就能看到多出了一个模拟器设备。正则表达式

Safari 调试菜单位置

而后就能够用 Safari 开始调试了。惟一要注意的是,断点须要在启动后才生效,启动时是断不下来的,启动问题能够在关键点加上日志,日志可以正常输出。若是是其它启动后问题,能够直接打断点,跟 Chrome 调试服务的使用方法基本一致。

整合后包打断点

3.内存占用状况

前端开发广泛对内存占用缺少概念,直到终端同窗过来讲 JS 内存占用太多把 App 搞崩溃了才回过神来。

JavaScript 目前主要以标记清除算法 [4] 的方案来进行内存回收,它的核心是按期从全局对象中遍历全部对象,而且对不可到达的对象进行标记,并进而清除。

在绝大多数状况下做为前端开发确实不须要关心内存占用,可是 Hippy 中不太同样,Hippy 是前端的开发方式去开发终端 App,有几个类在组件卸载时必定要记得销毁。

包含了 React 中负责事件监听的 EventEmitter 实例、Animation/AnimationSet 动画组件,Vue 中的 $app.on() 终端事件监听等等,不释放掉它们,它们就会一直占用着内存,随着界面愈来愈多,App 最终将会崩溃。

其实调试方法也很是简单,直接在调试器的 Memory 观察内存占用状况,打快照看一下当时各种对象对内存的占用状况,它是 Hippy 在浏览器里运行的容器,能够表明 App 的总体内存占用状况。

内存调试方案

固然,这部份内容 Google 官方也有文档 [5],感兴趣的同窗也能够去检索查看。

2、常见案例

1. 数据已更新,但界面内容或样式不变

这是常常碰到的,最直接的方式是对 React 和 Vue 进行界面绘画的模块 - UIManagerModule 的三个方法 :createNode、updateNode、deleteNode 打断点。

其实无论 MVVM 怎么作,最终都会经过这三个方法把界面通知终端画上去,这其实也带来了无限的扩展性,任何框架只要对接了这三个方法就能够进行 Hippy 绘制。

若是掌握了 UIManagerModule 的语法,甚至不须要 React 或者 Vue 也能够直接经过它画界面。

但从必定程度上来说,Hippy 画界面的方式其实跟浏览器是不同的,它是异步的。

MVVM 组件建立完毕,componentDidMount 或者 mounted 后,其实并不意味着界面真的画上去了(可是这个耗时极少,mounted 后基本能够认为真的画上去了)。

若是要对界面进行操做,须要肯定终端确实画上去了才行,这能够经过 onLayout 事件得到。其次能够看到画界面和普通的 Native Module 调用没有本质区别,最终都要经过 JSBridge 进行通信。-- 这部分正在经过 C++ 方式重写。

经过观察它,咱们能够了解到最终经过 React、Vue 解析后的组件是什么样的,能够观察到为何界面没有更新,或者样式不如预期。

Hippy 的前端框架在开发初期就考虑到了调试的便利性,调试模式下会将前端框架与终端之间的通信都打印到 Console 里,当以为本身的业务 App 或者框架显示存在问题时,直接观察它就能很方便得到全部信息。

以 Hippy-Vue 为例:

Hippy-Vue 的终端通信日志

Hippy-Vue 要关闭该功能只要将入口文件中的 Vue.config.silent 改成 true 便可;Hippy-React 要关闭该功能须要在启动参数里增长一个 silent: true。不过通常不建议关闭,它在打包后会自动中止输出。

2. ScrollView或 ListView没法滚动

在 Hippy 中只有这两种 View 是能够滚动的,剩下的都不能够滚动,可是要让它们能滚起来也不是那么简单,须要有样式进行配合,简单说就是:

  • ScrollView(Vue 的 div + overflow-x/y:scroll) 以上全部父节点都必须有一个固定的高度,ScrollView 中只能嵌套一个内容子节点,它能够随意变高。

  • ListView (Vue 的 ul/li)以上全部父节点都必须有一个固定的高度,里面全部的 renderRow 出来的 ListItemView(Vue 中的 li)能够随意变高。

这里的固定高度能够是直接指定高度,也能够是经过 flex 进行界面动态分割的高度。可是必定要是固定的,由于滚动实际是终端去实现的,它须要可以区分能够滚动和不能够滚动的区域,若是容器高度和内容高度同样,那就变成不能够滚动了。

另外 Vue 里的 ul 默认已经加上了 flex: 1 样式会把整个 View 撑满屏幕,通常状况下不用作特别处理,可是 div + overflow-x/y: scroll 依然须要手工指定高度。

当滚动出现异常的时候,能够经过 XCode 调试一下终端代码,它有个 Debug View Hierarchy 功能,能够很是直观地看到界面层级和尺寸,对调试样式问题有很大帮助。

XCode 的界面层级调试

3. ListView性能不好、卡顿、闪烁

这里须要提到前端三点很是须要注意的地方:

(1)界面发生异常闪烁

首先须要经过第一个小章节里的UIManagerModule 观察法,看一下那三个方法是否有异常的执行。例如 updateNode 执行过于频繁,或者 deleteNode/createNode 异常执行,一般是因为数据有变化致使界面重绘,能够经过调用栈看一下是哪里的数据更新致使界面重绘,并针对性地进行前端优化。

(2)处理 key 值

ListView 决定界面是否重绘,有个很关键的参数是 key(React 官文 [6]、Vue 官文 [7]),Hippy-React 也经过 getRowKey() 的方法实现了 key 在 ListView 中的应用。key 实际上是数据的惟一标示符,数据不发生改变,key 就不该该发生改变,而 key 一旦发生改变 ListView 就会重绘。

目前不少业务在开发时 key 不指定,或者把 index 做为 key,前者会致使 ListView 每次有数据更新都作一次完整的 Array diff,开销很是大;后者会致使删除中间一个节点时将后面全部的节点所有删除再从新插入一次,开销也很是大。

出于性能考虑,key 是必需要加的,通常跟数据的主键保持一致便可。可是:若是 ListView 中的数据须要进行排序,那就不要指定 key 了。

目前 Hippy 的 moveNode 功能,已经计划但仍未完成,指定 key 后在从新排序时会由于对应索引的 key 值不一样,先删除所有节点内容,再所有重建,可能会形成轻度闪烁。若是此时不指定 key,就只有一个更新节点的请求,两次请求合并为一次,终端层会对数据进行对比并更新节点内容。

(3)终端渲染

若是到这一步终端渲染依然很慢、帧率低,咱们就要提到另一个参数 type 了,对应到 Hippy-React 里是 getRowType() 方法,它是用来表示组件样式的,样式不变,type 就不变。

这里须要先说一下 Hippy ListView 的复用机制,当不指定 type 时,每次有新的ListItemView被渲染(HippyReact 里 renderRow() 将返回 ListItemView、Hippy-Vue 里的 li),终端都会从新构建全部终端组件节点。

加了 type 以后,会将将以前渲染过的终端组件节点放到缓存池中,下次碰到相同 type 类型的 ListItemView,就不会从新渲染,而是从缓存池中把缓存的节点拿出来作次拷贝并更新数据,再上屏,即便只有一个样式的 ListItemView,经过 type 也能作到性能优化。

通过上面三步,基本能够解决 90% 的 ListView 性能问题。一样的,Hippy-Vue 官方范例中也对这三个参数加了注释。

4. iOS 上 ListView 不渲染,但 Android 没问题

首先须要检查 numberOfRows 参数是否真的是 ListView 中 ListItemView 的数量,这个除了在业务代码中打断点查看数据数量是否和 numberOfRows 一致之外,也能够经过第一个 UIManagerModule 的调试方法查出来。

这个问题牵扯到 iOS 上一个 ListView 的上屏性能优化,iOS 上并非发一个 ListItemView 就上屏一个的。而是须要先改变 ListView 的 numberOfRows 再去建立节点,当节点数量与 numberOfRows 一致时再上屏。

目前碰到的全部不渲染的问题都是由于这个缘由形成的。

另外在 Hippy-Vue 中,对于静态的 li(就是终端的 ListItemView),能够不须要手工指定 numberOfRows,Hippy-Vue 会在 DOM 层计算子节点数量。

可是对于动态获取的数据,也必需要加上该参数,由于 Hippy-Vue 位于 Vue 的渲染层,跟业务还隔了一个 Vue,没法知道业务到底有多少数据准备要渲染。

5. iPhone 中红屏报告 ModuleNotRegist

这里须要提到 Hippy App 的启动方式:当终端 JS 引擎加载完 JavaScript 后,会从 GLOBAL.appRegister对象里去寻找终端指定的 moduleName,而 __GLOBAL__.appRegister 是在 Hippy 启动时经过 HippyRegister.regist() 方法注册上的。

在 Hippy-React 入口文件或者 Hippy-Vue 入口文件定义的 appName 最终都会执行到 regist() 方法上进行  __GLOBAL__.appRegister 的注册,因此,首先咱们要检查终端的 moduleName 是否和 appName 一致。

若是一致依然出错的话,很大概率是以前 JS 执行失败,也不排除 SDK 更新后存在 bug,也有可能其它问题,会致使 __GLOBAL__.appRegister 未注册成功。

但咱们能够在该错误抛出时二次确认一下终端所寻找到 moduleName 是否和前端定义的 appName 一致,只要在那一行打上日志,而后使用上文的 Release 包调试方案检查终端过来查的究竟是什么 appName,问题就能够获得解决。

6. iOS 版本低于 9 时模拟器报告 SyntaxError

这是由于 Hippy 自带的 Webpack 默认调试模式配置文件,最低仅开启了 iOS 9 的输出,由于输出到 iOS 8 会多出不少 polyfill,语法上也会转换,致使体积大不少。

Hippy 自己最低支持的 iOS 8,咱们建议在高版本的 iOS 上进行调试,而后打包后在低版本 iOS 走一遍测试流程,没什么问题便可。

若是非要在低版本的 iOS 上进行调试,修改一下 webpack 配置文件 iOS 将 preset-env 中的 ios 版本改为更低便可,但目前通过测试 core-js 对 iOS 8 那样对低版本可能存在问题,这就须要你本身手工调整了。

参考资料:

[1] Hippy 前端调试详解:https://tencent.github.io/Hippy/#/guide/debug

[2] 关于 hippy-debug-server :https://www.npmjs.com/package/hippy-debug-server

[3] Chrome DevTools Protocol 简介:https://developer.chrome.com/devtools/docs/integrating

[4] 标记清除算法:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Mark-and-sweep_algorithm

[5] 解决内存问题的Google官方文档:https://developers.google.com/web/tools/chrome-devtools/memory-problems/

[6] React 官方文档:https://reactjs.org/docs/lists-and-keys.html#keys

[7] Vue 官方文档:https://vuejs.org/v2/guide/list.html#Maintaining-State

欢迎关注「云加社区」,Hippy 的实战和原理解析系列文章将会陆续上线。

相关文章
相关标签/搜索