- 使用开发者工具命令行上传代码提示socket hang up?
开发者工具版本:预发布版 RC Build (1.02.1910121) 系统版本:[图片] 网络:使用有线连接,网络稳定 包大小:[图片] 我们使用一个 mac mini 作为打包机进行小程序的统一构建和上传;从上个版本开发者工具开始,经常出现上传失败的情况,更新至当前最新版本更是必现失败; 查看失败日志发现,一般失败报错都发生在开始上传后2分钟之后[图片] 有时 我们在上传失败后查看小程序后台的版本管理,发现这个版本实际上已经上传成功了(大部分时候是没有上传成功的) 现在只能开发者手动上传版本了,非常影响开发效率 希望官方能够确认: 上传过程是否有超时限制(2分钟?),超时后直接返回失败?如果有,是否可通过配置关闭? 如果没有配置关闭,是否有计划对命令行上传进行优化?如果有,具体会在哪个版本发布?
2019-11-04 - 小程序一直转圈 request fail interrupted
- 当前 Bug 的表现(可附上截图) iPhonex用户 其他用户正常 就这个用户 删除小程序或者清除微信缓存都不行。 [图片] - 预期表现 能够请求接口 - 复现路径 - 提供一个最简复现 Demo
2019-06-12 - wx.onError、App.onError疑惑及如何捕获Promise异常?
1、官方文档上说 wx.onError 和 App.onError 的回调时机与参数一致(https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onError.html),是指两种方式收集到的异常信息完全一致吗?我们实践过程中发现,wx.onError 获取到的信息比 App.onError 要少。 在我们上一个版本的微信小程序中,我们是在 App.onError 中监控异常,并通过 ELK 收集、查询异常。收集到的部分异常信息截图如下: [图片] [图片] 在我们小程序的最新版本中,我们使用了针对小程序平台的 Sentry SDK(https://github.com/lizhiyao/sentry-miniapp,该 SDK 原理是使用 wx.onError、wx.onPageNotFound、wx.onMemoryWarning 监控异常信息)进行信息收集上报,基于公司私有化部署的 Sentry 服务接收、存储、展示异常信息。结果发现 Sentry 服务没有收到 wx.onError 上报的异常(1. 上线之前有做过测试,Sentry SDK 是可以正常上报代码执行异常的。 2. 可以收集到页面无法找到、内存警告异常,说明线上版本小程序中 Sentry SDK 已经成功初始化,可以进行信息上报): [图片] 但是官方的微信预警群是有推送异常信息的: [图片] [图片] [图片] 2. App.onError 收集到的信息和官方后台运维中心收集到的信息是一致的吗?我们发现 onError 捕获的信息,在小程序官方后台查不到。 比如: 通过 App.onError 在 8.12 收集到了这样一条异常信息: [图片] 在小程序官方后台是搜不到这个异常信息记录的: [图片] 3. 假设 wx.onError 和 App.onError 获取到的异常信息完全一致,且和官方后台收集记录的异常信息完全一致。如果小程序后台运维中心的预警推送频率设置为 1次/5min,那么 onError 获取到的信息和微信预警群推送的信息完全一致吗? 4. 关于 Promise 的异常,对于浏览器有 window.onunhandledrejection,对于 node 有 global.process.on('unhandledRejection', callback()),对于小程序平台,有什么推荐的方式可以获取到 Promise 的异常吗?官方后台运维中心有收集到小程序中 Promise 中的异常吗?目前实践来看,小程序的 App.onError、wx.onError 中是无法捕获 Promise 的异常的。示例代码可参考:https://github.com/lizhiyao/sentry-miniapp/blob/master/examples/weapp/app.js 。 5. 在小程序官方后台及官方预警群中,会发现偶尔会出现非线上版本的异常被收集和上报了。请问这种情况是正常的吗? 比如:截图中 小程序版本对应为 0 的就是我们未发布版本代码中出现的异常。判断的依据是 /pages/homepage-config/skilled-tag/index 是新版本新增的页面,异常上报时新版本并未发布。 [图片]
2019-08-15 - onError不能捕获unhandledrejection
我在Promise的then回调中抛出一个error,没有使用catch处理: [代码]function[代码] [代码]timeout(ms)[代码][代码]{[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve) =>[代码][代码] [代码][代码]{[代码][代码] [代码][代码]setTimeout(resolve, ms);[代码][代码] [代码][代码]});[代码][代码]}[代码] [代码]timeout(100).then(() =>[代码][代码]{[代码][代码] [代码][代码]throw[代码] [代码]new[代码] [代码]Error([代码][代码]"test"[代码][代码]);[代码][代码]});[代码] console的报错截图是这样的: [图片] 我发现onError方法不能捕获未处理的Promise错误,即unhandledrejection。 那我应该怎样怎样全局监听unhandledrejection呢?
2018-08-10 - Wxml2Canvas -- 快速生成小程序分享图通用方案
Wxml2Canvas库,可以将指定的wxml节点直接转换成canvas元素,并且保存成分享图,极大地提升了绘制分享图的效率。目前被应用于微信游戏圈、王者荣耀、刺激战场助手等小程序中。 github地址:https://github.com/wg-front/wxml2canvas 一、背景 随着小程序应用的日渐成熟,多处场景需要能够生成分享图便于用户进行二次传播,从而提升小程序的传播率以及加强品牌效应。 对于简单的分享图,比如固定大小的背景图加几行简短文字构成的分享小图,我们可以利用官方提供的canvas接口将元素直接绘制, 虽然繁琐了些,但能满足基本要求。 对于复杂的分享图,比如用户在微信游戏圈发表完话题后,需要将图文混排的富文本内容生成分享图,对于这种长度不定,内容动态变化的图片生成需求,直接利用官方的canvas接口绘制是十分困难的,包括但不限于文字换行、表情文字图片混排、文字加粗、子标题等元素都需要一一绘制。又如王者荣耀助手小程序,需要将十人对局的详细战绩绘制成分享图,包含英雄数据、装备、技能、对局结果等信息,要绘制100多张图片和大量的文字信息,如果依旧使用官方的接口一步一步绘制,对开发者来说简直就是一场噩梦。我们急需一种通用、高效的方式完成上述的工作。 在这样的背景下,wxml2cavnas诞生了,作为一种分享图绘制的通用方案,它不仅能快速的绘制简单的固定小图,还能直接将wxml元素真实地转换成canvas元素,并且适配各种机型。无论是复杂的图文混排的富文本内容,还是展现形式多样的战绩结果页,都可以利用wxml2cavnas完美地快速绘制并生成所期望的分享图片。 二、Wxml2Canvas介绍及示例 1. 介绍 Wxml2Cavnas库,是一个生成小程序分享图的通用方案,提供了两种绘制方式: 封装基础图形的绘制接口,包括矩形、圆形、线条、图片、圆角图片、纯文本等,使用时只需要声明元素类型并提供关键数据即可,不需要再关注canvas的具体绘制过程; wxml直接转换成canvas元素,使用时传入待绘制的wxml节点的class类名,并且声明绘制此节点的类型(图片、文字等),会自动读取此节点的computedStyle,利用这些数据完成元素的绘制。 2. 生成图示例 下面是两张极端复杂的分享图。 2.1 游戏圈话题 [图片] 点击查看完整长图 2.2.2 王者荣耀战绩 [图片] 点击查看完整大图 三、小程序的特性及局限 小程序提供了如下特性,可供我们便捷使用: measureText接口能直接测量出文本的宽度; SelectorQuery可以查询到节点对应的computedStyle。 利用第一条,我们在绘制超长文本时便于文本的省略或者换行,从而避免文字溢出。 利用第二条,我们可以根据class类名,直接拿到节点的样式,然后将style转换成canvas可识别的内容。 但是和html的canvas相比,小程序的canvas局限性很多。主要体现在如下几点: 不支持base64图片; 图片必须下载到本地后才能绘制到画布上; 图片域名需要在管理平台加入downFile安全域名; canvas属于原生组件,在移动端会置于最顶层; 通过SelectorQuery只能拿到节点的style,而无法获取文本节点的内容以及图片节点的链接。 针对以上问题,我们需要将base64图片转换jpg或png格式的图片,实现图片的统一下载逻辑,并且离屏绘制内容。针对第五条,好在SelectorQuery可以获取到节点的dataset属性,所以我们需要在待绘制的节点上显示地声明其类型(imgae、text等),并且显示地传入文本内容或图片链接,后文会有示例。 四、Wxml2Canvas使用方式 1. 初始化 首先在wxml中创建canvas节点,指定宽高: [代码] <canvas canvas-id="share" style="height: {{ height * zoom }}px; width: {{ width * zoom }}px;"> </canvas> [代码] 引入代码库,创建DrawImage实例,并传入如下参数: [代码] let DrawImage = require('./wxml2canvas/index.js'); let zoom = this.device.windowWidth / 375; let width = 375; let height = width * 3; let drawImage = new DrawImage({ element: 'share', // canvas节点的id, obj: this, // 在组件中使用时,需要传入当前组件的this width: width, // 宽高 height: height, background: '#161C3A', // 默认背景色 gradientBackground: { // 默认的渐变背景色,与background互斥 color: ['#17326b', '#340821'], line: [0, 0, 0, height] }, progress (percent) { // 绘制进度 }, finish (url) { // 画完后返回url }, error (res) { console.log(res); // 画失败的原因 } }); [代码] 所有的数字参数均以iphone6为基准,其中参数width和height决定了canvas画布的大小,规定值是在iphone6机型下的固定数值; zoom参数的作用是控制画布的缩放比例,如果要求画布自适应,则应传入 windowWidth / 375,windowWidth为手机屏幕的宽度。 2. 传入数据,生成图片 执行绘制操作: [代码] drawImage.draw(data, this); [代码] 执行绘制时需要传入数据data,数据的格式分为两种,下面展开介绍。 2.1 基础图形 第一种为基础的图形、图文绘制,直接使用官方提供接口,下面代码是一个基本的格式: [代码] let data = { list: [{ type: 'image', url: 'https://xxx', class: 'background_image', // delay: true, x: 0, y: 0, style: { width: width, height: width } }, { type: 'text', text: '文字', class: 'title', x: 0, y: 0, style: { fontSize: 14, lineHeight: 20, color: '#353535', fontFamily: 'PingFangSC-Regular' } }] } [代码] 如上,type声明了要元素的类型,有image、text、rect、line、circle、redius_image(圆角图)等,能满足绝大多数情况。 class类名指定了使用的样式,需要在style中写出,符合css样式规范。 delay参数用来异步绘制元素,会把此元素放在第二个循环中绘制。 x,y用来指定元素的起始坐标。 将css样式与元素分离的目的是便于管理与复用。 此种方式每个元素都相互独立,互不影响,能够满足自由度要求高的情况,可控性高。 2.2 wxml转换 第二种方式为指定wxml元素,自动获取,下面是示例: [代码] let data = { list: [{ type: 'wxml', class: '.panel .draw_canvas', limit: '.panel' x: 0, y: 0 }] } [代码] 如上,type声明为wxml时,会查找所有类名为draw_canvas的节点,并且加入到绘制队列中。 class传入的第一个类名限定了查询的范围,可以不传,第二个用来指定查找的节点,可以定义为任意不影响样式展现的通用类名。 limit属性用来限定相对位置,例如,一个文本的位置(left, top) = (50, 80), class为panel的节点的位置为(left, top) = (20, 40),则文本canvas上实际绘制的位置(x, y) = (50 - 20, 80 -40) = (30, 40)。如果不传入limit,则以实际的位置(x, y) = (50, 80)绘制。 由于小程序节点元素查询接口的局限,无法直接获取节点的文本内容和图片标签的src属性,也无法直接区分是文本还是图片,但是可以获取到dataset,所以我们需要在节点上显示地声明data-type来指明类型,再声明data-text传入文字或data-url传入图片链接。下面是个示例: [代码] <view class="panel"> <view class="panel__img draw_canvas" data-type="image" data-url="https://xxx"></view> <view class="panel__text draw_canvas" data-type="text" data-text="文字">文字</view> </view> [代码] 如上,会查询到两个节点符合条件,第一个为image图片,第二个为text文本,利用SelectorQuery查询它们的computedStyle,分别得到left、top、width、height等数据后,转换成canvas支持的格式,完成绘制。 除此之外,下面的示例功能更加丰富: [代码] <view class="panel"> <view class="panel__text draw_canvas" data-type="background-image" data-radius="1" data-shadow="" data-border="2px solid #000"></view> <view class="panel__text draw_canvas" data-type="text" data-background="#ffffff" data-padding="2 3 0 0" data-delay="1" data-left="10" data-top="10" data-maxlength="4" data-text="这是个文字">这是个文字</view> </view> [代码] 如上,第一个data-type为background-image,表示读取此节点的背景图片,因为可以通过computedStyle直接获取图片链接,所以不需要显示传入url。声明data-radius属性,表示要将此图绘成乘圆形图片。data-border属性表示要绘制图片的边框,虽然也可以通过computedStyle直接获取,但是为了避免非预期的结果,还是要声明传入,border格式应符合css标准。此外,图片的box-shadow等样式都会根据声明绘制出来。 第二个文本节点,声明了data-background,则会根据节点的位置属性给文字增加背景。 data-padding属性用来修正背景的位置和宽高。data-delay属性用来延迟绘制,可以根据值的大小,来控制元素的层级,data-left和data-top用来修正位置,支持负值。data-maxlength用来限制文本的最大长度,超长时会截取并追加’…’。 此外,data-type还有inline-text,inline-image等行内元素的绘制,其实现较为复杂,会在后文介绍。 五、Wxml2Canvas实现原理 1. 绘制流程 整个绘制流程如下: [图片] 因为小程序的限制,只能在画布上绘制本地图片,所以统一先对图片提前下载,然后再绘制,为了避免图片重复下载,内部维护一个图片列表,会对相同的图片链接去重,减少等待时间。 2. 基本图形的实现 基础图形的绘制比较简单,内部实现只是对基础能力的封装,使用者不用再关注canvas的绘制过程,只需要提供关键数据即可,下面是一个图片绘制的实现示例: [代码] function drawImage (item, style) { if(item.delay) { this.asyncList.push({item, style}); }else { if(item.y < 0) { item.y = this.height + item.y * zoom - style.height * zoom; }else { item.y = item.y * zoom; } if(item.x < 0) { item.x = this.width + item.x * zoom - style.width * zoom; }else { item.x = item.x * zoom; } ctx.drawImage(item.url, item.x, item.y, style.width * zoom, style.height * zoom); ctx.draw(true); } } [代码] 如上,x,y值坐标支持传入负值,表示从画布的底部和右侧计算位置。 3. Wxml转Canvas元素的实现 3.1 computedStyle的获取 首先需要获取wxml的样式,代码示例如下: [代码] query.selectAll(`${item.class}`).fields({ dataset: true, size: true, rect: true, computedStyle: ['width', 'height', ...] }, (res) => { self.drawWxml(res); }) [代码] 3.2 块级元素的绘制 对于声明为image、text的元素,默认为块级元素,它们的绘制都是独立进行的,不需要考虑其他的元素的影响,以wxml节点为圆形的image为例,下面是部分代码: [代码] if(sub.dataset.type === 'image') { let r = sub.width / 2; let x = sub.left + item.x * zoom; let y = sub.top + item.y * zoom; let leftFix = +sub.dataset.left || 0; let topFix = +sub.dataset.top || 0; let borderWidth = sub.borderWidth || 0; let borderColor = sub.borderColor; // 如果是圆形图片 if(sub.dataset.radius) { // 绘制圆形的border if(borderWidth) { ctx.beginPath() ctx.arc(x + r, y + r, r + borderWidth, 0, 2 * Math.PI) ctx.setStrokeStyle(borderColor) ctx.setLineWidth(borderWidth) ctx.stroke() ctx.closePath() } // 绘制圆形图片的阴影 if(sub.boxShadow !== 'none') { ctx.beginPath() ctx.arc(x + r, y + r, r + borderWidth, 0, 2 * Math.PI) ctx.setFillStyle(borderColor); setBoxShadow(sub.boxShadow); ctx.fill() ctx.closePath() } // 最后绘制圆形图片 ctx.save(); ctx.beginPath(); ctx.arc((x + r), (y + r) - limitTop, r, 0, 2 * Math.PI); ctx.clip(); ctx.drawImage(url, x + leftFix * zoom, y + topFix * zoom, sub.width, sub.height); ctx.closePath(); ctx.restore(); }else { // 常规图片 } } [代码] 如上,块级元素的绘制和基础图形的绘制差异不大,理解起来也很容易,不再多述。 3.3 令人头疼的行内元素的绘制 当wxml的data-type声明为inline-image或者inline-text时,我们认为是行内元素。行内元素的绘制是一个难点,因为元素之前存在关联,所以不得不考虑各种临界情况。下面展开细述。 3.3.1 纯文本换行 对于长度超过一行的行内元素,需要计算出合适的换行位置,下图所示的是两种临界情况: [图片] [图片] 如上图所示,第一种情况为最后一行只有一个文字,第二种情况最后一行的文字长度和宽度相同。虽然长度不同,但都可通过下面代码绘制: [代码] let lineNum = Math.ceil(measureWidth(text) / maxWidth); // 文字行数 let sinleLineLength = Math.floor(text.length / lineNume); // 向下取整,保证多于实际每行字数 let currentIndex = 0; // 记录文字的索引位置 for(let i = 0; i < lineNum; i++) { let offset = 0; // singleLineLength并不是精确的每行文字数,要校正 let endIndex = currentIndex + sinleLineLength + offset; let single = text.substring(currentIndex, endIndex); // 截取本行文字 let singleWidth = measureWidth(single); // 超长时,左移一位,直至正好 while(singleWidth > maxWidth) { offset--; endIndex = currentIndex + sinleLineLength + offset; single = text.substring(currentIndex, endIndex); singleWidth = measureWidth(single); } currentIndex = endIndex; ctx.fillText(single, item.x, item.y + i * style.lineHeight); } // 绘制剩余的 if(currentIndex < text.length) { let last = text.substring(currentIndex, text.length); ctx.fillText(last, item.x, item.y + lineNum * style.lineHeight); } [代码] 为了避免计算太多次,首先算出大致的行数,求出每行的文字数,然后移位索引下标,求出实际的每行的字数,再下移一行继续绘制,直到结束。 3.3.2 非换行的图文混排 [图片] 上图是一个包含表情图片和加粗文字的混排内容,当使用Wxml2Canvas查询元素时,会将第一行的内容分为五部分: 文本内容:这是段文字; 表情图片:发呆表情(非系统表情,image节点展现); 表情图片:发呆表情; 文本内容:这也; 加粗文本内容:是一段文字,这也是文字。 对于这种情况,执行查询computedStyle后,会返回相同的top值。我们把top值相同的元素聚合在一起,认为它们是同一行内容,事实也是如此。因为表情大小的差异以及其他影响,默认规定top值在±2的范围内都是同一行内容。然后将top值的聚合结果按照left的大小从左往右排列,再一一绘制,即可完美还原此种情况。 3.3.3 换行的图文混排 当混排内容出现了换行情况时,如下图所示: [图片] 此时的加粗内容占据了两行,当我们依旧根据top值归类时,却发现加粗文字的left值取的是第二行的left值。这就导致加粗文字和第一部分的文字的top值和left值相同,如果直接绘制,两部分会发生重叠。 为了避免这种尴尬的情况,我们可以利用加粗文字的height值与第一部分文字的height值比较,显然前者是后者的两倍,可以得知加粗部分出现了换行情况,直接将其放在同组top列表的最后位置。换行的部分根据lineHeight下移绘制,同时做记录。 最后一部分的文本内容也出现了换行情况,同样无法得到真正的起始left值,并且其top值与上一部分换行后的top值相同。此时应该将他的left值追加加粗换行部分的宽度,正好得到真正的left值,最后再绘制。 大多数的行内元素的展现形式都能以上述的逻辑完美还原。 六、总结 基于基础图形封装和wxml转换这两种绘制方式,可以满足绝大多数的场景,能够极大地减少工作量,而不需要再关注内部实现。在实际使用中,二者并非孤立存在,而更多的是一起使用。 [图片] 如上图所示,对于列表内容我们利用wxml读取绘制,对于下部的白色区域,不是wxml节点内容,我们可以使用基础图形绘制方式实现。二者的结合更加灵活高效。 目前Wxml2Canvas已经在公司内部开源,不久会放到github上,同时也在不断完善中,旨在实现更多的样式展现与提升稳定性和绘制速度。 如果有更好的建议与想法,请联系我。
2019-02-28 - 小程序架构设计(二)
接着上篇文章《小程序架构设计(一)》 前边我们说到采用Web+离线包的方式可以解决很多问题,但是遗留了一个安全问题有待解决。 经过了一番讨论,我们决定把开发者的JS逻辑代码放到单独的线程去运行,因为不在Webview线程里,所以这个环境没有Webview任何接口,自然的开发者就没法直接操作Dom,也就没法动态去更改界面,“管控”的问题得以解决。 还存在一个问题:开发者没法操作Dom,如果用户交互需要界面变化的话,开发者就没办法动态变化界面了。所以我们要找到一个办法:不直接操作Dom也能做到界面更新。 其实Facebook早有方案解决这个问题,就是上篇文章提到的React。React引入了Virtual Dom的概念(后文简称VD),业务侧只需要改变数据即可引起界面变化,相关原理后边再写篇文章来分享。 至此小程序双线程的模型就定下来了:渲染层(Webview)+逻辑层(JSCore) [图片] 其中渲染层用了Webview进行渲染,开发者的JS逻辑运行在一个独立的JSCore线程。 渲染层提供了带有数据绑定语法的WXML,逻辑层提供了setData等等API,开发者需要进行界面变化时,只需要通过setData把变化的数据传进去,小程序框架就会进行Dom Diff等流程最后把正确的结果更新在Dom树上。 [图片] 可以看到在开发者的逻辑下层,还需要有一层小程序框架的支持(数据通信、API、VD算法等等),我们把它称为基础库。 我们在两个线程各自注入了一份基础库,渲染层的基础库含有VD的处理以及底层组件系统的机制,对上层提供一些内置组件,例如video、image等等。逻辑层的基础库主要会提供给上层一些API,例如大家经常用到的wx.login、wx.getSystemInfo等等。 解决了渲染问题,我们还要看一下用户在和界面交互时的问题。 [图片] 用户在屏幕点击某个按钮,开发者的逻辑层要处理一些事情,然后再通过setData引起界面变化,整个过程需要四次通信。对于一些强交互(例如拖动视频进度条)的场景,这样的处理流程会导致用户的操作很卡。 对于这种强交互的场景,我们引入了原生组件,这样用户和原生组件的交互可以节省两次通信。 [图片] 正如上图所示,原生组件和Webview不是在同一层级进行渲染,原生组件其实是叠在Webview之上,想必大家都遇到过这个问题,video、input、map等等原生组件总是盖在其他组件之上,这就是这个设计带来的问题。 我们也很重视这个问题,经过了一段时间的努力,我们攻克了这个难题,把原生组件渲染到Webview里,从而实现同层渲染。目前video组件已经完成同层渲染的全量发布,详细可以看我们之前的公告:同层渲染公测。 为了让开发者可以更好的开发小程序,我们在后来还引入了自定义组件和插件的概念,我们后续会有相关的文章再介绍这两块的设计,希望大家关注我们社区的文章板块。 [图片] 以上就是小程序架构设计的历史。
2019-02-27 - 小程序架构设计(一)
在微信早期,我们内部就有这样的诉求,在微信打开的H5可以调用到微信原生一些能力,例如公众号文章里可以打开公众号的Profile页。所以早期微信提供了Webview到原生的通信机制,在Webview里注入JSBridge的接口,使得H5可以通过它调用到原生能力。 [图片] 我们可以通过JSBridge微信预览图片的功能: [代码]WeixinJSBridge.invoke('imagePreview', { current: https://img1.gtimg.com/1.jpg', urls: [ 'https://img1.gtimg.com/1.jpg', 'https://img1.gtimg.com/2.jpg', 'https://img1.gtimg.com/3.jpg' ] }) [代码] 早期微信官方是没有暴露这些接口的,都是腾讯内部业务在使用,很多外部开发者发现后,就依葫芦画瓢地使用了。 从另外一个角度看,JSBridge是微信和H5的通信协议,有一些能力可能要组合不同的能力才能完整调用。如果我们直接开放这套API,相当于所有开发者都要直接理解这样的接口协议,显然是很不合理的。 所以在2015年初的时候,微信就发布了JSSDK,其实就是隐藏了内部一些细节,包装了几十个API给到上层业务直接调用。 [图片] 前边的代码就变成了: [代码]wx.previewImage({ current: https://img1.gtimg.com/1.jpg', urls: [ 'https://img1.gtimg.com/1.jpg', 'https://img1.gtimg.com/2.jpg', 'https://img1.gtimg.com/3.jpg' ] }) [代码] 开发者可以用JSSDK来调用微信的能力,来完成一些以前H5做不到或者难以做到的事情。 能力上得到了更多的支持,但是微信里的H5体验却没有改善。 第一点是加载H5时的白屏。在微信里打开链接后会看到白屏,有一些H5的服务不稳定,这个白屏现象会更严重。 [图片] 第二点是在H5跳转到其他页面时,切换的效果也很不流畅,只能看到顶部绿色进度条在走。 [图片] 随着JSSDK的开放,还出现了更不好对付的问题。 微信上越来越多干坏事的人,有人做假红包,有人诱导分享,有伪造一些官方活动。他们会利用JSSDK的分享能力变相的去裂变分享到各个群或者朋友圈,由于JSSDK是根据域名来赋予api权限的,运营人员封了一个域名后,他们立马用别的域名又继续做坏,要知道注册一个新的域名的成本是很低的。 [图片] [图片] [图片] 龙哥在2016年微信公开课上提出了应用号的概念,我们要重新设计一个新的移动应用开发模式,同时我们要解决刚刚提到的一些问题。 至此,我们回顾一下目前移动应用开发的一些特点: Web开发的门槛比较低,而App开发门槛偏高而且需要考虑iOS和安卓多个平台; 刚刚说到H5会有白屏和页面切换不流畅的问题,原生App的体验就很好了; H5最大的优点是随时可以上线更新,但是App的更新就比较慢,需要审核上架,还需要用户主动安装更新。 我们更想要的一种开发模式应该是要满足一下几点: 像H5一样开发门槛低; 体验一定要好,要尽可能的接近原生App体验; 让开发者可以云端更新,而且我们平台要可以管控。 很多人可能会第一时间想到Facebook的React Native(下边简称RN),是不是RN就能解决这些问题呢? 是的,React Native貌似可以解决刚刚那些问题,我们也曾经想用RN来做。但是仔细分析了一下,我们发现了采用RN这个机制做开放平台还是存在一些问题。 RN只支持CSS的子集,作为一个开放的生态,我们还要告诉外边千千万万的开发者,哪些CSS属性能用,哪些不能用; RN本身存在一些问题,这些依赖RN的修复,同时这样就变成太过依赖客户端发版本去解决开发者那边的Bug,这样修复周期太长。 RN前阵子还搞出了一个Lisence问题,对我们来说也是存在隐患的。 [图片] 所以我们舍弃了这样的方案,我们改用了Hybrid的方式。简单点说,就是把H5所有代码打包,一次性Load到本地再打开。这样的好处是我们可以用一种近似Web的方式来开发,同时在体验上也可以做到不错的效果,并且也是可以做到云端更新的。 [图片] 现在留给我们的最后一个问题就是,平台的管控问题。 怎么理解呢?我们知道H5的界面结构是用HTML进行描述,浏览器进行一系列的解析最终绘制在界面上。 [图片] 同时浏览器提供了可以操作界面的DOM API,开发者可以用这些API进行一些界面上的变动,从而实现UI交互。 [图片] 既然我们要采用Web+离线包的方式,那我们要解决前边说过的安全问题,我们就要禁用掉很多危险的HTML标签,还要禁用掉一些API,我们要一直维护这样的白名单或者黑名单,实现成本太高了,而且未来浏览器内核一旦更新,对我们来说都是很大的安全隐患。 [图片] 这就是小程序一开始遇到的问题,在下篇文章《小程序架构设计(二)》,我们再详细展开一下小程序是如何解决以上这个问题的。
2019-02-26