- 微信小程序可以将多张图片合成为MP4视频吗?
如题,我有多张图片,想合成为MP4视频,如果不行,合成为GIF动画也行
2023-05-05 - 微信小程序怎么区分是热启动进入的页面还是从当前小程序内别的页面跳转过来的?
微信小程序怎么区分是热启动进入的页面还是从当前小程序内别的页面跳转过来的? 前提是在一个小程序里
2023-05-22 - 微信开发者工具1.06.2401020 Stable 更新说明
下载地址Windows 64、Window 32、MacOS、MacOS(M1) 当前版本基于 1.06.2312061 修复了以下bug: [代码]F[代码] 修复 win11 预览版系统工具启动卡死的问题[代码]U[代码] 优化 编辑器打开较大 JS文件解析的时间[代码]U[代码] 优化 不应该允许开启调试器设置 Selected Context Only[代码]F[代码] 修复 异步分包出现重定义的问题 新增功能特性:1、wx.request 支持处理重定向请求 文档地址工具对齐真机,支持调试 wx.request 重定向请求拦截能力。 2、工具多端插件模式本版本完善了系列工具配置多端能力,同时为方便开发者实现在多端应用中引入第三方 SDK 或实现原生扩展的一些功能,新增支持开发者通过「多端应用插件」的方式以实现与 native 的连接与扩展。更多文档详情[图片] 3、上传预览展示优化针对上传、预览因为文件大小超过限制的情况,增加返回编译后的文件列表,和对应的大小,方便用户排查体积问题 [图片] [图片] 4、Mock 调试支持 getFuzzyLocation 接口[图片] 5、基础库推送增加桌面端微信客户端[图片] 6、小程序渲染调试能力优化增加完善 glass-easel、skyline 渲染能力的工具适配支持 具体所有特性:[代码]A[代码] 新增 多端 Android 支持打包 aab 格式[代码]A[代码] 新增 [代码]wx.getSystemInfo[代码] 对齐客户端的内容[代码]A[代码] wx.request新增支持redirect参数传值manual[代码]A[代码] 多端安卓云构建展示专业版构建横幅[代码]A[代码] 优化了多端的部分交互[代码]A[代码] 新增 工具 skyline 内核更新[代码]A[代码] 新增 多端支持查看 apk 文件签名与公钥[代码]A[代码] 新增 支持多端应用插件模式详情[代码]A[代码] 新增 windows 32位版本的工具使用 skyline 功能时提示不支持[代码]A[代码] 新增 工具模拟器 webview 渲染时支持 glass-easel 组件框架[代码]A[代码] iOS多端模拟器支持显示iPad[代码]A[代码] 新增 支持多端iOS配置RequireFullScreen[代码]A[代码] 新增多端第三方框架兼容,自动恢复 app.miniapp.json[代码]A[代码] 新增多端第三方框架兼容,自动补齐丢失字段[代码]A[代码] 新增 代码片段分享时支持不设置最低基础库版本[代码]U[代码] 优化 多端插件开发流程[代码]U[代码] 优化 [代码]project.miniapp.json[代码] 可视化配置 iOS 端支持开启 Skyline 扩展模块[代码]F[代码] 修复 微搭项目窗口工具栏错位问题[代码]F[代码] 修复 公众号右上角更多按钮点击失效[代码]F[代码] 修复 代码质量面板为空的问题[代码]F[代码] 修复 windows 编译出现 wcc.exe ENOENT的问题[代码]F[代码] 修复多端 ios 水印移除后的黑屏问题[代码]F[代码] 修复 偶现新建项目后出现 [代码]a.getState is not a function[代码] 的错误[代码]F[代码] 修复 部分情况下,代码包大小超出之后,上传报错面板没有展示文件列表的 bug[代码]F[代码] 修复 [代码]wx.switchTab[代码] 到当前页面回调与客户端不一致的问题[代码]F[代码] 修复 [代码]wx.chooseAddress[代码] 无法取消的问题[代码]F[代码] 修复 自定义机型浮窗样式[代码]F[代码] 修复 2.11.0 基础库模拟器白屏[代码]F[代码] 修复 第二次编译的时候报错的问题[代码]F[代码] 修复 路由事件未完成前调用 [代码]wx.reLaunch[代码] api 表现与客户端不一致的问题[代码]F[代码] 修复 redirectTo 不应该有动画效果[代码]F[代码] 修复 APIMock 配置在下次启动时丢失的问题[代码]F[代码] 修复 自定义编译条件搜索过滤失效的问题[代码]F[代码] 修复 多端插件插件解压错误[代码]F[代码] 修复 工具中 skyline 渲染模式下某些情况会 crash 的 bug[代码]F[代码] 修复 首次 cmd+Q 弹窗报错的问题[代码]F[代码] 修复 信任项目的弹窗被挡住的问题[代码]F[代码] 修复 当调试器在模拟器下方/右方时,终止模拟器时调试器区域没有提示的问题[代码]F[代码] 修复 启动时会连续编译两次的问题[代码]F[代码] 修复 项目启动时调试器会被刷新一次的问题[代码]F[代码] 修复 某些情况下会一直编译的问题[代码]F[代码] 修复 工具 1.06.2310182 版本引入的 WXML 编译报错问题[代码]F[代码] 修复 工具 onPageNotFound 事件监听不触发的 bug[代码]F[代码] 修复 worker 脚本执行同个 API 多次回调只有一个的问题[代码]U[代码] 更新 工具模拟器 wxml 编译模块的版本[代码]F[代码] 修复 skyline 渲染模式下,wxml 调试面板可能出现空白的 bug[代码]F[代码] 修复 [代码]picker[代码] 的 level 属性为非 [代码]sub-district[代码] 时与客户端不一致的问题 如果使用过程中有相关问题和建议反馈,可以加入下面的反馈群 (左)windows vip 反馈群 (右)mac vip 反馈群 [图片][图片]
2024-02-18 - 运用小程序Skyline技术构建无缝用户体验 —— 同程旅行酒店最佳实践分享
[图片] 动效衔接设计与小程序渲染框架 1、什么是动效衔接设计? 随着互联网技术和设计理念的不断发展,动效设计成为现代 UI 设计中不可或缺的一部分。其中,动效衔接是非常重要的一环。动效衔接设计是指通过巧妙的动效设计,将不同的 UI 元素在动画过程中自然、流畅地衔接起来,从而增强用户的交互体验和视觉感受。在实际应用中,动效衔接设计主要应用于界面转场、信息提示、状态变化等方面,通过顺畅的衔接,降低用户因白屏等待而产生的焦虑。 2、动效衔接设计的意义 (1)极大提高用户体验,让用户感受到界面的流畅和自然,从而增加用户对产品的好感度。 (2)降低用户的操作认知成本,帮助用户更好地理解执行操作后所带来的结果,从而减少用户对产品的困惑。 (3)强化视觉层,让用户更好地区分不同的信息和元素,从而增强视觉层次感。 (4)增加界面的美感度,让界面更加生动有趣,从而提升整体的美感和设计价值。 (5)提升品牌的认知度,让产品更加具有特色和独特性,从而提高品牌的认知度和市场竞争力。 3、什么是小程序渲染框架Skyline? 为了进一步优化小程序性能,小程序在原 webview 渲染引擎之外最新推出 小程序渲染框架Skyline,其使用更精简高效的渲染管线,并拥有诸多增强特性,让它拥有更接近原生渲染的性能体验。新的增强特性有 worklet 动画系统、手势系统、自定义路由、共享元素动画,而且许多常用的组件如 scroll-view、swiper 都有了更高性能的实现。 [图片] [图片] 实践理念和场景拆解 1、动效衔接设计的核心原则 简单而清晰的动效设计,需要遵守以下几个原则: (1)一致性:动效衔接应该与整体设计风格保持一致。包括颜色、字体、动画速度等方面。 (2)可预测性:用户能够感知动画元素的变化关联性,从而增加用户对产品的掌控感和对界面的理解。 (3)反馈性:动效衔接与用户操作相响应,从而帮助用户理解他们的操作所带来的结果。 (4)视觉层次:动效衔接遵循视觉层次原则,让用户区分页面中的上下关系以及三维物理世界的关系层次,给用户清晰的层级区分感知,提高用户体验。 (5)自然性:动效衔接符合物理规律,例如重力、加速度等,从而增强动画的真实感和用户体验。 2、理念孵化与使用场景拆解 以提炼的动态感受为出发点,理性的层面给予了我们大致的产品体验感知,为我们动效理念的建成提供了框架。对此我们将继续从感性层面出发,找寻可传递真实感受的运动现象并加以组合提炼。 本次 同程旅行小程序 以酒店预订链路中核心的相册页面进行应用场景,在用户操作图片的过程中运用小程序渲染框架承接。 (1)将整个过程进行了拆解,首先为了退出时行动的路径更加清晰,做了一个响应设计,当界面向右滑动退出的过程中,相册图片进行缩小,在缩放过程中,会根据位移距离控制缩放的比例,同时蒙层的透明度以及毛玻璃效果也跟随手指移动变化,和相册列表页在视觉上呈现 XY 轴以及上下层级的空间关系,在缩小到一定的比例时,触发震动效果,松手退出到相册列表页。 (2)在交互结束时,图片退回相册列表页原始位置,在返回路径的过程中,根据交互结束时的定位点,来判断运动的方向和距离,计算运动加速度,以及模拟运动加速度带来的惯性回弹的方向和角度变化,加强与模拟真实物理世界的运动定律,和视觉上的动态感知。 结合自然世界的运动规律来看,把页面进入的元素比作是行驶的汽车,用户当作是正在斑马线上行驶的人,将马路作为页面空间。若汽车采用的是缓入运动(加速)的话,马路上的行人则看到的是一辆不断加速向他行驶过来的车辆。因为担心车辆高速的逼近导致刹车不及时的情况,行人便会本能的作出躲闪的反应。其实页面也是一个道理,进入的元素使用加速运动出现过冲的运动感知会让用户体验时产生不适。 [图片] 小程序渲染框架技术开发实践过程剖析 1、开发自定义路由实现此交互,需要 自定义路由动画,因为小程序渲染框架的页面支持自定义跳转动画。当使用自定义路由后,页面跳转时指定路由类型,就会触发自定义路由动画,而不再是默认的从右往左的动画,此处的实现可以使得页面跳转时,没有默认的路由动画,页面将直接以透明的方式渲染在屏幕上,由开发者自己控制页面内元素的动画展示方式,具体实现如下: (1)在图片查看页面配置文件 index.json 中声明 { "backgroundColor": "#00000000", "backgroundColorContent": "#00000000", // 设置客户端页面背景为透明 "navigationStyle": "custom", "renderer": "skyline", // skyline渲染引擎 "disableScroll": true, "usingComponents": { } } (2)在 wxss 中,设置图片查看页面的 page 节点为透明背景 page { background: transparent; } (3)在 js 中,使用 wx.router.addRouteBuilder(routeType, fn) 来声明自定义路由动画 wx.router.addRouteBuilder('myCustomRoute', function (params) { const handlePrimaryAnimation = () => { 'worklet'; return { // 可在此处,根据 params.primaryAnimation.value 的值,来设置页面的动画效果 backgroundColor: `rgba(0,0,0,${ params.primaryAnimation.value })` }; }; return { opaque: false, handlePrimaryAnimation, barrierColor: '', barrierDismissible: false, transitionDuration: 320, reverseTransitionDuration: 250, canTransitionTo: true, canTransitionFrom: false }; }) (4)在图片列表页面中,使用 x.navigateTo 来跳转页面,并且设置 routeType 为 myCustomRoute wx.navigateTo({ url: '/pages/skyline-image-viewer/index?index=0', routeType: 'myCustomRoute' }) [图片] 需配置页面的渲染引擎为 Skyline,并且在跳转时使用 routeType 就可以实现让页面在跳转时没有默认的路由动画。 2、共享元素穿越在连续的页面跳转时,页面间 key 相同的 share-element 节点将产生飞跃特效,还可自定义插值方式和动画曲线,通常作用于图片。为保证动画效果,前后页面的 share-element 子节点结构应该尽量保持一致 <share-element key="share-key"> <view> you code here </view> <!-- 需要注意,share-element 内要求只有一个根节点 --> </share-element> [图片] 这时,界面的表现像上面视频一样,是一个连续的动画状态,这完全是由 share-element 来控制的,share-element 的动画原理如下图所示: [图片] 3、接入手势组件,实现图片放大、缩小、平移在图片查看页面有如下结构: <scale-gesture-handle worklet:ongesture="onScaleGestureHandle"> <share-element key="{{ shareKey }}" class="current-item"> <image src="{{ src }}"/> </share-element> </scale-gesture-handle> 这里,我们使用小程序渲染框架提供的 手势组件 <scale-gesture-handle>,来实现图片的放大、缩小、平移等手势交互。 注意,所有声明为 worklet 指令的方法它们运行在UI线程,不要在方法中修改普通的变量,因为跨线程的关系,只能修改使用 wx.worklet.shared 声明的变量。 const GestureState = { POSSIBLE: 0, // 此时手势未识别 BEGIN: 1, // 手势已识别 ACTIVE: 2, // 连续手势活跃状态 END: 3, // 手势终止 CANCELLED: 4 // 手势取消 }; Component({ attached() { this.shareX = wx.worklet.shared(0); this.shareY = wx.worklet.shared(0); this.sharScale = wx.worklet.shared(1); // 声明共享变量,并且给需要变化的dom,绑定动画 this.applyAnimatedStyle('.current-item', () => { 'worklet'; return { transform: `translate3d(${this.shareX.value}px, ${this.shareY.value}px, 0) scale(${this.sharScale.value})` } }); // 页面所需的数据,需要在 attached 事件里初始化完毕,使其可以参与首帧渲染 this.setData({ src: '...', shareKey: '...' }); }, methods: { // 当手势组件识别到手势时,触发此回调 onScaleGestureHandle(e) { 'worklet'; const { state } = e; // 在worklet函数里,不要使用 const {} = this 对this解构 const shareX = this.shareX; const shareY = this.shareY; const sharScale = this.sharScale; if (state === GestureState.BEGIN) { // 手势已经识别,此时,可以获取到手势的初始值 } else if (state === GestureState.ACTIVE) { // 手势活跃状态,此时,可以获取到手势的变化值,如平移的距离、缩放的比例等 // 将当前变化的值,设置到 `shared` 变量,就可以改变元素的样式,类似于vue3的数据驱动 shareX.value += e.focalDeltaX; shareY.value += e.focalDeltaY; sharScale.value = e.scale; } else if (state === GestureState.END || state === GestureState.CANCELLED) { // 手势终止或取消,此时,可以获取到手势的最终值 } } } }) [图片] 4、手势协商(解决手势冲突) 上面的 demo 简单演示如何使用手势组件来做图片交互,但是在图片查看页面中,我们还有其他的手势交互,如图片的左右滑动切换等,一般我们会使用 <swiper> 组件来实现,但是 <swiper>组件的内部实现和 <scale-gesture-handle> 组件,都会监听手势事件,手势组件的事件不支持冒泡的,就会导致下面结构横时: <scale-gesture-handle worklet:ongesture="onScaleGestureHandle"> <swiper> <swiper-item wx:for="{{ imgs }}"> <share-element key="{{ item.shareKey }}" class="current-item"> <image src="{{ item.src }}"/> </share-element> </swiper-item> </swiper> </scale-gesture-handle> 使用手势横向滑动时,会优先触发 swiper 的横向切换事件,而无法触发 <scale-gesture-handle> 的手势事件了,这在图片放大时的图片横向移动产生了冲突。此时就需要使用手势协商来解决手势冲突。 什么是手势协商? 手势协商指的是:当页面同时有多个手势交互时,需通过一定的约定来决定哪些手势事件应该被执行,哪些需要被忽略。 小程序渲染框架解决手势冲突的方式,主要是通过手势组件的 tag、simultaneous-handlers、native-view 和 should-response-on-move 来实现 tag:手势组件的标识,用于区分不同的手势组件simultaneous-handlers:手势组件的协商者,表示需要同时触发事件的手势组件的标识should-response-on-move:参与手势时间的派发过程,返回 false时,表示该手势时间不会继续派发native-view:用当前手势组件来代理原生组件内部的手势事件,如<swiper>组件内部的手势事件<swiper> 的内部也是使用了 <horizontal-drag-gesture-handler>手势组件,但是我们不能直接在<swiper>上设置tag来使其参与手势协商,需要用相同的手势组件通过native-view=swiper将其内部的事件代理出来,使其可以参与协商<!-- <scale-gesture-handle> 缩放手势 --> <!-- <horizontal-drag-gesture-handler> 横向拖动手势 --> <!-- 通过 simultaneous-handlers=tag 来声明多个手势应该同时触发 --> <scale-gesture-handle tag="scale" simultaneous-handlers="{{['swiper']}}" worklet:ongesture="onScaleGestureHandle"> <!-- 此处使用 native-view=swiper 代理内部的手势组件 --> <!-- 通过 should-response-on-move=fn 来参与`事件派发`过程,决定手势的事件是否应该派发 --> <horizontal-drag-gesture-handler tag="swiper" native-view="swiper" simultaneous-handlers="{{['scale']}}" worklet:should-response-on-move="shouldResponseOnMove"> <swiper> <swiper-item wx:for="{{ imgs }}"> <share-element key="{{ item.shareKey }}" class="current-item"> <image src="{{ item.src }}"/> </share-element> </swiper-item> </swiper> </horizontal-drag-gesture-handler> </scale-gesture-handle> const GuestureMode = { INIT: 0, SCALE: 1, SWIPE: 2, MOVE: 3 // ... }; Component({ attached() { this.GuestureModeShared = wx.worklet.shared(GuestureMode.INIT); this.shareX = wx.worklet.shared(0); this.shareY = wx.worklet.shared(0); this.shareScale = wx.worklet.shared(1); // 声明共享变量,并且给需要变化的dom,绑定动画 this.applyAnimatedStyle('.current-item', () => { 'worklet'; return { transform: `translate3d(${this.shareX.value}px, ${this.shareY.value}px, 0) scale(${this.shareScale.value})` } }); // ... }, methods: { onScaleGestureHandle(e) { 'worklet'; const { state } = e; if (state === GestureState.BEGIN) { this.GuestureModeShared.value = GuestureMode.INIT; } else if (state === GestureState.ACTIVE) { if(this.GuestureModeShared.value === GuestureMode.INIT) { this.gestureBefore(e); // 手势类型未知时,判断手势类型 } else { this.gestureHandle(e); // 手势类型已知时,处理手势事件 } } else if (state === GestureState.END || state === GestureState.CANCELLED) { this.GuestureModeShared.value = GuestureMode.INIT; } }, // 判断手势类型 gestureBefore(e) { 'worklet'; const { focalDeltaX, focalDeltaY, scale } = e; if (Math.abs(focalDeltaX) > Math.abs(focalDeltaY)) { this.GuestureModeShared.value = GuestureMode.SWIPE; } else if (scale > 1) { this.GuestureModeShared.value = GuestureMode.SCALE; } else { this.GuestureModeShared.value = GuestureMode.MOVE; } }, // 处理手势事件 gestureHandle(e) { 'worklet'; if (this.GuestureModeShared.value === GuestureMode.SCALE) { this.shareScale.value = e.scale; } else if (this.GuestureModeShared.value === GuestureMode.SWIPE) { // swiper 切换模式时,这里什么都不用做 } else if (this.GuestureModeShared.value === GuestureMode.MOVE) { this.shareX.value += e.focalDeltaX; this.shareY.value += e.focalDeltaY; } }, // 用于判断手势事件是否应该派发 shouldResponseOnMove(e) { 'worklet'; return this.GuestureModeShared.value === GuestureMode.SWIPE; // 当模式为SWIPE时,才响应手势事件 } } }) [图片] 通过上面的代码,我们实现了手势协商,当用户在图片上进行滑动的操作时,总是会触发 <scale-gesture-handler> 的手势事件,通过对图片当前状态的判断来决定应该触发哪种手势,我们通过此种协商让 <horizontal-drag-gesture-handle> 手势在合适的时机触发,以此避免手势冲突。 5、使用小程序渲染框架时需要注意的一些地方作为一款新的渲染优化方式,开发者使用小程序渲染框架需要注意以下内容,以保证渲染的效果和性能。 (1)自定义路由时首帧渲染&首帧性能优化 小程序渲染框架的首帧渲染对共享元素动画非常重要,若共享元素节点的key 错过首帧设置的话,可能会丢失飞跃动画,所以在使用小程序渲染框架时,共享元素的 key 应该尽量在 attached 中或之前设置到页面,并且在首帧渲染时,应尽可能的减少 UI 层的渲染工作 如下: 1)所需要的数据应尽可能使用提前计算好,避免构建页面时等待太久影响响应速度 2)首次设置的数据应该尽可能的少,避免首次渲染时,页面上的元素过多,导致首帧渲染时间过长,导致动画卡顿(如:不要同时初始化太多的 <swiper-item>) 3)确保首帧渲染时,共享元素的 key 正确的设置,避免在首帧渲染时,由于找不到对应的共享元素,导致动画丢失,看不到飞跃动画 4)由于手势事件触发频繁,应尽量避免大量需要的计算的逻辑高频执行,容易导致机器发烫,或者导致动画卡顿 **worklet 函数的使用** worklet 函数的使用有一些限制,主要是由于它是在 UI 线程执行的,所以 worklet 函数中的 this 并非是页面的 this 实例, 里面所使用到的变量也是通过特殊的 babel 插件转换到UI线程的,需要与逻辑层共用的变量都需要用 wx.worklet.shared 将它声明成共享变量,在 UI 线程调用逻辑层的函数需要使用 wx.worklet.runOnJS (2)与 web 规范的差异 虽然小程序渲染框架尽可能的与 web 规范保持一致,但是由底层渲染引擎的限制,还是有一些差异,如: 1)display: flex 的默认朝向是 column,而不是 row,这需要开发者注意,官方后续会支持 block 布局方式 2)暂不支持 css 伪元素,如 ::after、::before,官方正在支持中 3)position 仅支持 absolute、relative,不支持 sticky,实现滚动吸附的效果需用 sticky-* 组件来配合 scroll-view 实现 ** <share-element> 在非小程序渲染框架运行环境里的表现是什么** 在非小程序渲染框架的运行环境内,<share-element> 组件会被视为一个 <view> 组件,需要做好布局的兼容 6、何时使用小程序渲染框架开发时,请确保小程序开发者工具版本是 最新版 nightly,sdk 版本在 2.30.2+,具体限制可参考 文档。 这些新特性的引入,使得小程序渲染框架在小程序开发中的优势更加明显,开发者可以更加便捷地实现各种复杂的交互效果,并且达到接近原生APP的体验。 [图片] 未来展望 1、个性化产品形态:将会根据不同的用户需求和场景,设计出更加符合用户喜好和习惯的动效衔接,进行组件化调用。 2、更加自然和真实的动效衔接:动效衔接将会更加贴近自然规律和真实物理效应,从而增强动画的真实感和用户体验。 3、更加智能化和自适应的动效衔接:动效衔接将会根据用户的操作行为和使用习惯,自适应调整动画效果,从而提高用户体验和产品效果。 4、扩大产品、设计与开发的协作效应:设计对动效的把控、产品对用户的洞察以及开发对新技术的应用,才可以发挥最大化的协作效应。 附1:本文作者 同程旅行研发工程师 同程旅行体验设计师 同程旅行产品经理 附2:代码片段 相册小程序代码片段(请使用 PC 端浏览器打开):https://developers.weixin.qq.com/s/E979jCmP7oHG 附3:UE标注 [图片] 附4:AB 实验效果 AB 实验显著win0.23% [图片]
2023-04-28 - (3)强制更新
背景 此前有开发者反馈小程序发布新版本后,新版本覆盖率比较慢,因为小程序的更新机制是异步的,部分用户不会马上应用上新版本。 小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。 假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就是热启动;冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。 小程序的异步更新发生在冷启动过程,当发现新版本后,会异步下载新版本的代码包,但不会马上应用上最新版本,需要等小程序下一次冷启动,才会应用上新版本。 解决思路为了解决这个问题,我们内部也经历了数个方案的讨论,这里简单介绍下: 1. 同步检查更新(放弃):可能是最直接的解决思路,但主要问题是会影响小程序的启动速度,当下小程序的更新迭代是非常频繁的,部分用户可能每次启动都命中更新,如果需要同步检查更新+同步下载新的版本,那将会影响这部分用户的启动体验。 2. 模块热替换(放弃):从技术上来说,这是最好的方案,小程序运行起来后,在打开新页面时,马上应用新版本里的页面,但这就会存在新旧逻辑、页面共存问题,对于开发者来说,反而更不好处理,特别是涉及到全局变量时,情况会更复杂,对于我们已有的框架来说,也是一个大挑战,不过这个也是我们之后努力的方向。 3. 定时 check 新版本(目前方案):6.6.3 及以上版本的客户端,会定时 check 最近使用过的小程序是否有发布新版本;如果有,下次打开的时候会同步更新新版本再打开。这可以保证在新版本发布 24 小时后,所有小程序都能使用最新版本。(这部分是微信客户端自身优化,开发者无需关心) 4. 异步更新 + 强制更新(目前方案):同步检查更新与模块热替换两者之间的折衷方案,即还是维持异步更新机制,在异步下载完小程序代码包后,提供重启小程序的能力,这样在遇到紧急问题时可以马上解决。 异步更新 + 强制更新方案介绍从基础库 1.9.90 开始,我们提供了 wx.getUpdateManager 接口,使用该接口,可以获知是否有新版本小程序、新版本是否下载好以及应用新版本的能力。 当小程序冷启动时,会自动向微信后台请求新版本信息,如果有新版本,会马上触发新版本的下载。开发者可以通过 wx.getUpdateManager,获知当前更新的状态。 wx.getUpdateManager 接口会返回一个 UpdateManager 实例,UpdateManager 包含了三个回调: 1. onCheckForUpdate:当小程序向后台请求完新版本信息,会通知这个版本告知检查结果 2. onUpdateReady:当新版本下载完成,会回调这个事件 3. onUpdateFailed: 当新版本下载失败,会回调这个事件 还有重启应用新版本的接口: 1. applyUpdate:当新版本下载完成(onUpdateReady),调用该方法会强制当前小程序应用上新版本并重启 具体示例: [代码]// wx.getUpdateManager 在 1.9.90 才可用,请注意兼容[代码] [代码]const updateManager = wx.getUpdateManager()[代码] [代码]updateManager.onCheckForUpdate(function[代码] [代码](res) {[代码] [代码] // 请求完新版本信息的回调[代码] [代码] console.log(res.hasUpdate)[代码] [代码]})[代码] [代码]updateManager.onUpdateReady(function[代码] [代码]() {[代码] [代码] wx.showModal({[代码] [代码] title: '更新提示',[代码] [代码] content: '新版本已经准备好,是否马上重启小程序?',[代码] [代码] success: function[代码] [代码](res) {[代码] [代码] if[代码] [代码](res.confirm) {[代码] [代码] // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启[代码] [代码] updateManager.applyUpdate()[代码] [代码] }[代码] [代码] }[代码] [代码] })[代码] [代码]})[代码] [代码]updateManager.onUpdateFailed(function[代码] [代码]() {[代码] [代码] // 新的版本下载失败[代码] [代码]})[代码] 更详细信息可以参考 UpdateManager 的详细文档 最佳实践从用户体验上来说,我们还是建议只在非常必要时才强制用户重启更新,例如出现线上紧急 BUG。通常情况下,可以选通过 wx.showModal 弹出选择框让用户选择是否重启更新(实现请参考示例代码)。 如何调试最新版本的微信开发者工具提供了强制更新的调试能力,通过编译模式 - 编辑编译模式 - 勾上「下次编译时模拟更新」即可在开发者工具上调试强制更新功能。 最新开发者工具下载链接 点我。
2022-08-08 - 获取用户信息方案介绍
背景 小程序一个比较重要的能力就是获取用户信息,也就是使用 [代码]wx.getUserInfo[代码] 接口。我们发现几乎所有的小程序都会调用这个接口。虽然我们在设计文档上有提出最好的设计是在真正要用户信息的情况下才去获取用户信息,不过很多开发者并没有按照我们的期望去做,导致用户在使用的时候有很多困扰。 归结起来有几点: 开发者在首页直接调用 [代码]wx.getUserInfo[代码] 进行授权,弹框有会使得一部分用户放弃小程序的使用。 开发者没有处理用户拒绝弹框的情况,有部分小程序强制要求用户授权头像昵称等信息才能继续使用小程序。 用户没有很好的方式重新授权,虽然在前几个版本我们增加了[代码]设置[代码]页面可以让用户选择重新授权,但是操作还是不够便捷。 开发者希望进到首页就获取到用户的[代码]unionId[代码],以便和之前已经关注了公众号的用户画像关联起来。 开发者默认将 [代码]wx.login[代码] 和 [代码]wx.getUserInfo[代码] 绑定使用,这个是由于我们一开始的设计缺陷和实例代码导致: [代码]getUserInfo[代码]必须通过[代码]wx.login[代码] 在后台生成[代码]session_key[代码] 后才能调用。 为了解决以上几点,我们更新了三个能力: 使用组件来获取用户信息,用户拒绝授权后也可以重新弹窗再次授权 若用户满足一定条件(下文有详细介绍),则可以用[代码]wx.login[代码] 获取到的code直接换到[代码]unionId[代码] [代码]wx.getUserInfo[代码] 不依赖 [代码]wx.login[代码] 就能调用得到数据。 获取用户信息组件介绍[代码][代码] 组件变化: [代码]open-type[代码] 属性增加 [代码]getUserInfo[代码] :用户点击时候会触发 [代码]bindgetuserinfo[代码] 事件。 新增事件 [代码]bindgetuserinfo[代码] :当 [代码]open-type[代码] 为 [代码]getUserInfo[代码] 时,用户点击会触发。可以从事件返回参数的[代码]detail[代码]字段中获取到和[代码]wx.getUserInfo[代码] 返回参数相同的数据。 示例: [代码]<[代码][代码]button[代码] [代码]open-type[代码][代码]=[代码][代码]"getUserInfo"[代码] [代码]bindgetuserinfo[代码][代码]=[代码][代码]"userInfoHandler"[代码][代码]> Click me </[代码][代码]button[代码][代码]>[代码]和 [代码]wx.getUserInfo[代码] 不同之处在于: API [代码]wx.getUserInfo[代码] 只会弹一次框,用户拒绝授权之后,再次调用将不会弹框 组件 [代码][代码][代码][代码] 由于是用户主动触发,不受弹框次数限制,只要用户没有授权,都会再次弹框 直接获取unionId考虑很多场景下,业务方申请userinfo授权主要为了获取unionid。我们鼓励开发者在不骚扰用户的情况下合理获得unionid,而仅在必要时才向用户弹窗申请使用昵称头像。为此,凡使用“获取用户信息组件”获取用户昵称头像的小程序,在满足以下全部条件时,将可以静默获得unionid。 在微信开放平台下存在同主体的App、公众号、小程序。 用户关注了某个相同主体公众号,或曾经在某个相同主体App、公众号上进行过微信登录授权。 getUserInfo 和 login很多开发者会把login和getUserInfo捆绑调用当成登录使用,其实login已经可以完成登录,可以建立账号体系了,getUserInfo只是获取额外的用户信息。 在login获取到code,然后发送到开发者后端,开发者后端再通过接口去微信后端换取到openid和sessionKey(并且现在会将unionid也一并返回)之后,然后把3rd_session返回给前端,就已经完成登录行为。而login行为是静默,不必授权的,不会对用户造成骚扰。 getUserInfo只是为了提供更优质的服务而存在,比如展示头像昵称,判断性别,通过unionId和其他公众号上已有的用户画像结合起来提供历史数据。所以不必在刚刚进入小程序的时候就强制要求授权。 推荐使用方法调用[代码]wx.login[代码] 获取[代码]code[代码],然后从微信后端换取到[代码]sessionKey[代码],用于解密[代码]getUserInfo[代码]返回的敏感数据。 使用[代码]wx.getSetting[代码] 获取用户的授权情况 如果用户已经授权,直接调用 API [代码]wx.getUserInfo[代码] 获取用户最新的信息 用户未授权,在界面中显示一个按钮提示用户登入,当用户点击并授权后就获取到用户的最新信息。 获取到用户数据后可以进行展示或者发送给自己的后端。 文档中的quickStart已经更新 特别注意为了给用户提供更好的小程序环境,我们约定在一段时间后(具体时间会做通知),若还出现以下情况(包括但不限于),将无法通过审核 初次打开小程序就弹框授权用户信息 未处理用户拒绝授权的情况 强制要求用户授权 已经上线的小程序不会受到影响。 FAQ Q: 除了 UserInfo 呢,比如说位置信息 --- ’风の诺言 . A: 其他授权信息不像用户信息那么高频繁,也基本是在使用时候才申请授权,所以没有同 UserInfo 一起给出。我们会先看看 UserInfo 的使用情况再结合具体场景我们会给出相应的方案 Q: 后台要维护用户信息 --- Azleal 我们的小程序业务是功能都需要授权才能使用的(也就是必须拿到unionid获取用户信息) --- elemeNT 我在小程序与服务号的数据需要互通,通过unionId来确定用户的唯一性,如果在用户进入小程序后不强制他授权,单凭一个openid来存储他的用户数据,在用户下次从服务号进入时。不就会产生重复数据吗?就没做到数据互通了 --- ﺭ并向你吐了趴口水ﺭ五年. 另外看到官方提到 要强制推行,我想说我们目前所有用户是通过unionid注册的。那么这些用户就不得不使用 openid重新登录 、注册一遍。更重要的是,之前他们的相关数据都会对应不上(因为你们也不允许强制用户登录授权) --- 羊毛 现在这种方案,不能满足我们的需求,我们的小程序,必须一进入就要获取他的信息,然后加载他的数据; --- 韩文 A: 调用`wx.login`已经可以获取到用户的登录态,已经可以做用户账号的管理。 UserInfo 中带的 UnionId 是额外的信息,没有它完全可以完成登录 对于需要和开发平台绑定的业务进行数据互通的情况,一个新用户进来没有互通数据的情况下也是可以体验到所有业务,那么对于没有授权unionId的用户,可以将其当成是新用户,当真正授权unionId之后再做绑定完全是可以的 Q: 我需要确保用户的唯一性,这样就必须取unionID,否则用户删除了小程序,或者换了设备, 下次再进来这个小程序,该用什么来区分是上次来过的用户呢?? --- WEI+ A: 如果你本身没有其他公众号、App、小程序,那么也就没必要拿到unionid,因为unionid是打通你在开放平台下所有应用的标识 如果只有一个小程序,用 openid 足以, openid 是一个用户对于一个小程序的标识,永远不变 Q: wx.getUserInfo 是网络请求,如果使用了 open-type = "getUserInfo",是否每次点击都会调接口? --- SouthernBox A: 是的,open-type="getUserInfo" 的作用以及内部实现基本和 wx.getUserInfo 一样 区别是一个开发者主动(拒绝一次不再弹窗),一个是用户主动(拒绝任意次都可以重新弹窗) Q: 比如有一个创建按钮,用户点击一次授权了,我已经获取到用户信息,再次点击就没必要再调用 getUserInfo 去网络请求了。 --- SouthernBox A: 可以参考文中 quickStart 的做法,如果已经授权了,那就可以把按钮隐藏,之后的授权直接用API wx.getUserInfo 调用(因为已经授权,所以也不会弹窗),用户也不会再点了 Q: 小程序是不是必须要用微信自带的授权才可以登录 ,能否不使用授权方式登录,用自己系统的api接口数据实现?这个会不会涉及到审核不过的问题??麻烦解答一下 谢谢了。 --- WEI+ A: 自己做登录不会涉及到审核问题。 不过不建议在没有原有账号体系的情况下让用户在小程序内注册,过重的行为会损失用户。 Q: 在小程序中有一个"我的"页面,这是属于会员页,如果用户要进入这个页面就必须授权。交互方式就是在用户未授权情况下整个页面只显示一个授权获取用户信息的button 按钮,这个需要用户自己去触发,算不算强制授权? --- ﺭ并向你吐了趴口水ﺭ五年. A: 强制授权是说如果用户如果不授权基本信息,连最基础的浏览功能都不提供(当然这个也是要分具体的业务场景,不会限制得太死板) 可以有更好的交互,参考下主流App,在未登录的时候点击【我的】页面,也不会直接要求登录,而是展示了一定的页面结构,同时给一个登录按钮(例如【携程】【京东】等),之后再在这个页面做操作的话可以弹一个登录页面或按钮提示用户登录是完全可以的。 上述所说的登录只是用户感知上的登录,从业务逻辑上用户其实在 wx.login 的时候已经完成登录了。 Q: 看了很多评论,有些人还是不知道为什么官方要这样做,我作为一个商家角度来说下。 --- Mr.J 1. 比如我们要做一些户外推广的二唯码,用户只看到了你的图片宣传单,扫描二唯码一打开就提示“需要获取你的个人信息,您是否允许”,你不要当自己是开发者当自己是一个正常人,看到这个提示我相信很多人的第一反应就是拒绝。如果第一步已经把你拒之门外,谈何营销? 2. 没有小程序之前,我们在公众号有很多用户,都绑定了unionid,有小程序之后我们考虑怎么让用户接受小程序,可以静默登录我觉得非常好,从公众号过来的用户可以直接就登录了,没有任何提示,完美的对接,这是一个很好的体验。 A: 说得很好,我们的这些改造不仅是为了开发者,同时也是为了这个生态下的用户考虑。希望开发者们也能站在用户的角度去思考怎么做一个产品。 Q: 我不明白为什么login 给多个unionid 为什么不行? unionid也不能算是个人信息吧,给多个unionid可以更方便开发者,而且很多情况下就不用调用getUserInfo了 --- candyTong 我们提个建议,能否直接开放unionid呢?这样也许会有许多小程序不需要再弹窗了。既一定程度保障了用户体验,也照顾到了我们开发者的体验。 --- 羊毛 A: 如果直接开放了unionid,就会出现这种情况:当你作为一个用户进入一个小程序,这个小程序并没要求你授权就直接把你的头像昵称显示出来(它之前把unionId对应的头像昵称都存了下来),但是这个小程序主体(open平台主体和公众平台主体并不相同)相关的任何一个应用你从来没用过,你会不会觉得很奇怪并且很不舒服,觉得自己在微信内的用户信息没有丝毫的保障? Q: 那有推荐的比较好的例子么?对于必须使用用户头像、昵称这些信息的小程序而言 --- 亚里士朱德 A: 首先,没有什么逻辑是一定要使用用户的头像、昵称才能work的。对于这个case,完全可以先用默认头像、匿名昵称先做替代,用户点击默认头像后就可以弹出授权信息,非常的水到渠成。 Q: 之前看了这个帖子一直在思考,如果是一进去需要回去用户的地理位置信息显示到地图上的呢?这样算不算是一进去就弹窗授权获取用户信息? --- 吴俊绩🤔 A: 地图的情况和获取用户信息不同,我们目前还没对地图的授权请求有所调整。当前不受上述策略的影响 Q: 对于开发者而言,小程序与公众号是同级的,只是不同的入口 但是这样的设计,公众号与小程序成了主从关系咯 --- log琥珀① A: 并无什么主从关系,只是多一个渠道让开发者可以更方便的获取到已经是该主体下用户的unionId
2017-08-07