- 小程序性能优化指南
开发者可通过开发者工具中的性能扫描工具提前发现代码中的可优化项: 1. 代码包不包含插件大小超过 1.5 M 【建议】小程序代码包单个包大小限制为2M。因此我们建议开发者在开发时,如果遇到单包体积大于1.5M的情况,可以采取分包的方式,把部分代码拆分到分包去,降低单个包的体积,提升小程序的加载速度。具体可以查看文档《使用分包》。 2. 引用插件大小超过 200 K 【知会】小程序插件的大小是会算进小程序代码包2M体积限制中的。因此当我们发现开发者引用的插件体积大于200K时,会对开发者予以提示,避免出现上传阶段提示代码包体积超限,但是不知道为何超限的问题。 3. 图片和音频资源大小超过 200 K 【建议】小程序代码包里可以存放一些必要的静态资源(如tabbar的icon等);但其他非必要的静态资源体积过大会影响小程序代码包加载速度。因此我们建议图片、音频等静态资源体积大小超过200K时,将它们上传到CDN,用URL引入会是个更好的选择。 4. 主包存在仅被其他分包依赖的JS 【建议】当主包里存在一些JS文件只会被分包使用(而主包自己不使用)时,我们建议把这些JS文件从主包中拆分出去,放到对应的分包里,从而优化主包的加载速度。 5. 主包存在仅被其他分包依赖的组件 【建议】当主包里存在一些组件只会被分包使用(而主包自己不使用)时,我们建议把这些组件从主包拆分出去,并且可以使用 分包异步化 这个特性加载这些组件,从而优化主包的加载速度。 6. 存在无使用的插件 【必须】如果有无使用的插件,请将其从 app.json 中去除。不然它会占用代码包体积,也会延迟代码包加载的时间。 7. 存在无使用的组件 【必须】如果在对应页面JSON的 `usingComponents` 里声明的组件但是没有使用,请将其从 `usingComponents` 里去除。 8. 未开启JS压缩 【必须】在工具「详情」-「本地设置」中开启「上传代码时自动压缩脚本文件」的设置 [图片] 9. 未开启WXML压缩 【必须】在工具「详情」-「本地设置」中开启「上传代码时自动压缩wxml文件」的设置 [图片] 10. 未开启WXSS压缩 【必须】在工具「详情」-「本地设置」中开启「上传代码时自动压缩样式文件」的设置 [图片] 11. 存在无依赖文件 【必须】在「代码质量」面板,点击「建议去除」后,可以打开代码依赖分析面板的「无依赖文件」页面,这里可以看到代码包里没有被用到的文件。请在代码包中去除这部分文件,减小体积并优化加载速度。 在本地开发的过程中,会自动过滤无依赖的文件,如果出现误过滤的情况,可以在 project.config.json 的 setting 字段中添加 ignoreDevUnusedFiles 为 false,也可以在 packOptions 的 include 字段中手动将被忽略的文件引入,同时欢迎发帖反馈误报的情况提交代码片段帮助我们完善此功能 注意:页面若为配置在 app.json 中,将被识别为无依赖文件 [图片] 12. 未开启组件懒注入(按需注入) 【必须】在 app.json 中加入 `"lazyCodeLoading": "requiredComponents"` 可以开启小程序组件按需注入特性。 其他优化内容,请点击学习《小程序性能优化实践》课程 [图片]
2023-02-17 - 小程序云测简介
[视频] 大家好,我是来自微信小程序测试团队的严烨。 本次我们推出了微信小程序云测服务的系列视频教程,希望可以帮助小程序开发或者测试同学快速上手小程序的自动化测试和性能测试。 这节课程我将和大家分享一下小程序云测服务的一个来由以及它的能力,并且以智能化Monkey测试为例,给大家演示一下如何通过云测服务快速开始小程序自动化冒烟测试。 随着小程序生态的发展,越来越多的大型小程序也随之出现。页面越来越复杂,全手工测试已无法满足快速增长的业务需求。那么自然而然的,我们越来越多的开发者就希望用自动化测试的方式来解决这个问题。 我们也收集了部分团队来做小程序自动化测试时遇到的问题: 组内的测试同学代码能力比较弱,自动化测试的时候不知道如何下手;电商小程序很重视小程序的性能,比如说启动小程序启动时间比较长,要怎么优化;小程序运行时候感觉比较卡,占用内存很高,那这时候应该怎么排查;已经开始了自动化测试,但测试环境不稳定。例如使用图像识别的方式,换一个机器用例容易挂掉;微信账号限制。一方面是个人能申请的微信账号数量有限,另一方面如果微信安全如果识别到微信账号在做自动化操作,可能会被识别为黑产而导致封号。 为了帮助开发者来解决上述问题,更好地进行小程序自动化测试,我们微信测试团队推出了小程序云测服务。服务主要能力如下: 丰富的自动化测试能力:云测服务支持智能化Monkey、录制回放、Minium脚本等多种自动化测试能力。无论会不会编写代码,都可以来做小程序自动化;全面的性能分析能力:云测服务支持启动性能专项测试,以及小程序运行时的性能分析来帮助开发者针对性的解决小程序的性能问题;支持持续集成:云测服务支持开发中版本、体验版和线上版小程序测试,并支持定时任务以及丰富的第三方API接口,帮助业务打通Devops流程;解决微信账号问题:云测服务支持24个虚拟账号测试来解决前面提到了微信测试账号的问题,同时也支持用户使用真实账号跑测。 最后,我们会对整个测试环境做了稳定性的优化,大大的提高了底层真机的一个稳定性,主要包括: 底层使用了WeTest的云真机能力;对微信小程序的测试环境专项优化;机器智能选择策略,并增加真机测试失败自动重试和换机重试机制。 当然最最最最重要的是小程序云测服务是免费开放给开发者使用。每个小程序每周有150分钟的免费时长(第三方服务商每周有1000分钟)。目前来看可以满足绝大多数项目的一个测试需求。云测服务官方文档地址:MiniTest 小程序云测服务简介 | 微信开放文档。 接下来我们来看一下,怎么安装小程序云测服务。 首先打开微信开发者工具,这里需要注意: 有登录态。需要扫码登录开发者工具,不能用游客模式;需要打开小程序代码;必须是小程序类型,小游戏类型不支持;必须是发布过线上版本的小程序;推荐使用最新稳定版或者nightly版本开发者工具。 在开发者工具中,通过点击 “设置” →“扩展设置” →“其他插件” ,里面可以找到云测,点击安装即可。安装完了之后,在我们开发者工具的右上角,会增加一个云测按钮。点击一下,就可以打开云测服务。 这里再演示一下,我们怎么样快速开始我们小程序的智能化Monkey测试。 其实非常简单,我们直接进来之后,点新建任务。选择安卓,如果想测iOS也可以把iOS勾上,直接点创建任务。这样的话我们就创建了我们一个智能化Monkey的任务。再刷新一下测试结果,可以看到刚才提交任务已经在排队中了 智能化Monkey测试报告,主要分为总览和设备详情。在设备详情里面,重点可以关注下异常情况和体验评分信息。 体验评分主要是去发现小程序运行时的性能问题。比如说我们页面渲染时间太长等。 云测服务还提供了性能分析的能力,可以查看运行时CPU,内存运行曲线,点击曲线的点,会看到这个点对应的截图信息。 后面我们也将继续推出小程序云测服务的课程,如: 如何开始小程序自动化测试、性能测试;如何打通Devops流程;结合业务真正的实践案例,帮助大家快速上手小程序的自动化测试以及性能测试。最后大家在使用云测服务时遇到任何问题或者想要吐槽的,可以查看文档:需要帮助 | 微信开放文档。
2023-07-10 - 使用独立分包和分包预下载(上)
[视频] 你好,我是李艺。 上节课我们学习了按需注入和初始渲染缓存,明确了静态导航页适合使用初始渲染缓存,动态详情页适合使用骨架屏的策略。这节课我们学习分包、独立分包以及分包预下载。 首先我们看一下问题,在冷启动流程中间使用按需注入改良的只是代码注入阶段的性能,如果这个代码包本身它就比较大,那么在第一阶段,也就是资源准备阶段,就会浪费大量的时间,还有如果是代码包过大,它主包超过2MB,在微信开发者工具里面根本也没有办法上传以申请这个版本的审核,为了让这个代码包瘦身,微信开发者工具提供了静态代码依赖分析这个工具,可以帮助开发者找到无依赖文件。 但是有一点我们需要注意,有一些文件它表面上看起来是用不到的,但其实是不能删除的,因为有时候它是在运行时动态加载并使用的,如果这个代码包删除了无依赖的文件以后依然超过了2MB的限制,这时候应该怎么办?小程序又提供了分包以及独立分包的机制,只要单个代码包不超过2MB,总包大小不超过20MB即可。有些开发者可能觉得2MB限制太小了,决定阈值的从来其实都不是规定,而是网络环境和用户群体,国内有很多网民还在使用低端机,包括WiFi以及4G网络环境其实还没有100%的普及,或许等以后5G技术普及以后,限制会进一步的放开,那个时候主包大小或许会进一步的放大,在目前这个阶段,在当下网络环境里面如果要想提升小程序的启动性能以及运行性能,只能在深谙小程序的运行机制以及启动流程的前提之下,在已有的技术框架之下靠精耕细作的精神一个字节 一个字节地去优化。 接下来我们看项目实践。 目前我们的项目已经超过了2MB,必须通过分包分割总代码包的大小。 首先我们看实践一:用户主页分包、创建tab-bar组件。 将用户主页放在分包内,只需要给页面所在的目录添加分包配置就可以了,在app.json全局配置文件里面,我们可以在subpackages这个节点之下添加一个分包设置,root设置为当前页面所在的目录的一个名称,然后再加上pages设置就可以完成用户主页的一个分包设置了。 在小程序里面,如果是使用配置的方式开启tabBar导航,或者是使用这种官方自定义的方式,也就是我们通过在配置文件里面设置tabBar.custom等于true这样的方式实现tabBar导航组件,这个时候tabBar它页面地址只能放在主包里面,为了破除这个限制,我们可以使用自定义的方式实现tabBar的导航。 首先我们需要在app.json配置文件里面将tabBar的配置删掉,我们现在不需要这个配置了,然后我们在components目录下面创建一个tabBar的组件,用它提供全局的导航功能,在组件的JS代码里面会创建一个select方法,它设置哪个链接,当前是选中的状态,这个方法是给外部的代码进行提供的,这个组件的wxml代码很简单,主要是渲染一个list列表,而且这个list的数据还是从原来app.json文件里面复用过来的,wxss样式仅包含自定义组件本身它所需要的样式,这个里边有一个特殊的类样式,就是bar__active。这个是选中的样式,当这个链接与当前这个页面地址一致的时候这个文本它会呈现红色,这个图标也是红色图标,文本的选中颜色还有文本的默认颜色也就是它默认呈现的颜色,与我们原来在app.json配置文件里面的设置是一致的,完全以自定义组件这种方式实现的tabBar导航有哪些优点?我们一起看一下。 首先第一点,页面不再受分包的限制。我们可以将这个页面放到任何一个分包里面去;第二点就是tab-bar组件里边用到的图标,也不再被限制只能放在本地;第三点就是这个代码里面所有的tabBar页面的跳转也不用再单独使用wx.switchTab这样的接口,可以统一使用wx.navigateTo或者是wx.redirectTo这两个接口任何一个都可以,甚至tab-bar组件本身,如果我们愿意的话还可以将它进一步的优化,不放在主包里边,放在分包里面也是可以的。 现在我们开始实践一的代码演示。 在拿到这个源码以后,我们需要在小程序里面进行一个代码的引入,引入方式也很简单,只需要找到对应的目录然后选择,选择以后这个地方AppID要选择我们自己的账号的AppID,这个名称也可以修改,然后单击确定就可以了。我们要实现分包,实现用户主页的一个分包,首先我们看一下app.json这个文件,这个文件里面,目前我们这个地方其实已经有了一个分包设置了,在subpackages下面只需要在这个底下再添加一个新的配置就可以了。这个里面第一个字段要写的是root,它代表的是我们分包的一个地址,这个地方我们写user,因为我们这个项目底下确实有一个user的目录,也就是这个,根目录指定以后,接下来我们要指定pages,也就是这个页面了,这个页面它是我们在分包下面的一个相对地址,我们可以在这个地方复制一个相对路径,然后粘到这个地方,前面这些是不需要的,它只需要从pages下面,也就是从我们目录下面,开始计算路径,这样的话我们分包的一个设置就算是已经完成了。 再往下我们再创建我们的自定义导航按钮,自定义导航的组件在我们的components目录下面,然后去创建这个组件,现在这个目录还没有,可以创建一下目录,然后这个目录下面再创建一个tab_bar这样的子目录,在这个目录下面放tab-bar自定义组件它的一个源码,可以采用一种快速的新建的方式,比如这个地方我们选择新建Component选择这个菜单,然后写入一个index,完成以后,下面所有的四个文件已经全部默认帮我们创建了,下面开始实现这个代码,这个代码我们之前这已经有实现好的,为了快速我们直接复用我们已经实现的代码,这是页面的标签代码,这个标签代码里面我们可以看一下,主要是一个列表渲染,这个地方用了一个wx:for这样一个列表渲染,然后循环渲染这个list里边还有关于image这个地方有一个判断,当前的index是否等于selected就是我们当前选择的索引,如果等于的话,然后去渲染这样的一个图标地址,如果否的话就是选择另外一个图标地址,这是它的样式。 这个地方是我们这个页面的就是文本 一个标题或者说我们页面的一个名称,这个页面的一个跳转是靠我们绑定的一个事件,在这个上面可以看一下,在这个地方有一个tap 一个绑定,nav的方法上面,我们再看一下JS代码,这是我们JS代码,这个JS代码里面nav,这是我们刚才提到的用于页面跳转的一个方法,上面这有一个select它的参数是index就是索引,我们当前选择哪一个,这个方法是对外提供的,在这个页面里面使用这个组件的时候,稍后我们会调用这个方法。 再往上就是data对象了,第一个是一个selected,就是选择的索引,我们当前选择这列表里面哪一项作为选中的页面,它的默认是0,下面这个list这些数据是我们原来的app.json里面的数据,它的一个修改,我们可以看一下我们原来的数据这个地方 这有一个list,我们可以将这个list给它拷贝一下,除了这个list还有两个color,这里有一个color 这有一个color,我们先将这个list给它拷贝一下,然后到tab_bar这个里面,可以放在这个地方对比一下,这两个数据是很像的,它的差别其实只是我们pagePath前面多了一个斜杠,包括iconPath还有selectedIconPath前面多了两个斜杠,其他的其实都是一样的。 当然了我们上面的这种格式是微信它定义的,在app.json里面,如果是要定义tabBar导航的话必须要用这种格式,但下面这种格式是我们自定义的,我们选择这样一种方式定义便于我们和默认的官方配置、保持一致的数据结构,最后我们再看一下样式。这个代码里面只有一项就是component等于true,如果它是个组件的话,在这个json里边它这一项是等于true的样式代码,这是它的一个样式代码,在样式代码里面可以看到这个里面有一个bar__active代表的是选中状态,选中状态下它文本色,这个地方其实是文本,我们可以看到在这个里边这是它的样式,选中以后文本变成这个颜色,d81e06这样的一个样式与我们前面在这个地方看到的样式它俩是一样的,包括我们前面的5151默认的文本颜色跟我们组件里面的样式也是一样的。我们可以看一下,也就是这个颜色,这是它默认的一个颜色,是一样的。这是组件已经准备好了,这个它默认生成的,其实下面那个我们不需要,只需要这一个就可以了,组件已经准备好了。 接下来我们需要在配置文件里面做一个组件的引入,在这个里面也需要写一个usingComponents,然后是tab-bar,一般的话自定义组件的命名方式就使用这样一种单词中间用连字符,因为官方它组件,然后分割单词也是这样一种形式,所以我们就沿用官方的形式就可以了,组件的地址在哪里,这个地方可以复制一下相对地址,然后粘到这个地方再稍做修改这样就可以了,稍做修改,当然后面JS不需要 把这个给去掉,接下来我们就需要在我们的三个页面:用户主页、商品详情页和主页面里面分别添加tar-bar组件的一个使用。 首先我们在user这个里面找到这个标签页wxml这个页面,在它的最下方添加一个tab-bar,再给它一个id,这样一个代码,然后我们还需要在JS里面,在这个页面的onReady的时候onReady,这个地方没有onReady就在这个地方再加一个onReady,在这个地方需要调用这个页面的selectComponent这个地方的参数,我们写#tabBar 井号代表的是id,tabBar是我们刚才定义的id,选择以后再调用它的select方法,要传一个参数,因为我们用户主页居于导航栏的里面的属于它的索引在2这个位置,所以我们这地方要选2。这是用户主页,然后将这个代码复制一下,在另外的两个页面里面,一个是商品详情页,在这个页面最下方也拷贝一份,还有是我们的index主页的最下方也放置一份,这是wxml代码,接着是拷贝我们的选择代码,在主页里面找到onReady,在这个地方可以放在最后面 放在这个位置,这个是主页,数字要设置为1,还有一个是商品详情页,商品详情页在onReady里边添加代码,这个要设置为0,这就是我们的代码,现在已经改造完成了。 单击编译我们看一下运行效果。这个地方有一个问题,就是我们的地址它不应该在subPackages分包内出现这样的一个提示,这个提示是什么意思,看一下我们这个项目的配置文件,现在我们给用户主页实现分包,将这个地址放在了这个地方,当然这个地方多了一个.js,放在这个地方以后,在我们整个文件默认的pages下面,这个地址其实我们就应该给它删掉,它不需要了,一个页面不能同时出现在两个包里,然后这样去处理再编译一下 看看效果。 这个地方也有一个问题,模拟器无法获取这个文件,已经被忽略上传,它这个忽略上传我们暂时不要去理它,只是它自己的一个提示,现在要测试导航组件的一个表现是否正常,选择商品页,商品页现在高亮了对吧,然后选择主页,然后主页高亮了对吧,然后选择我的页面,我的页面也高亮了对吧。这个导航已经实现了它预想的一个功能,现在我们拿我们的商品详情页,就是detail这个页面它作为启动页再测试一下,也没有问题对吧,也显示了这个代码它的作用是良好的。 在这个地方有一点我们需要提示一下,我们这个代码里面是有选择组件,然后再调用其中的方法的代码也就是这个对吧,在主页里面这个地方选择组件然后再调用它的方法,选择这个组件它其实未必是可以选到的,所以如果这个地方我们要避免出错的话我们可以加一个问号,加个问号以后 就是ES6里面的可选链语法,如果是它不是null,然后找到这个组件,调用它的一个方法,这样的一个语法,这是主页,然后在这个商品详情页里面也是一样的一个处理,这个地方加一个问号,还有一个是在用户主页这个地方也加一个,我们再测试一下,没有问题。正常情况下这个页面已经走到了onReady这个地方,按理说它不会出现选择不到的这种情况,但是有一种情况我们稍后可能会遇到,如果是我们当前这个页面,它本身它跟我们的用到的组件,我们现在这个组件tab-bar这个组件是放在我们主包里面,因为它是在app.json里面配置的,如果是我们这个页面是在一个独立分包里面 它是独立的,然后它显示的时候其实主包没有加载,主包里边的组件你也使用不了,这种情况下有可能,这个地方我们要去选择组件的时候,它可能拿到的是一个空值或者是一个undefined,你在一个undefined调用它的方法,显然是不合适的话可能会报错,所以这个地方所有的组件其实都是一样,你如果想万无一失的话,给它加一个问号,这个问题就可以轻松化解了。 这个代码演示就到这里了。
2022-07-14 - 长列表也可以丝滑
使用小程序 scroll-view 即可实现丝滑长列表 ———— [图片] [图片] 对于长列表出现的白屏、卡顿、界面跳动等问题,小程序提供了新 scroll-view 来解决这一系列问题。我们先来看看效果~ 快速滚动效果对比我们通过一组长列表来展示新旧 scroll-view 在快速滚动下的效果对比。 当长列表快速滚动时,旧 scroll-view 容易出现白屏的情况,新 scroll-view 则不会出现白屏。 左:旧 scroll-view、右:新 scroll-view [视频] 在安卓机器快速滚动过程中,旧 scroll-view 反应卡顿,容易出现手指离开操作时,滚动动画还在进行。 而新 scroll-view 则可以保持界面滚动效果跟随手指,停止滚动则缓慢结束动画效果。 左:旧 scroll-view、右:新 scroll-view ,测试机型:Xiaomi MIX 3 [视频] 反向滚动效果对比在对话等场景下,反向滚动是常见的功能,旧 scroll-view 并没有提供反向滚动的能力,我们来看看旧 scroll-view 下是怎么完成反向滚动的~ 在对话数据在加载的时候,对话列表需要在更新完列表数据之后,再使用 scroll-into-view 或者 scroll-top 来保持当前滚动位置,因为设置滚动位置会有延迟,所以容易出现 界面跳动 的情况。 // .js // scroll-view 滚动到顶部时触发 bindscrolltoupper() { // 先更新列表数据 this.setData({ recycleList: getnewList() }, () => { // 更新完数据后再设置滚动位置 this.setData({ scrollintoview: scrollintoview }) }) } 为了解决界面跳动的问题,社区上也有通过翻转的方法来解决,将 scroll-view 与 scroll-view 的子元素进行翻转。 // .wxss .reserve { transform: rotateX(180deg); } // .wxml <scroll-view class="reserve"> <view wx:for="{{list}}" class="reserve"></view> <scroll-view> 然而进行翻转之后,会遇到手指滚动方向与列表滚动方向相反、scroll-into-view 属性无效等问题。 为了帮开发者们解决反向滚动类列表的一系列问题,新 scroll-view 直接提供了 reverse 属性支持反向滚动的能力,滚动效果更加顺滑。 左:旧 scroll-view、右:新 scroll-view(图片加载期间,GIF 渲染较慢) [视频] 怎么接入新 scroll-view ?新的 scroll-view 使用起来很简单,主要有以下两个步骤: 修改小程序配置scroll-view 增加 type="list"// app.json // "renderer": "skyline" 开启之后所有页面会变成自定义导航,可参考 https://developers.weixin.qq.com/s/Y5Y8rrm37qEY 实现自定义导航 // 也可在 page.json 中配置 "renderer": "skyline" 逐个页面开启 { ... "lazyCodeLoading": "requiredComponents", "renderer": "skyline" } // page.json { ... "disableScroll": true, "navigationStyle": "custom" } // page.wxml <scroll-view type="list"> ... </scroll-view> // 反向滚动 <scroll-view type="list" reverse></scroll-view> 新的 scroll-view 从安卓 8.0.28 / iOS 8.0.30 开始支持,如需兼容低版本需要进行兼容处理。 wx.getSkylineInfo({ success(res) { if (res.isSupported) { // 使用新版 scroll-view } else { // 使用旧版 scroll-view } } }) 如需体验长列表效果,可在微信开发者工具导入该代码片段即可体验:https://developers.weixin.qq.com/s/Y5Y8rrm37qEY 更多接入详情请参考文档 怎么做到的?大家肯定好奇为什么新 scroll-view 可以解决这个头疼的问题呢? 我们来对比一下新旧 scroll-view 有什么区别就可以知道答案啦~ 旧 scroll-view 逻辑层与渲染层的通信需要通过 JSBridge 进行转换,需要一定的时间开销渲染采用异步分块光栅化,当渲染赶不上滚动的速度,来不及渲染则会出现白屏渲染大量节点内存占用高,需要开发者自行优化只渲染在屏节点,开发成本高新 scroll-view 逻辑层与渲染层的通信无需通过 JSBridge 进行转换,减少了大量通信时间开销渲染采用同步光栅化,滚动与渲染在同一线程,不会出现白屏针对长列表进行优化,只渲染在屏节点,内存占用低,减少了一些渲染耗时,且开发接入成本低[图片] 除此之外,新 scroll-view 后续将提供 type="custom" 支持 sticky 吸顶效果、网格布局、瀑布流布局等能力便于开发者接入使用~
2023-10-20 - 小程序性能优化实践
小程序性能优化课程基于实际开发场景,由资深开发者分享小程序性能优化的各项能力及应用实践,提升小程序性能表现,满足用户体验。
2024-10-09 - 了解小程序的启动流程(上)
[视频] *所有课程源码的链接:https://gitee.com/geektime-geekbang_admin/weapp_optimize 你好,我是李艺,是腾讯云TVP 、小程序从0到1的作者,极客时间 微信小程序全栈开发实战课程讲师。 小程序上线已经5年时间了,日活目前已经达到了4.5亿+,已然成为任何一家互联网企业都不能忽视的产品运营阵地。小程序的开发能力经过微信团队数年来的不断地努力,目前已经日臻完善,它早已经不是那个随随便便翻翻文档就可以完成开发的一个简易框架了,尤其是在性能优化方面,会优化与不会优化 ,对于产品的运行效果可以说有霄壤之别。 首先我们看第一部分,需要明确一下有哪些需要优化的现象。不知道你的小程序产品有没有遇到过这样的一些问题,点开小程序一直都是白屏什么也看不到、Loading加载提示转了好几圈页面还不显示,单击页面链接的时候页面跳转迟钝迟迟打不开、有的按钮单击了好几次一点反应也没有,长列表内容在滑动的时候越往下滑 页面越卡顿。你有没有因为这些问题而遭受过用户的抱怨,你会以为这些问题都是因为平台技术不完善而造成的吗? 那么为什么京东 滴滴等大厂的一些小程序,它们的功能那么繁杂。但使用起来却还是那么流畅呢,当我们抱怨框架不够给力的时候,我们对微信小程序的性能优化技巧又真正了解和使用多少?性能优化它是一个现人现地的活,讲究具体问题具体分析,需要有一个字节 一个字节去抠,一个毫秒 一个毫秒去节省的这样的一个细致精神。这个课程我们会演示相关的性能优化技巧。我准备了一个性能比较堪忧的项目,这个课程我们就一起诊断 优化这个项目,让它从体态臃肿的一个状态慢慢变得健步如飞,为了更好地理解和应用小程序优化技巧,在开始实践之前,我们十分有必要看一下小程序整体的运行环境以及启动的流程。小程序的运行环境大体可以分为三类,第一类是iOS端、Mac微信端,第二类是Android端 PC微信端,第三类就是我们开发者经常使用的微信开发者工具模拟器端。另外还有儿童手表上面也有微信,但是那个环境它没有小程序,所以不在我们的讨论范围之内。 三类的运行环境,虽然它们在底层是基于不同的技术实现的,但是它们的启动流程大体上是相似的,小程序的优化主要是指从小程序开始启动到首页完全渲染显示,也就是Page.onReady事件派发,这个过程之间的一个优化。这个过程主要包含了三个流程的节点,这一步包括小程序运行进程及运行环境的准备,这里面具体又包括拉取小程序基本信息,包括代码包版本 地址等信息,另外还有Native小程序进程和微信基本模块的一个初始化。例如在Android环境里面有Activity活动组件的一个初始化,再往下是代码包的下载 校验以及初始化,再往后是系统组件 WebView组件容器和原生组件的一个初始化,最后是JS引擎初始化以及域的创建。 下面我们看第二步关于代码注入,这一步主要包含两大部分:第一部分是框架及第三方基础代码的一个初始化,这里面又分为三个小部分: 第一部分是小程序基础库的注入; 第二小部分是扩展库,例如我们在配置文件里面通过使用useExtendedLib引入的WeUI以及kbone这样的一个类库的初始化; 第三小部分是插件、自定义组件 扩展库代码的一个注入。 第二部分是开发者代码的一个注入,这个里面主要分为两个小部分: 第一个小部分是开发者逻辑层代码的一个注入,这里会派发小程序里面的App.onLaunch还有App.onShow这些事件的一个派发,这些事件都是在这个阶段进行派发的; 第二小部分是开发者视图层代码的一个注入,包括公共代码以及页面代码的一个注入。 下面我们看第三部分关于首屏渲染,这个部分大致可以分为五个小部分: 第一小部分是页面的初始化,这个时间点是initDataSendTime这个时间点的一个触发时机,会有Page.onLoad一个事件的派发; 第二小部分是时间点走到viewLayerReaderStartTime这样的一个时间点的阶段,这个时候会有Page.onShow事件的一个派发; 第三小部分是开发者代码从后端拉取数据,准备data数据,这个时候也是一个阶段,是第三小部分; 第四小部分是页面的一个整体的渲染; 第五部分是当这个时间点走到viewLayerReaderEndTime这个时间点的时候,它会有一个Page.onReady事件的派发,这个时候就标志着我们首屏渲染的一个完成。 小程序采用逻辑层、视图层双线程运行机制,Native的工作准备它是先于这两个线程开始之前开始的,基础的执行环境准备好以后,逻辑层与视图层两个线程才开始工作,并且两个线程几乎是并发执行的。在视图层与逻辑层它代码完全注入以后,这个时间点它会对齐以后才会进入下一个阶段,也就是首屏渲染这个阶段的开始执行。 这些节点它并不是每一次小程序启动时都会经历的,有些会有,有些不会有。微信有运行环境预加载机制,如果小程序在启动时命中了预加载的环境,有关准备运行环境的节点就可以省略掉,这一部分的启动时间也可以节省了。对于开发者紧跟小程序框架的更新,及时使用用户覆盖率最广的基础库版本,让自己的小程序运行环境大众化、普通化,则有助于预加载环境的一个命中,终端类型不同经历的节点也不尽相同。 在Android上小程序启动的时候,微信它开启了新线程,在iOS上则没有 iOS上小程序它在启动的时候,它会复用与微信相同的一个进程,因此Android上有小程序进程与Activity初始化这样的一个节点,在iOS上则没有,再加上iOS的设备它普遍的性能是高于Android设备的,所以这使得iOS的设备的启动的效率普遍就高于Android。 对于相同版本的小程序在同一性能级别的设备上运行,iOS设备平均会比Android大概会少0.5的这样一个启动时间,另外还有启动方式对流程经历的节点也有影响。 下面我们看一下启动方式。小程序按启动方式不同分为冷启动和热启动两种方式。 什么是冷启动?什么是热启动?如果小程序在用户设备上是第一次打开或者是销毁后再次打开,这个时候的启动就是冷启动。热启动是相对冷启动而言的,热启动是小程序启动的一种优化机制,小程序进入后台30分钟以内再次进入前台,可以直接从后台状态然后恢复到前台,在这种热启动方式里面,像前面我们提到的小程序基本信息、拉取代码包的下载还有JS引擎初始化等等这些流程节点,甚至像App.onLaunch、Page.onLoad以及Page.onReady这些一次性的流程事件都不会有了。小程序第一次启动以及冷启动30分钟以后被系统回收重新再启动都是冷启动。 前面我们讲的启动流程的主要节点是冷启动流程的节点。我们说的小程序性能优化主要是指冷启动性能的一个优化以及运行时渲染性能的一个优化,在小程序冷启动流程里边涉及到一些程序以及页面事件。下面我们统一看一下这些事件。 在小程序中App与Page都有它们各自的一个生命周期函数,这些周期函数有一些与启动流程是密切相关的。我们先看一下App周期函数,这里面有三个事件需要我们注意: 第一个是onLaunch,它是监听小程序初始化的一个事件; 第二个是onShow 监听小程序启动或切前台这样一个事件; 第三个是onHide 监听小程序切后台这样的一个事件。 下面我们再看一下Page周期函数。这个里面有五个事件是与优化相关的: 第一个是onLoad它监听页面加载; 第二个是onShow监听页面显示; 第三个是onReady监听页面初次渲染完成; 第四个是onHide监听页面隐藏; 第五是onUnload监听页面卸载。 App.onShow事件和Page.onShow事件是视图界面开始显示时派发的,它们会重复派发与启动流程优化密切相关的一次性事件,主要有App.onLaunch Page.onLoad和Page.onReady这三个事件,在这三个事件节点恰当的安排执行合适的逻辑代码是优化的重要技巧一。 至于像App.onShow App.onHide以及Page.onHide Page.onUnload是与运行时性能优化十分相关的一些事件,下面我们根据小程序的冷启动流程以及与其相关的密切相关的一些八个生命周期函数大致讲一下有哪些节点是可以优化的。 第一条在这个环境准备阶段中,在拉取小程序基本信息阶段,这个阶段是有可能优化的。微信对用户设备上经常使用的小程序它会有轮询机制,在轮询的时候会自动拉取小程序的一个基本信息,正常情况下这个小程序的基本信息的一个拉取它是同步的,它会阻塞我们后续流程节点的一个执行,如果通过轮询节省了这样的一个过程,启动流程跳过这个节点时间是可以节约的,当然这个节点开发者基本上做不了什么事情,开发者并不能左右微信的轮询机制。但是越受用户欢迎的一个小程序因为它属于经常使用的小程序,它会命中轮询机制,启动的一个性能也会更好,而那些不被用户经常访问的小程序反而没有这个福利,这大概就是技术里面的一个马太效应,就是好的会更好然后坏的会越差。 针对环境准备阶段微信提供了环境预加载机制,微信客户端会根据用户设备的使用场景和设备资源的一个消耗情况,依据一定的一个策略,在小程序启动之前对运行环境进行部分的预加载,这个过程开发者基本也无法干涉,开发者能做的仅是紧跟小程序基础库的一个更新,积极使用最新的、最普遍的、最广泛的一个基础库版本以及提高预加载环境的一个命中率。 在代码注入阶段,逻辑层与视图层代码都需要注入,两个线程的代码都注入完成以后首屏渲染流程才能开始,Page.onLoad事件才能触发。我们可以想方设法减少代码的一个注入量和复杂度以期减少启动时间,小程序在这方面有分包、有独立分包、有按需注入、有用时加载和占位组件等等这些特性,这些都是这一阶段的一些优化技巧。这些技巧稍后我们在课程里面都会详细介绍。 在合适的生命周期函数节点执行合适的代码也可以优化启动性,Page.onReady事件派发于首屏渲染完成的时候,如果我们要从后端拉取数据并在首页上进行渲染,在这个事件函数里面执行拉取操作,势必会造成二次渲染的CPU资源浪费,但如果我们在Page.onLaunch这个事件触发的时候就开始数据拉取,又可能会阻塞小程序正常的一个启动流程,在这种情况下我们要怎么去做?我们可以使用异步转同步的编程范式以及使用并发复合命令,在多个文件里边对齐这个代码的执行点,这样的话就显得尤为重要了。具体的优化办法,稍后我们在课程里面会详细讲解。 从Page.onLoad事件派发页面开始渲染到Page.onReady这个事件派发首屏渲染完成,这中间涉及到的动态数据加载,其加载的数据量有多少、网络请求所需的时间有多少还有图片等静态资源它加载所需要的时间有多少,都会影响首屏渲染的一个效率,这个阶段使用骨架屏技巧包括压缩图片、提高服务器接口响应效率和数据传输效率等等,这些都可以优化首屏渲染的一个用户体验。针对小程序里面用到的一些数据,微信还提供了数据预加载周期性更新机制,不需要开发者自己去拉取微信就可以代为拉取,小程序在启动的时候,直接取用这些已经加载好的数据就可以了,这也是优化启动流程的一个技巧之一。 当然了这个技巧是微信团队特意为开发者而设计的,针对低端机首次渲染需要较长的一个时间,微信提供了初始渲染缓存机制,启用初始渲染缓存可以使视图层不需要等待逻辑层代码初始化完毕就可以直接提前将这个页面初始化的数据渲染的结果展示给用户。 以上就是针对启动流程中部分节点的一个性能优化技巧,稍后我们在课程里面都会详细地进行讲解在运行的时候针对小程序的双线程运行机制和视图重渲染机制也有相关的一些性能优化技巧。下面我们就再看一下这方面的一些技巧。
2022-07-29 - 使用异步转同步的编程范式
[视频] 你好,我是李艺。 上节课我们主要学习了如何在小程序里面使用WebAssembly技术,这节课学习异步转同步的编程范式。 首先我们看一个问题,JS它作为一门应用最广泛的前端编程语言,主要有两大特点,第一个特点是单线程,第二个特点是异步编程。对于第一点单线程很好理解,不是JS不能支持多线程,而是JS在诞生的时候它本身就是作为HTML页面的交互语言而存在的,人类的屏幕交互本身就是一个单线程的,这要求JS也必须是单线程的,如果是在一个页面之上同时有多个交互控件需要人类去处理,那么这个用户他能处理过来吗,显然这可能是为章鱼而设计的对吧,不是为人类而设计的。 至于第二点异步编程,这是JS语言区别于Go语言、C、C++、Java等后端编程语言最主要的一个特征,在后端编程语言里边如果我们要读取文件或者是要发起一个网络请求,必须要等待这个结果返回,返回以后才可以进一步执行下面的一个代码,在编程里面这叫做同步开发 JS不一样,除了主线程以外还有一个或者多个异步线程,异步线程处理worker timer定时器、网络请求、用户输入监听、事件派发等等这些任务,当异步线程有回调函数代码需要执行的时候,异步代码它将这些代码推入到主线程的执行队列里面去,由主线程在不是很忙碌的时候尽快将这些代码进行执行,这种方式就叫做异步编程。 在异步编程里面如果有后续逻辑是依赖前面这个代码请求成功的前提才可以执行的,这些代码将写在回调函数里面,如果后续又有异步线程需要执行,还要进一步的内嵌,这种方式就使得我们代码可读性非常差,当然有经验的程序员可能会讲,你可以另外再创建一个函数,然后在这里去调用它,没有必要一定要把这个代码写成俄罗斯套娃的这样一种形式,这样当然也可以。但是另外定义函数上下交织进行调用,这又会破坏我们整体上代码线性逻辑的一个连续性,可读性又不能得到根本性的一个保证,计算机CPU的工作模式是多核并发的,但本质上人类的思维却是线性的,就像我们阅读小说一样,我们没有办法同时阅读多页或者是同时阅读多本书,这样也是不可以的,上下连贯的连续的逻辑更容易被人所理解和接受,为了解决异步线程在多层异步代码嵌套情况下带来的代码可读性差这个问题,我们可以使用异步转同步编程技巧,它依赖于ES6的async/await一套语法而实现,它本身是异步的,但是却可以以同步的方式编写和呈现,它既有后端同步编程的表又兼具我们前端异步编程的里,是一个非常不错的通用的代码优化技巧之一,下面我们看项目实践。 首先看实践一,编写和实现异步转同步编程范式的这个工具函数。 首先我们需要创建一个promisify的JS文件,这个文件里面会导出一个promisify函数,这个函数它最大的一个作用就是将异步请求的wx Api转化为可以同步调用的方法,小程序里面大多数接口都已经支持了Promise风格调用,凡是支持的在官方文档上都有标注,但是目前仍有少部分接口它是不支持的,不管是支持还是不支持,我们都可以用这个工具函数进行转化。在这个地方我们说一下,自定义的全局的require方法到底能不能使用,前面我们提到过这个问题,在使用require关键字引入模块的时候,有时候我们想使用绝对路径以保持在各个纵深层次文件里面引用同一个文件时的一个路径一致性,这时候我们可以在app.js文件里面,在这个App实例上定义一个替代的require方法,这样的一个require方法不仅可以用于分包异步化的一个加载,还可以用于普通的CommonJS模块加载,并且保持路径的写法一致性,好处实在是多多,但是有一点我们需要特别注意,替代的require方法使用了路径变量,这可能会给我们项目带来隐藏的麻烦,在微信小程序里面,使用require加载模块的时候,有时候我们可以使用变量来标识这个路径,有时候却不可以,为了保险起见,项目里面我们最好不要用这样的一个全局方法。 下面我们看实践一的代码演示。 我们这个函数要写在我们项目下面有一个特殊的目录叫做library,library目录本质上就是我们要放一些JS代码,并且是在所有分包里面基本上都要使用的一个JS代码,在下面有一个optimus这样的一个子目录,我们request替代的异步转同步的工具函数promisify就定义在这里,这个方法我们可以看到它其实已经存在了 对不对,就是这样的一个方法,本身导出,这是一个方法名字,然后它会传进来一个方法或者是函数,传进来以后,我们会导出,就返回这样的一个,本身它返回的也是一个函数,所以返回的结果我们首先要去调用,调用以后它会返回一个Promise的对象,Promise对象里边又有fn的一个调用,其中在这个地方我们重点是做了一个Object.assign将我们的后面的对象 自定义对象写到args对象里面去,这个地方我们重点是做了一个success向resolve以及fail向reject它本身的一个映射,从而实现我们这个方法调用的时候让它的调用结果变成一个Promise这样的结果,本身我们Promise对象是我们ES6,使用ES6这种语法以后,它本身就支持了,所以我们这个地方可以直接使用,这就是我们的promisify工具函数的一个实现了。 另外我们还需要再看一下我们前面提到的,在这个app.js里边,我们提到的会有require这个函数 还有requireAsync,本质上它们两个原理是一样的,这种方式最好不要去用,当然你如果大胆一点你也可以用,但是如果是你项目里面出现了不可理解的一个异常的时候,你如果要排查错误,可以先把它给它注掉,注掉后看看它的一个表现,默认情况下我们就不使用这样的一个方法了,至于路径写法稍微麻烦我们就麻烦一点,其实麻烦一点,本质上也只需要改一次,我们路径拷贝以后只需要修改一次就可以了,它也不需要重复进行变动,代码演示我们就说到这里。 接下来我们看实践二,改写wx.request的请求。 接下来开始改写,使用wx.request编写的网络请求代码,这是一个标准的wx API,根据官方文档显示,它本身是不支持Promise风格调用的,它也有返回值,但是这些都不影响我们使用统一的编程范式去改写它,使用统一的编程风格可以有效减少我们脑细胞的一些浪费,以最经济的一种方式实现,具有良好的可阅读性,以及可维护性的一些代码,具体实践的时候可以在这个项目里面搜索,然后wx.request,看看有哪些代码用到这个接口了,再以promisify函数修改为伪同步调用的方式。 在我们这个项目里面大概会有三处以上的代码涉及到修改,在改写的时候我们主要有三点需要注意,第一点就是引入这个工具函数promisify的时候,因为我们的工具函数本身是用ES Module这种规范编写的,所以在引入的时候如果是用CommonJS模块规范进行动态引入的话,要从default上面做解构赋值,第二点就是给父方法添加async标记,为什么要加这个标记,因为在这个方法内部要使用await关键字,第三点就是在promisify这个工具函数改造wx.request接口并且调用的时候,主体代码的逻辑基本是保持不变的,其实只需要将上下这个代码位置稍微变一下就可以了。 下面我们看实践二的代码演示。 这个代码演示在微信开发者工具里面不是特别的好改写,不过我们同时也有VSCode,我们用VSCode来改写,首先找到我们的项目目录,找到小程序的根目录,然后在上面用这个文件夹里边搜索这个功能,让我们搜索wx.request,这个地方注意 可以看一下这个地方有1个 2个 3个 4个,一共涉及到3个文件里面4处代码的一个修改。 我们先看第一个,这个地方首先这是我们的商品详情页,这个地方首先我们要把我们这个工具函数进行引入,引入这个地方要用一个析构赋值 default等于promisify等于request,这个地方就要写相对路径了,好在VSCode有着很不错的一个代码提示功能,当我们敲这个路径的时候它可以自动帮我们完成,这样就可以了,引入了对吧,引入了以后接下来改写,怎么改写 首先第一步前面这个地方要加async,然后这个地方我们将下面先关掉,在前面加一个 res等于await然后等于它,后面我们要用promisify工具函数把它转一下,把它当成一个参数传进去,传进去以后,后面这个参数其实不变,在这个地方正常的结果我们要放在它的下面,改变一下它的位置,然后这个代码现在要给它去掉 就不需要了,success也不需要了可以放在,直接这么写是不可以的,要加一个catch,这样就可以了。后面这个是对前面返回结果的一个调用,这样就可以了,这是第一个修改。 接下来我们再看第二个修改,这个地方第一步仍然是引入default:promisify等于require然后路径library optimus Promisify.js已经引入了,找到下面这个的位置,在这个位置首先一定别忘了里面前面加async关键字,然后在这个地方const res等于await,然后promisify把它转化一下,这个代码是最终正确逻辑的一个处理代码,然后拷贝到下面,这个缩进稍微处理一下,这个给它去掉,然后这个地方加上catch,这个就改写完了。 下面还有一个,前面那个工具方法已经引入过了,它是同一个文件,所以我们直接使用就可以了,同样的方式,也是将它转化,转化以后调用,然后正确处理的代码拷贝一下 放在下面,这个地方删掉加一个默认的catch,是不是很简单。 还有最后一个文件,最后一个文件改造方法是类似的,等于require,找到合适的路径,再选择我们这个方法,这个地方加上res等于await,然后转化,这个地方不要忘了加async关键字对吧,我们要加在这个地方,因为它本身这个是一个函数常量,正确的处理结果放在下面对吧,方式是一样的,改写方式是相同的,然后最后是catch,这样就结束了,我们的改写已经完成了。 回到我们小程序里面单击编译,重新测试一下效果,首先看看有没有编译错误,目前来看没有什么编译错误,然后看一下表现,我们发现这些加载的数据也如期加载了没有问题,然后在商品详情页里边好像也没有什么问题,我们可以点一点看看有没有问题,也没有问题。这就是使用promisify对我们wx.request的接口的一个改写,这个里边我们有一点可以多说一点,就是在我们目录下面还有另外的一个文件同时还有一个promisify_on,这是做什么用的,它这个其实是改写我们小程序里面有一些以ws开头的onXxx这样的一类的接口,将这类接口然后改写为这个,就是异步转同步这样同样的一个编程方式这样的一种写法,当然我们不常用,所以在我们项目里面其实也没有用到,我们最常用的其实就是这样一个方法,这个代码演示就说到这里。 最后我们总结一下,因为小程序里边require它不支持绝对路径,而使用这个相对路径又会给我们编程带来额外的一些麻烦,一个路径长不说,在拷贝的时候还不能直接复用原来的模块引入代码,为什么在app.js文件里面定义全局的require方法可以使用绝对的路径,主要有两点原因 一是全局的程序的实例app,它可以通过getApp进行取用,然后取到它以后就可以调用它上面的方法了;二是app.js这个文件里面本身这个文件它位于项目的根目录下,我们要利用它得天独厚的位置以便可以从这个项目的根目录进行路径的一个标记,工具函数promisify在使用的时候有两点我们需要特别注意,第一点,函数在调用的时候为了避免这个程序报错,我们一般在后面一定要加一个默认的catch设置,回调函数如果没有特别的需要,我们可以默认传递console.log这样的一个方法,如果程序对这个错误处理有特别的一个需求,在传递这个console.log这个方法的一个地方,我们还可以进一步的进行完善和优化,第二点就是使用promisify函数的父函数,由于我们在这个里边使用了这个await 所以在外面,一定要添加async关键字,在添加async关键字以后,它已经不是一个异步执行的这样一个函数了,我们一定要避免在主线程上以阻塞的方式,也就是我们前面提到的添加await这样的一种方式直接去调用这个函数,关于第二点它很重要,如果使用不好的话可能会有副作用,我们可以再进一步的来阐述一下这个问题,在主线程上面可以挂很多使用async标记的函数,但是这些函数它其实都应该像葡萄一样挂在主线程之上,它们执行不应该阻塞主线程的一个执行,async这个关键字 这个单词它本身是异步的意思,放在这个函数前面表示这个函数里面有await起始的异步代码,在运行的时候我们可以理解为它执行了两次,第一次是执行到await关键字的时候,这个代码停在这里,第二次它拿到异步的结果以后继续从await后面的代码处继续往前执行,async await这一对关键字它是ES6的语法,它编译为ES5代码以后和我们原来使用回调函数的写法是类似的,但是这种写法前面我们讲了,它有助于让我们这个代码更加清晰 易读,代码的执行效率很重要,代码的易读性对开发者来说也很重要,所以异步转同步的编程范式是JS开发里面也是它最重要的一个优化技巧之一,这节课我们就讲到这里。上面的网址是本课涉及的文档地址。 点击查看开放文档: 网络 /发起请求 /wx.request 这节课主要学习了异步转同步的一个编程范式,那下节课我们学习并使用并发复合命令模式对齐代码的执行点。 最后我们说一下思考题。这里有个问题请你思考一下,在这个项目开发里面我们经常会遇到这样的一个问题,某一处代码C的执行需要同时满足条件A和条件B,假设获得条件A需要三秒,获得条件B需要两秒,那么最快我们可以多久可以执行这个代码C,在数学上这个答案很明显 是三秒对不对,但是开发里面,因为获得条件A和获得条件B的代码可能不在同一个文件里面,并且有时候它们也不一定可以同时开始,谁先开始 谁后开始不一定,那么在这种情况下,我们应该如何从整体上设计代码才能让最后这个代码C尽快得到执行呢?这个问题先留给你思考一下,下节课我们一起来深入探讨一下这个问题。
2022-07-14 - 收下这份指南,小程序蓝牙应用不再难
在移动互联网全民化发展的当下,蓝牙技术凭借着简便、安全、低成本的优势逐渐成为无线技术领域的中流砥柱。在共享单车领域,电子设备和单车锁通过蓝牙技术形成可靠且安全的连接,从而提升用户体验。 微信团队一直致力于 小程序蓝牙能力 的应用开发,提升小程序在无线技术领域的高效、安全、可靠应用。通过 滴滴青桔小程序 等项目落地验证,小程序蓝牙能力的合理应用能够实现搜索成功率、连接成功率、开锁成功率的全面提升。 [图片] 当前主流的蓝牙技术主要是经典蓝牙 (BR / EDR) 和低功耗蓝牙 (BLE),其中 BLE 凭借着与电子设备的简便互操作性,成为大部分共享单车蓝牙锁的重要技术选型。 在实际应用场景,小程序蓝牙技术与蓝牙锁之间进行以下技术交互,实现解锁。 Step 1:蓝牙扫描 扫描蓝牙广播,检查 Android 系统直连模式是否连接正常。 Step 2:生成通信帧 与锁建立 BLE 连接后,准备获取锁的 Token 通信帧,将传递信息 AES 加密并转化成 HexString 发送。 Step 3:获取锁的 Token 将传递信息 AES 解密后收到 Token 获取成功通信帧,同时截取相应位数的字节作为令牌 Token,准备开始解锁。 Step 4:等待解锁 发送解锁信息通信帧,利用 Password 还有 Token 同样将传递信息 AES 加密并转化成 HexString 以一定的规则发送,等待解锁。 Step 5:解锁成功 收到蓝牙返回的信息 AES 解密后得到解锁通信帧代表解锁成功。 [图片] 通过 滴滴青桔小程序 等项目的实践探索,合理应用小程序蓝牙技术能够实现解锁全链路的优化提升—— 提前搜索时机提升蓝牙搜索成功率 蓝牙技术在搜索耗时的原生限制导致单车锁-电子设备之间的搜索成功率偏低。因此在不增加用户功耗的前提下,适当提前搜索时机能够减少搜索设备的耗时,提升蓝牙搜索成功率。 [图片] 默认直连模式提升蓝牙连接成功率 大部分的 Android 系统手机均支持直连模式,建议针对 Android 系统,默认使用直连模式,加速蓝牙连接流程。如果直连模式失效,则重新搜索,连接附近设备。 [图片] 建立失败重试机制提升蓝牙开锁成功率 低功耗蓝牙的原生不稳定性要求开发者做好重连逻辑。建议建立失败重试机制,即在每个 API 回调失败后,允许失败重试 3 次,用于提升开锁成功率。 [图片] 在小程序蓝牙技术应用过程中,总结以下应用注意点,助力开发者更好地应用蓝牙能力。 连接前 开启 notify 功能:连接前必须调用 notifyBLECharacteristicValueChange 才能接收到设备推送的信息,否则只能发送数据,无法接收数据。注意 iOS 与 Android 的蓝牙应用区别[图片] 连接中 停止搜索以减少性能消耗:调用 onBluetoothDeviceFound 搜索设备成功,必须执行 stopBluetoothDevicesDiscovery 停止搜索,避免后台资源消耗性能。不使用 Android 1800 / 1801服务:在获取设备服务时,部分 Android 机型增加系统自带的 00001800 和 00001801 服务,请不要使用这 2 项服务。重装微信获取其他设备服务:部分 Android 机型通过 getBLEDeviceServices 仅得到系统自带的 00001800 和 00001801 服务,重装微信即可获取其他设备服务。 连接后 及时关闭连接及蓝牙设备:操作完成后,及时关闭连接及蓝牙设备,否则在 Android 系统下,出现设备搜索失败的情况。成对调用创建和关闭 BLE 连接:建议成对调用 createBLEConnection 和 closeBLEConnection 接口,否则在 Android 系统下,重复调用 createBLEConnection 接口,可能导致系统持有同一设备多个连接的实例,最终导致调用 closeBLEConnection 不能真正断开与设备的连接。 合理应用小程序蓝牙技术能够丰富小程序的应用场景,提升小程序性能表现,进一步优化用户体验。 如有其他小程序应用相关的问题,可在 微信开放社区小程序交流专区 发帖互动,技术专员将为大家解答及进行深度交流。
2022-07-12