- 小程序同层渲染原理剖析
众所周知,小程序当中有一类特殊的内置组件——原生组件,这类组件有别于 WebView 渲染的内置组件,他们是交由原生客户端渲染的。原生组件作为 Webview 的补充,为小程序带来了更丰富的特性和更高的性能,但同时由于脱离 Webview 渲染也给开发者带来了不小的困扰。在小程序引入「同层渲染」之前,原生组件的层级总是最高,不受 [代码]z-index[代码] 属性的控制,无法与 [代码]view[代码]、[代码]image[代码] 等内置组件相互覆盖, [代码]cover-view[代码] 和 [代码]cover-image[代码] 组件的出现一定程度上缓解了覆盖的问题,同时为了让原生组件能被嵌套在 [代码]swiper[代码]、[代码]scroll-view[代码] 等容器内,小程序在过去也推出了一些临时的解决方案。但随着小程序生态的发展,开发者对原生组件的使用场景不断扩大,原生组件的这些问题也日趋显现,为了彻底解决原生组件带来的种种限制,我们对小程序原生组件进行了一次重构,引入了「同层渲染」。 相信已经有不少开发者已经在日常的小程序开发中使用了「同层渲染」的原生组件,那么究竟什么是「同层渲染」?它背后的实现原理是怎样的?它是解决原生组件限制的银弹吗?本文将会为你一一解答这些问题。 什么是「同层渲染」? 首先我们先来了解一下小程序原生组件的渲染原理。我们知道,小程序的内容大多是渲染在 WebView 上的,如果把 WebView 看成单独的一层,那么由系统自带的这些原生组件则位于另一个更高的层级。两个层级是完全独立的,因此无法简单地通过使用 [代码]z-index[代码] 控制原生组件和非原生组件之间的相对层级。正如下图所示,非原生组件位于 WebView 层,而原生组件及 [代码]cover-view[代码] 与 [代码]cover-image[代码] 则位于另一个较高的层级: [图片] 那么「同层渲染」顾名思义则是指通过一定的技术手段把原生组件直接渲染到 WebView 层级上,此时「原生组件层」已经不存在,原生组件此时已被直接挂载到 WebView 节点上。你几乎可以像使用非原生组件一样去使用「同层渲染」的原生组件,比如使用 [代码]view[代码]、[代码]image[代码] 覆盖原生组件、使用 [代码]z-index[代码] 指定原生组件的层级、把原生组件放置在 [代码]scroll-view[代码]、[代码]swiper[代码]、[代码]movable-view[代码] 等容器内,通过 [代码]WXSS[代码] 设置原生组件的样式等等。启用「同层渲染」之后的界面层级如下图所示: [图片] 「同层渲染」原理 你一定也想知道「同层渲染」背后究竟采用了什么技术。只有真正理解了「同层渲染」背后的机制,才能更高效地使用好这项能力。实际上,小程序的同层渲染在 iOS 和 Android 平台下的实现不同,因此下面分成两部分来分别介绍两个平台的实现方案。 iOS 端 小程序在 iOS 端使用 WKWebView 进行渲染的,WKWebView 在内部采用的是分层的方式进行渲染,它会将 WebKit 内核生成的 Compositing Layer(合成层)渲染成 iOS 上的一个 WKCompositingView,这是一个客户端原生的 View,不过可惜的是,内核一般会将多个 DOM 节点渲染到一个 Compositing Layer 上,因此合成层与 DOM 节点之间不存在一对一的映射关系。不过我们发现,当把一个 DOM 节点的 CSS 属性设置为 [代码]overflow: scroll[代码] (低版本需同时设置 [代码]-webkit-overflow-scrolling: touch[代码])之后,WKWebView 会为其生成一个 [代码]WKChildScrollView[代码],与 DOM 节点存在映射关系,这是一个原生的 [代码]UIScrollView[代码] 的子类,也就是说 WebView 里的滚动实际上是由真正的原生滚动组件来承载的。WKWebView 这么做是为了可以让 iOS 上的 WebView 滚动有更流畅的体验。虽说 [代码]WKChildScrollView[代码] 也是原生组件,但 WebKit 内核已经处理了它与其他 DOM 节点之间的层级关系,因此你可以直接使用 WXSS 控制层级而不必担心遮挡的问题。 小程序 iOS 端的「同层渲染」也正是基于 [代码]WKChildScrollView[代码] 实现的,原生组件在 attached 之后会直接挂载到预先创建好的 [代码]WKChildScrollView[代码] 容器下,大致的流程如下: 创建一个 DOM 节点并设置其 CSS 属性为 [代码]overflow: scroll[代码] 且 [代码]-webkit-overflow-scrolling: touch[代码]; 通知客户端查找到该 DOM 节点对应的原生 [代码]WKChildScrollView[代码] 组件; 将原生组件挂载到该 [代码]WKChildScrollView[代码] 节点上作为其子 View。 [图片] 通过上述流程,小程序的原生组件就被插入到 [代码]WKChildScrollView[代码] 了,也即是在 [代码]步骤1[代码] 创建的那个 DOM 节点对应的原生 ScrollView 的子节点。此时,修改这个 DOM 节点的样式属性同样也会应用到原生组件上。因此,「同层渲染」的原生组件与普通的内置组件表现并无二致。 Android 端 小程序在 Android 端采用 chromium 作为 WebView 渲染层,与 iOS 不同的是,Android 端的 WebView 是单独进行渲染而不会在客户端生成类似 iOS 那样的 Compositing View (合成层),经渲染后的 WebView 是一个完整的视图,因此需要采用其他的方案来实现「同层渲染」。经过我们的调研发现,chromium 支持 WebPlugin 机制,WebPlugin 是浏览器内核的一个插件机制,主要用来解析和描述embed 标签。Android 端的同层渲染就是基于 [代码]embed[代码] 标签结合 chromium 内核扩展来实现的。 [图片] Android 端「同层渲染」的大致流程如下: WebView 侧创建一个 [代码]embed[代码] DOM 节点并指定组件类型; chromium 内核会创建一个 [代码]WebPlugin[代码] 实例,并生成一个 [代码]RenderLayer[代码]; Android 客户端初始化一个对应的原生组件; Android 客户端将原生组件的画面绘制到步骤2创建的 [代码]RenderLayer[代码] 所绑定的 [代码]SurfaceTexture[代码] 上; 通知 chromium 内核渲染该 [代码]RenderLayer[代码]; chromium 渲染该 [代码]embed[代码] 节点并上屏。 [图片] 这样就实现了把一个原生组件渲染到 WebView 上,这个流程相当于给 WebView 添加了一个外置的插件,如果你有留意 Chrome 浏览器上的 pdf 预览,会发现实际上它也是基于 [代码]<embed />[代码] 标签实现的。 这种方式可以用于 map、video、canvas、camera 等原生组件的渲染,对于 input 和 textarea,采用的方案是直接对 chromium 的组件进行扩展,来支持一些 WebView 本身不具备的能力。 对比 iOS 端的实现,Android 端的「同层渲染」真正将原生组件视图加到了 WebView 的渲染流程中且 embed 节点是真正的 DOM 节点,理论上可以将任意 WXSS 属性作用在该节点上。Android 端相对来说是更加彻底的「同层渲染」,但相应的重构成本也会更高一些。 「同层渲染」 Tips 通过上文我们已经了解了「同层渲染」在 iOS 和 Android 端的实现原理。Android 端的「同层渲染」是基于 chromium 内核开发的扩展,可以看成是 webview 的一项能力,而 iOS 端则需要在使用过程中稍加注意。以下列出了若干注意事项,可以帮助你避免踩坑: Tips 1. 不是所有情况均会启用「同层渲染」 需要注意的是,原生组件的「同层渲染」能力可能会在特定情况下失效,一方面你需要在开发时稍加注意,另一方面同层渲染失败会触发 [代码]bindrendererror[代码] 事件,可在必要时根据该回调做好 UI 的 fallback。根据我们的统计,目前同层失败率很低,也不需要太过于担心。 对 Android 端来说,如果用户的设备没有微信自研的 [代码]chromium[代码] 内核,则会无法切换至「同层渲染」,此时会在组件初始化阶段触发 [代码]bindrendererror[代码]。而 iOS 端的情况会稍复杂一些:如果在基础库创建同层节点时,节点发生了 WXSS 变化从而引起 WebKit 内核重排,此时可能会出现同层失败的现象。解决方法:应尽量避免在原生组件上频繁修改节点的 WXSS 属性,尤其要尽量避免修改节点的 [代码]position[代码] 属性。如需对原生组件进行变换,强烈推荐使用 [代码]transform[代码] 而非修改节点的 [代码]position[代码] 属性。 Tips 2. iOS 「同层渲染」与 WebView 渲染稍有区别 上文我们已经了解了 iOS 端同层渲染的原理,实际上,WebKit 内核并不感知原生组件的存在,因此并非所有的 WXSS 属性都可以在原生组件上生效。一般来说,定位 (position / margin / padding) 、尺寸 (width / height) 、transform (scale / rotate / translate) 以及层级 (z-index) 相关的属性均可生效,在原生组件外部的属性 (如 shadow、border) 一般也会生效。但如需对组件做裁剪则可能会失败,例如:[代码]border-radius[代码] 属性应用在父节点不会产生圆角效果。 Tips 3. 「同层渲染」的事件机制 启用了「同层渲染」之后的原生组件相比于之前的区别是原生组件上的事件也会冒泡,意味着,一个原生组件或原生组件的子节点上的事件也会冒泡到其父节点上并触发父节点的事件监听,通常可以使用 [代码]catch[代码] 来阻止原生组件的事件冒泡。 Tips 4. 只有子节点才会进入全屏 有别于非同层渲染的原生组件,像 [代码]video[代码] 和 [代码]live-player[代码] 这类组件进入全屏时,只有其子节点会被显示。 [图片] 总结 阅读本文之后,相信你已经对小程序原生组件的「同层渲染」有了更深入的理解。同层渲染不仅解决了原生组件的层级问题,同时也让原生组件有了更丰富的展示和交互的能力。下表列出的原生组件都已经支持了「同层渲染」,其他组件( textarea、camera、webgl 及 input)也会在近期逐步上线。现在你就可以试试用「同层渲染」来优化你的小程序了。 支持同层渲染的原生组件 最低版本 video v2.4.0 map v2.7.0 canvas 2d(新接口) v2.9.0 live-player v2.9.1 live-pusher v2.9.1
2019-11-21 - 小米8机型进入特定几个页面经常出现闪屏和黑屏问题
一共用过华为nova,小米6,苹果8等多个机型都从来没有出现过这个问题,但是小米8基本上十次有八次都会出现这个问题。 看了之前有关于闪屏的帖子都是因为图片过大的问题,但是我这几个页面都没有一张图片,也没有做任何高频率的操作,就是单纯的列表展示和切换选项而已。 [视频]
2019-08-15 - 只有在华为任意手机上,wx.request接口访问失败
wx.request接口访问其他手机上访问都能正常接收到数据,只有在华为任意手机上,wx.request接口访问失败,报错如下: [图片]
2018-04-19 - 小程序线上source map 文件无法定位所在位置 JSON转化失败
下载小程序线上source map 文件后 sourceMap.SourceMapConsumer失败 需要手动修改source map文件...
2019-11-06 - 华为畅享平板 AGS2-W09上横屏打不开小程序,竖屏可以
- 当前 Bug 的表现(可附上截图) [图片] - 预期表现 至少可以打开,不要一直白屏 - 复现路径 - 提供一个最简复现 Demo
2019-06-19 - canvas绑定的手势事件失效
- 当前 Bug 的表现(可附上截图) [图片] 具体代码比较复杂就不贴片段了。使用蚂蚁的F2图表自定义组件,页面上有三个tab,每个tab下是wx:if 3个图表,初始进入页面使用正常。 重点来了,当多次切换页面,“七进七出”,或者把tab切来切去,切来切去,切来又切去,又或者把页面上下滚来滚去。图表依然能渲染,但是上面的点击事件就失效了!!!重新进页面也不能正常,只能重启小程序。。。 然后我在<canvas bindtouchstart="touchStart">里的touchStart打印一条信息,发现没有触发。。。 然后再看系统信息,正常情况下,安卓的vconsole里面会自动输出info,"Invoke event XXX in component: XXX",代表引用了组件内的某个事件,果不其然这条信息也没了!!! 后面我把切换tab的wx:if 改成hidden,出现bug的情况少了很多,但仍然在滑动页面滚来滚去,tab切来又切去的时候会复现,重新进页面又正常。 请问这是怎么回事呢? 补充一下:测试机型iphone 6s,X,6p等正常。安卓部分手机出现问题后等一会又变正常,部分手机必须重启,附上代码片段 https://developers.weixin.qq.com/s/q3szo7mJ7s9n。
2019-06-14 - #算解决了吧#小米6手机微信小程序播放阿里云CDN加速视频中间卡住问题
- 当前 Bug 的表现(可附上截图) 小米6播放(video组件)阿里云CDN视频卡住 [图片] - 预期表现 正常播放 - 复现路径 小米6手机,打开二更视频,点一个超过5分钟的视频,播放。加载一段播放完就会卡住。 从nginx请求日志来看,小米6和其它手机播放的数据请求不太一样 华为机子播放视频nginx请求日志 [图片] 第一次是httpcode = 200,应该是tcp连接超时了断连了,后面的视频会以range,一段一段请求。 小米6播放视频nginx请求日志 [图片] 小米6上就一条请求记录,和播放器上播放完播放器也就卡住了吻合。 求助阿里云CDN工程师,小米6视频播放抓了个包 [图片] TCP知识不太懂现查的。我的理解是服务端断开连接了,客户端没有正常处理连接断开,还在发送本地window有空余可以继续写入数据。后面也没有继续发起range请求视频数据。然后播放器就卡住了。 这个断开连接的时间感觉有点短,而且好像数据还有在读写突然就断开了。 和阿里云CDN工程师沟通了两天,最后 [图片] 写客户端超时时间改成300秒后,我们的视频基本是6分钟内的,测试播放正常了。因为300秒超时远大于一块数据缓冲后的可播放时长,播放器会继续请求后面的数据。 虽然CDN调整了这个写客户端超时时间,但是觉得播放器数据请求处理上有点问题。同样30秒超时机制下,其它机型上播放是没问题的。 又了解到只是针对我们视频域名做了超时时间调整,遇到相同问题的同学临时解决方案可以去阿里云提工单。
2019-03-14 - 微信版本更新到7.0.5以后,安卓机型video组件视频出错率飙升
微信版本更新到7.0.5之后,同样的视频源,安卓机型下,video组件的binderror回调比例飙升,ios下表现正常,主要出错的手机品牌为,oppo, 华为,小米。以下为第三方统计平台统计到的数据,望官方尽快帮忙排查一下。 [图片] [图片] [图片] [图片]
2019-07-18