- 【性能优化】wxs函数调用页面数据对性能的影响
背景 我们发现有个页面在加载100个节点的时候会很卡,正常来说应该不会这么卡的。所以我深入排查了一下问题的原因。 场景 有个横向滚动的列表,向右滚动到最右边会加载新的列表,而两个卡片有可能是属于一个相同的父节点,则需要判断是否为同一个节点,方便写样式。 节点代码实例 [代码]<wxs module="utils"> var some_msg = "hello world"; module.exports = { msg : some_msg, judgeSameParent: function(list, index){ if(index<1 || list.length <= index){ return false } return list[index-1].parentId === list[index].parentId } } </wxs> <view class="intro">测试 wxs 调用函数传入数组的性能</view> <view> <scroll-view scroll-x id="scrollView" style="height: 300rpx;display: flex; background-color: gray;" lower-threshold="300rpx" bindscrolltolower="handleScrollToLower" > <view style="display: flex; flex-direction: row;"> <view wx:for="{{list}}" style="width: 200rpx;height: 200rpx;margin-top: 50rpx; flex-shrink: 0;background-color: green;margin-right: 10rpx;" wx:for-index="idx" wx:for-item="item" wx:key="idx" > <view wx:if="{{utils.judgeSameParent(list, idx)}}" style="width: 20rpx; height: 20rpx; background-color: greenyellow;"></view> <view wx:if="{{utils.judgeSameParent(list, idx)}}" style="width: 20rpx; height: 20rpx; background-color: greenyellow;"></view> </view> </view> </scroll-view> </view> [代码] 通过performance面板查看到页面运行性能会比较差,有一个运行时花了169ms [图片] 深入查看究竟是哪个函数耗时比较多,发现都是$gdc 函数比较耗时 [图片] 深入排查$gdc 函数究竟是什么 之前在做单元测试渲染的时候,发现可以通过小程序原生编译器编译wxml页面,于是我接入了单元测试,劫持了wxml的编译结果,找到了$gdc函数。 [图片] 经过分析,发现$gdc函数是一个自定义深复制函数。 所以如果我的数组越大,越复杂,就越耗时,渲染层就会卡顿,性能不好。 思考深复制的原因 wxs函数运行的时候,能拿到渲染数据,如果是值引用的话,有可能在wxs函数运行的时候修改了渲染层的渲染数据,比如说: [代码]wxs.func = function(list){ // 删除原有数组的值 list.splice(0, 1) } [代码] 如果出现这样的情况,那渲染层的渲染数据和逻辑层的渲染数据就对应不上了,所以不能这样是值引用,必须要深复制。 解决方案 所以在没有必要的情况下,往wxs函数里面尽量不要传对象,尽量传基本类型的数据。 当前这个场景的解决方案也可以将wxs层的处理放到js层,预处理数据。 代码片段:https://developers.weixin.qq.com/s/hhHT88mB74yi 备注: 代码片段的编译和页面编译不一样,函数名会不一样,$gdc 对应的函数名为K 小程序项目:https://github.com/aa875982361/test-wxs-func-performance
2022-03-31 - (7)小程序音频能力介绍
小程序支持播放和录制音频。小程序播放音频的方式有两种:内部音频和背景音频。 1.内部音频支持用户在使用小程序过程中播放音效; 2.背景音频支持在用户离开小程序后继续播放音效。 一、播放音频 (一)背景音频 播放背景音频 背景音频接口适用于音乐类小程序,如“音乐站”、“QQ 音乐小电台”。通过 wx.getBackgroundAudioManager() 接口可以获取全局唯一的背景音频管理器,所有关于背景音频的操作都由它来实现。 微信内只有一个背景音频,一个小程序开始播放背景音频之后,就持有背景音频播放器,只要当前小程序持有背景音频播放器,即使这个小程序进入后台(即用户离开小程序),也可以继续使用背景音频接口,且当前小程序不会被微信主动回收;一旦背景音频播放器被抢占(可能是其他小程序、微信内其他音乐、其他 App 的音乐),则小程序不再持有背景音频播放器。 [图片] [图片] (音乐站小程序) 在系统播放面板显示和控制 通过设置标题、专辑名、歌手名、封面图等属性,小程序音频接口支持在系统音乐播放面板显示出来。通过响应系统面板的点击事件([代码]onPrev[代码],[代码]onNext[代码]),可以实现列表播放。 [图片] (系统播放面板控制效果) (二)内部音频播放内部音频内部音频适用于所有小程序,尤其是游戏类目的小程序,如“跳一跳”。通过 wx.createInnerAudioContext() 接口可以创建一个音频实例。 [图片] 每个小程序可以同时持有和播放多个内部音频,但一旦小程序进入后台(onHide),所有内部音频都会被暂停,且在用户回到前台(即打开小程序)之前无法再被播放。 静音下也能播放在 iOS 系统中,内部音频默认遵循静音键设置。如果希望在静音时也能播放,可以设置 [代码]obeyMuteSwitch[代码] 为 [代码]false[代码]。 [图片] 安卓系统没有统一的静音开关,暂不支持此特性。 处理音频中断事件以游戏为例,在游戏中,经常有播放使用内部音频来播放游戏背景音乐的场景。音频中断事件指的是在游戏期间,音频被系统打断时触发的事件。音频中断事件分为中断开始和中断结束事件,分别使用 wx.onAudioInterruptionBegin() 和 wx.onAudioInterruptionEnd() 来监听。 以下事件会触发音频中断开始事件:接到电话、闹钟响起、系统提醒、收到微信好友的语音/视频通话请求。被中断之后,小游戏内所有音频会被暂停,并在中断结束之前都不能再播放成功。 中断结束之后,被暂停的音频不会自动继续播放,游戏可监听音频中断结束事件,并在收到中断结束事件之后调用背景音乐继续播放。 [图片] 如果游戏的逻辑强依赖音乐的播放(如音乐类游戏),需要在音频开始中断的时候暂停游戏 [图片] [图片] (跳一跳小游戏) 二、录制音频 通过 wx.getRecorderManager 接口,可以获取全局唯一的录音管理器。 [图片] 实现边录边传 默认情况下,录音结束后会生成一个本地文件,并通过回调返回本地文件的地址。对于实时性要求比较高的小程序(如“面对面翻译”),可以通过设置 [代码]frameSize[代码] 参数来设置一个帧的大小,这样每录制指定帧大小的内容后,会通过 [代码]onFrameRecorded[代码] 回调返回本次分片的数据。 [图片] 注意事项:不建议使用的历史接口上述接口可以满足所有音频相关的需求。除了上述接口,小程序内还有若干跟音频相关的接口(如 [代码]wx.startRecord[代码]、[代码]wx.playVoice[代码]、[代码]wx.playBackgroundAudio[代码] 等)。这些接口由于早期设计存在一些缺陷,我们不建议继续使用。
2018-08-17 - 自定义组件中无法使用地图API
- 当前 Bug 的表现(可附上截图) [图片] - 预期表现 在地图视野发生改变时,利用MapContext.getCenterLocation()获取当前地图中心的经纬度 再移动标注点 MapContext.translateMarker(Object object)至中心点坐标 但是这两个方法都不起效果 - 复现路径 - 提供一个最简复现 Demo
2018-12-05 - 微信小程序自动化框架minium实践
一、背景需求精选小程序发生了一次线上问题,测试阶段的小程序开发码测试ok,但是小程序正式码由于打包问题,"我的订单"页面文件打包失败,导致线上用户访问我的页面白屏。 当前并不能避免该打包问题,为了规避异常版本发布至线上,需要在预发、体验码发布、正式码发布等各阶段进行主流程回归。手动回归测试非常耗时,在发布前的各阶段,测试人员须重复执行大量测试用例,以确保本次上线功能OK且对其他功能无影响。 一遍又一遍执行相同的测试用例,不仅要花费更多的时间,而且还会降低整体测试效率,因此引入微信小程序自动化以解放重复人力。 二、调研1.Jest+小程序SDK优点: 小程序自动化 SDK 为开发者提供了一套通过外部脚本操控小程序的方案,从而实现小程序自动化测试的目的,小程序自动化 SDK 本身不提供测试框架。这意味着你可以将它与市面上流行的任意 Node.js 测试框架结合使用;jest 是facebook推出的一款测试框架,集成了 Mocha,chai,jsdom,覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架;缺点: 语言仅支持JavaScript 编写;使用中遇到问题,网上相关资料比较少;2.minium框架优点: 微信小程序官方推出的小程序自动化框架,是为小程序专门开发的自动化框架, 提供了 Python 和 JavaScript 版本。支持一套脚本,iOS & Android & 模拟器,三端运行提供丰富的页面跳转方式,看不到也能去得到可以获取和设置小程序页面数据,让测试不止点点点支持往 AppSerive 注入代码片段可以使用 minium 来进行函数的 mock, 可以直接跳转到小程序某个页面并设置页面数据, 做针对性的全面测试缺点: 暂不支持H5页面的调试;暂不支持插件内wx接口调用;3.选型精选小程序主要是原生页面,minium和Jest均能满足需求。minium支持Python 和 JavaScript 版本,而且有专门的团队定期维护,遇到问题可以在微信开发者社区进行提问,因此选择了minium。 三、minium介绍minium提供一个基于unittest封装好的测试框架,利用这个简单的框架对小程序测试可以起到事半功倍的效果。 测试基类Minitest会根据测试配置进行测试,minitest向上继承了unittest.TestCase,并做了以下改动: 加载读取测试配置在合适的时机初始化minium.Minium、minium.App和minium.Native根据配置打开IDE,拉起小程序项目和或自动打开真机调试拦截assert调用,记录检验结果记录运行时数据和截图,用于测试报告生成使用MiniTest可以大大降低小程序测试成本。 Properties: 名称类型默认值说明appminium.AppNoneApp实例,可直接调用minium.App中的方法miniminium.MiniumNoneMinium实例,可直接调用minium.Minium中的方法nativeminium.NativeNoneNative实例,可直接调用minium.Native中的方法 代码示例: #!/usr/bin/env python3 import minium class FirstTest(minium.MiniTest): def test_get_system_info(self): sys_info = self.mini.get_system_info() self.assertIn("SDKVersion", sys_info) 四、环境搭建安装minium-doc,这个是小程序安装和使用的文档介绍,或者不用自己本地安装直接访问官方文档安装python 3.8及以上安装微信开发者工具(我本机使用的版本是1.05.2103200),并打开安全模式: 设置 -> 安全设置 -> 服务端口: 打开在工具栏菜单中点击设置,选择项目设置,切换到“本地设置”,将调试基础库选择大于2.7.3的库; [图片] 下载minium安装包并安装,地址参考官网安装命令:pip3 install minium-latest.zip 或者python3 setup.py install 安装完成后,可执行以下命令查看版本:minitest -v 开启微信工具安全设置中的 CLI/HTTP (提供了命令行和HTTP两种调用方式)调用功能。在开发者工具的设置 -> 安全设置中开启服务端口。 [图片] 开启微信工具安全设置中的 CLI/HTTP (提供了命令行和HTTP两种调用方式)调用功能。在开发者工具的设置 -> 安全设置中开启服务端口。开启被测试项目的自动化端口号"path/to/cli" auto --project "path/to/project" --auto-port 9420 默认的命令行工具所在位置: macOS: <安装路径>/Contents/MacOS/cli Windows: <安装路径>/cli.bat 五、小程序脚本编写思路:使用Page Object 架构,使系统架构分层,每一个页面设计为一个Class,包含了页面需要测试的元素,测试用例只要关心测试的数据即可;1.目录结构[图片] cases/: 存放测试脚本和用例case/base/:页面公共方法case/pages/:页面对象模型outputs/:测试报告test/:测试脚本route.py:小程序页面操作的路径2.自动化脚本BasePage是页面基类,封装所有页面会用到的公用方法class BasePage: def __init__(self, mini): self.mini = mini def navigate_to_open(self, route): """以导航的方式跳转到指定页面,不允许跳转到 tabbar 页面,支持相对路径和绝对路径, 小程序中页面栈最多十层""" self.mini.app.navigate_to(route) def redirect_to_open(self, route): """关闭当前页面,重定向到应用内的某个页面,不允许跳转到 tabbar 页面""" self.mini.app.redirect_to(route) def switch_tab_open(self, route): """跳转到 tabBar 页面,会关闭其他所有非 tabBar 页面""" self.mini.app.switch_tab(route) @property def current_title(self) -> str: """获取当前页面 head title, 具体项目具体分析,以下代码仅用于演示""" return self.mini.page.get_element("XXXXXX").inner_text def current_path(self) -> str: """获取当前页面route""" return self.mini.page.path HomePage是要测试的精选首页页面from case.base.basepage import BasePage from case.base import route class HomePage(BasePage): """小程序首页公共方法""" locators = { "BASE_ELEMENT": "view", "BASE_BANNER": "首页banner元素选择器XXX" } # 首页点击官方补贴的"更多"按钮 subsidy_more_button = ("跳转页面的元素选择器XXX", "更多") """ 校验页面路径 """ def check_homepage_path(self): self.mini.assertEqual(self.current_path(), route.homepage_route) """ 校验页面的基本元素 """ def check_homepage_base_element(self): # 校验页面是否包含view元素 self.mini.assertTrue(self.mini.page.element_is_exists(HomePage.locators['BASE_ELEMENT'])) # 校验页面banner位置 self.mini.assertTrue(self.mini.page.element_is_exists(HomePage.locators['BASE_BANNER'])) """ 获取官方补贴,点击"更多"按钮跳转 """ def get_subsidy_element(self): self.mini.page.get_element(str(self.subsidy_more_button[0]), inner_text=str(self.subsidy_more_button[1])).click() BaseCase是测试用例基类,用于设置用例输出路径和清理工作,项目的测试用例都继承此类from pathlib import Path import minium class BaseCase(minium.MiniTest): """测试用例基类""" @classmethod def setUpClass(cls): super(BaseCase, cls).setUpClass() output_dir = Path(cls.CONFIG.outputs) if not output_dir.is_dir(): output_dir.mkdir() @classmethod def tearDownClass(cls): super(BaseCase, cls).tearDownClass() cls.app.go_home() def setUp(self): super(BaseCase, self).setUp() def tearDown(self): super(BaseCase, self).tearDown() 3.元素定位的方法minium 通过 WXSS 选择器来定位元素的,目前小程序仅支持以下的选择器: [图片] 参考例子: 假如要查找像上面这一个元素的话,他的选择器会像是下面这样: tageName + #id + .className view#main.page-section.page-section-gap tagName :类型选择器,标签名称,view、checkbox 等等,选择所有指定类型的最简单方式。id:ID 选择器,自定义给元素的唯一 ID,使用时前面跟着 # 号,这是选择单个元素的最有效的方式。className:类选择器,由一个点.以及类后面的类名组成,存在多个类的时候可以以点为间隔一直拼接下4.编写精选首页的测试用例被测试的有赞精选小程序首页如下图:[图片] HomePageTest # coding=utf-8 from case.base import loader from case.base.basecase import BaseCase from case.pages.homepage import HomePage """ 小程序首页测试 """ class HomePageTest(BaseCase): def __init__(self, methodName='runTest'): super(HomePageTest, self).__init__(methodName) self.homePage = HomePage(self) """ case1:测试首页的跳转路径是否正确,跳转路径要使用绝对路径,小程序默认进入就是首页,所以不用再切换进入的路径 """ def test_01_home_page_path(self): self.homePage.check_homepage_path() """ case2:页面的基本元素是否存在 """ def test_02_page_base_element(self): self.homePage.check_homepage_base_element() """ case3:检查首页的"官方补贴"模块存在 """ def test_03_live_sale(self): self.assertTexts(["官方补贴"], "view") self.assertTexts(["轻松赚回早餐钱"], "view") """ case4:从首页点击"更多"跳转到直播特卖页面,页面包含"推荐"模块 """ def test_04_open_live_sale(self): # 点击首页的"更多"按钮的元素 self.homePage.get_subsidy_element() self.page.wait_for(2) result = self.page.wait_for("页面元素选择器xxx") # 等待页面渲染完成 if result: category = self.page.data['categoryList'] self.assertEquals("美食", category[0]['title'], "接口返回值包含美食模块") self.assertEquals("美妆", category[1]['title'], "接口返回值包含美妆模块") self.page.wait_for(2) self.app.go_home() if __name__ == "__main__": loader.run(module="case.homepage_test", config="../config.json", generate_report=True) 5.编辑配置文件config.json{ "project_path": "XXXXX", "dev_tool_path": "/Applications/wechatwebdevtools.app/Contents/MacOS/cli", "debug_mode": "debug", "test_port": 9420, "platform": "ide", "app": "wx", "assert_capture": false, "request_timeout":60, "remote_connect_timeout": 300, "auto_relaunch": true } 6.minitest 命令行minium安装时执行的setup.py文件,指定了minitest命令运行的方法入口为:minium.framework.loader:main [图片] loader.py文件解释了运行的命令行的含义 [图片] -h, --help: 使用帮助。-v, --version: 查看 minium 的版本。-p PATH/--path PATH: 用例所在的文件夹,默认当前路径。-m MODULE_PATH, --module MODULE_PATH: 用例的包名或者文件名--case CASE_NAME: test_开头的用例名-s SUITE, --suite SUITE:测试计划文件-c CONFIG, --config CONFIG:配置文件名,配置项目参考配置文件-g, --generate: 生成网页测试报告--module_search_path [SYS_PATH_LIST [SYS_PATH_LIST ...]]: 添加 module 的搜索路径-a, --accounts: 查看开发者工具当前登录的多账号, 需要通过 9420 端口,以自动化模式打开开发者工具--mode RUN_MODE: 选择以parallel(并行)或者fork(复刻)的方式运行用例7.suite测试计划文件{ "pkg_list": [ { "case_list": [ "test_*" ], "pkg": "case.*_test" } ] } suite.json的pkglist字段说明要执行用例的内容和顺序,pkglist 是一个数组,每个数组元素是一个匹配规则,会根据pkg去匹配包名,找到测试类,然后再根据case_list里面的规则去查找测试类的测试用例。可以根据需要编写匹配的粒度。注意匹配规则不是正则表达式,而是通配符。 8.命令行运行脚本minitest -m case.homepage_test --case test_07_open_live_sale -c config.json -g #运行执行class文件中的指定用例test_07_open_live_sale minitest -s suite.json -c config.json -g #按照suite配置去执行用例 9.生成测试报告生成报告之后,在对应的目录下面有index.html文件,但是我们不能直接用浏览器打开这个 文件,需要把这个目录放到一个静态服务器上 测试结果存储在outputs下,运行命令python3 -m http.server 12345 -d outputs然后在浏览器上访问http://localhost:12345即可查看报告 六、遇到的问题1.需要开启被测试小程序应用的自动化测试端口9420 [图片] 开启被测试工程的自动化端口 "path/to/cli" auto --project "path/to/project" --auto-port 9420 2.打开微信开发者工具超时 [图片] 微信开发者工具:设置-代理设置,关闭ide的代理 [图片] 3.连接开发者工具后报错 原因:可能是微信开发者工具和minium的版本不一致; 我测试使用ok的匹配版本为: Minium版本:1.0.5 开发者工具版本:1.05.2102010 python版本:3.8.8 4.出现以下报错,可能是登陆的开发者工具的账号,没有被测试小程序的开发者权限; [图片] 5.运行过程中,发现调用截图的方法比较耗时,但是在config文件设置了"assert_capture": false,配置没生效,仍然会去调用截图的方法; [图片] ps:猜测是一个bug,然后给微信社区留言了,最新版本1.0.6修复了这个问题 原因:是框架的minitest.py文件调用setup和TearDown方法的时候,没有判断配置文件"assert_capture": false这个条件 [图片] 可以修改minitest.py文件,增加配置文件的判断条件,修改如下: if self.test_config.assert_capture: self.capture("setup") 6.命令行执行的时候加了-p xxx参数,运行时报引入的包不存在 [图片] 原因:命令行运行时默认是当前路径,加-p xxx, 这样会导致脚本运行的PYTHONPATH变了(不是当前目录了),这样会导致包不存在 [图片] 解决方法: 命令行运行的时候,用-m 指定运行的包路径,不用-p把-p xxx用到的路径都加入到PYTHONPATH中七、参考资料 微信官方文档简书上Rethink的相关文章介绍
2022-06-23 - 微信小程序如何一键发布??小程序CI工具 miniprogram-ci
https://herrylo.github.io/front/ 微信小程序官方维护的CI工具!!方便快捷,解放双手。微信小程序官方CI工具 miniprogram-ci文档 最近公司同事在接入弄微信小程序的CI工具,实现一键发布,我也跟着学习了一波,哈哈哈!!了解到使用方法之后,赶紧把自己的小程序也加上了这个功能,也算是自己玩玩。 小程序CI接入很方便,依赖node环境,安装好CI包之后就可以使用了。先看看下面这段上传示例代码: [代码]// 可以参考我的个人项目代码:https://github.com/HerryLo/wxSapp/blob/master/config/upload.wx.js const ci = require('miniprogram-ci') ;(async () => { const project = new ci.Project({ appid: 'wxsomeappid', type: 'miniProgram', projectPath: 'the/project/path', privateKeyPath: 'the/path/to/privatekey', ignores: ['node_modules/**/*'], }) const uploadResult = await ci.upload({ project, version: '1.1.1', desc: 'hello', setting: { es6: true, }, onProgressUpdate: console.log, }) console.log(uploadResult) })() [代码] 依赖微信小程序官方维护的[代码]miniprogram-ci[代码]包,安装了node后,install就可以使用了。 初始化[代码]ci.Project[代码]时,需要提供[代码]appid[代码]、项目的类型[代码]type[代码]、项目路径[代码]projectPath[代码]、私钥[代码]privateKeyPath[代码]、 排除的规则[代码]ignores[代码],6个参数; 其中私钥[代码]privateKeyPath[代码],是在 [代码]微信小程序后台-开发-开发设置-小程序代码上传[代码]获取私钥。 至于[代码]ci.upload[代码]字面理解就可以,上传小程序代码,当然也需要配置参数。参数不在这里过多介绍,要看参数,直接看文档吧!!微信小程序官方CI工具 文档 miniprogram-ci功能 上面只是展示上传功能,其实它还有其他功能,下面是[代码]miniprogram-ci[代码] 目前可以提供功能: [代码]1. 上传代码,对应小程序开发者工具的上传 2. 预览代码,对应小程序开发者工具的预览 3. 构建 npm,对应小程序开发者工具的: 菜单-工具-构建npm 4. 代理,配置 miniprogram-ci 的网络请求代理方式 5. 支持获取最近上传版本的 sourceMap 6. 支持 node 脚本调用方式和 命令行 调用方式 [代码] 以上就是微信小程序[代码]miniprogram-ci[代码]具备的功能,相信可以满足大多数人的需求。提醒⏰:记住别忘记配置IP白名单!!,不然上传会报错。 废话不多说,赶快来解放你的双手吧!! 附上我自己的微信小程序CI的代码,希望可以帮助到你。 代码地址:https://github.com/HerryLo/wxSapp 其实还可以有很多玩法,比如配合Jenkins、gitlab、github Action都是可以的,喜欢的可以自己研究哦!不过欢迎交流👏👏 ps: 微信公众号:Yopai,有兴趣的可以关注,每周不定期更新。不断分享,不断进步
2023-06-18 - 侵权投诉审核标准
一、昵称侵权 昵称侵权主要涉及商标权、组织名称权以及姓名权的侵权。2016年4月19日上线全平台命名唯一,确保平台名字的唯一性。如平台内有同名帐号,将无法完成新注册或认证的操作。同时,若认为别人侵犯了自己合法权利,可发起昵称侵权投诉。 1、商标权:权利人提供注册成功后的纸质商标证进行投诉,商标范围与被投诉方帐号经营内容近似。 示例: (1)商标持有人为A,投诉方为B,需要提供商标独家授权证明或委托代理书或商标转让证明; (2)投诉方提供商标“好吃”进行投诉,商标类别为“餐饮类”,被投诉方昵称为“好吃食品”,且帐号从事食品生意,基于餐饮与食品为近似行业,可作删除昵称处理; 2、组织名称权:权利人提供组织证明或营业执照投诉他人占用名称,确保在先使用,且地域一致、行业近似。 示例: (1)被投诉方命中“广州金穗技术有限公司”字号“金穗”,且注册信息显示为广州,行业一致的情况,可删除昵称; (2)投诉方证件注册时间为2016年,被投诉方帐号使用时间为2014年,无法证明在先,暂不处理。 (3)个人提供组织证件进行投诉,需要补充提供组织对个人的授权证明方可进行投诉。 3、姓名权:提供身份证进行投诉,使用他人姓名。 示例: (1)“王则”投诉用他名字作为昵称,被投诉方确实有利用他人姓名的行为,可删除名字; (2)“王则”投诉用他名字作为昵称,但是被投诉方主体证件名字也为“王则”,基于同名问题存在,不处理。 二、冒充他人 被投诉方存在故意仿冒的迹象,投诉方可提供任意平台内外资料,包括但不限于微博截图、计算机软件著作证明、网页快照、出版许可证等,证明比被投诉方早使用帐号; 且被投诉方在昵称、头像、功能介绍、发布内容等存在整体冒用、混淆,对情节较重者,核实存在整体仿冒及混淆即可注销处理,对情节较轻者,会综合评估对侵权内容删除处理; 示例: (1)投诉用冒用微博帐号“小小乐”,并提供微博在先使用时间证明及仿冒对比截图等证明,发布内容基本抄袭微博内容等多处存在仿冒,核实后可注销帐号; (2)投诉其冒用帐号“米粒”,在昵称近似或一致、头像近似或一致,并提供在先使用的截图证明,且有证据表明帐号有故意仿冒的行为,以引起相关公众混淆,核实后可分别删除帐号的昵称和头像 ,情节严重者,可注销帐号 。 三、文章侵权 1、名誉权:投诉中明确具体失实内容、对应的实际情况,并提供有效证明材料证明文章内容存在捏造失实,且构成诽谤毁誉。或文章内容存在辱骂、谩骂、恶意攻击诋毁言论,且造成毁誉的结果。经审核评估后,会对涉嫌侵权的文章进行删除处理。 示例: (1)A公司投诉B公众号所发布的文章内容,认为针对其公司存在不实诽谤的言论。其提供了加盖公章的情况说明,其中明确了具体失实之处以及实际情况,同 时提供了权威机构出具的检测报告,证明相关批次的产品并不存在文中所称的问题。文章由于存在捏造失实之处,且相关言论会对投诉方造成社会评价的降低,因此 删除文章。 (2)A公司投诉C公众号发布文章,含有针对其公司的负面内容。该文系针对该公司某真实事件的评论,相关论述有数据等依据,虽有部分带有负面性质,但未有捏造失实,也没有诋毁侮辱的言论,因此无法处理。 2、隐私权:未经许可,擅自披露他人隐私信息,如身份证、家庭住址、医疗病例等。 示例: 张某投诉某公众号对外泄露了其个人信息,提供了身份证等有效资料; 该公众号某日发文,确实公布了张三身份证清晰图片以及其家庭居住地址,同时将张三的病例记录照片也放于文中。该内容涉嫌侵犯张三的隐私权,对该文进行删除处理。 3、肖像权 未经授权,擅自商业性使用他人肖像、照片。 示例: 李某投诉某公众帐号未经许可,使用了其照片作商业宣传,提供了身份证、相关照片等材料; 公众号文章内,确实利用了李某的照片作为宣传照,进行其化妆用品的商业推广。该内容涉嫌侵犯李某肖像权等合法权益,对该文进行删除处理。 四、内容未经授权 著作权:未经授权,擅自转载或使用了你的原创文字、图片、视频、影视作品等内容,或从事其他侵犯他人著作权的行为; 投诉方可提供作品登记证书/原创文章/首发证明/摄影原图等材料进行投诉。 示例: (1)王某投诉从未授权某公众号发布,该公众号抄袭其文章,提供了原创文章首发证明等材料; 该文章与投诉方的原创文章内容一致,未经授权擅自转载他人文章,涉嫌侵犯他人著作权合法权益,故对文章进行删除。 (2)某公司投诉公众号未经授权,擅自使用其摄影作品,提供了带有拍摄信息的原图等证明材料; 经核实该公众号未经授权擅自使用他人版权作品,其行为涉嫌侵犯他人著作权,故对文章进行删除。 五、头像侵权 提供商标、权利人身份证或图形版权证明(如已盖章的作品登记证书及图形等)材料,核实后作清除头像处理。 六、功能介绍/自定义菜单/自动回复侵权 1、功能介绍侵权 提供商标、组织证明、身份证材料,证明存在侵犯权利人合法权益的行为,核实后作清除侵权内容处理。 2、自定义菜单/自动回复侵权 提供商标、组织证明、身份证、在先排版截图、作品版权材料,证明存在侵犯权利人合法权益的行为,核实后作清除侵权内容处理。 七、身份证/营业执照被盗 满足身份证或营业执照被盗用的情况,核实情况即注销并释放主体绑定关系。
2019-11-21 - (11)低功耗蓝牙能力
在无线通信领域,蓝牙是最基础又常用的能力。蓝牙组网十分简单,两设备间做个配对就可以建立起连接,其通信距离理论值为10米至100米以内,是近距离通信应用场景的首选。今天我们想跟大家分享小程序连接蓝牙的能力。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 目前蓝牙最为普遍的两种规格为蓝牙基础率/增强数据率 (BR/EDR) 和低功耗 (LE) 蓝牙。br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 蓝牙基础率/增强数据率 (BR/EDR) 是经典的蓝牙协议,常用在对数据传输带宽有一定要求的场景上,比如需要传输音频数据的蓝牙音箱等; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 低功耗 (LE) 蓝牙是从蓝牙4.0起支持的协议,特点就是耗电极低、传输速度更快,常用在对续航要求较高且只需小数据量传输的各种智能电子产品中,包括心率监测仪器、体温计、血糖仪、智能穿戴设备、胎压监测和电子烟等等,应用场景广泛,所以小程序在很早的版本(基础库 1.1.0)就优先支持了低功耗蓝牙能力。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 我们在开发基于低功耗蓝牙接口的小程序时,需要先理解一下蓝牙在连接和通信过程中的一些概念。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 工作模式 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 低功耗蓝牙协议给设备定义了若干角色,其中最主要的角色是:外围设备(Peripheral)与中心设备(Central)。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 1 外围设备是用来提供数据,通过不停地向外广播数据,让中心设备发现自己。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 2 中心设备会扫描外围设备,发现有外围设备存在后,可以与之建立连接,之后就可以使用外围设备提供的服务(Service)。一般而言,手机会担任中心设备的角色,利用外围设备提供的数据进行处理或展示等等。因此,小程序提供低功耗蓝牙接口是默认设定手机为中心设备的。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 通信协议 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 在两个蓝牙设备建立连接之后,双方的数据交互是基于一个叫做 GATT (Generic Attribute Profile) 的规范,根据该规范可以定义出一个个配置文件(Profile),描述该蓝牙设备提供的服务(Service)。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 在整个通信过程中,有三个最主要的概念:配置文件(Profile)、服务(Service)、特性(Characteristic)。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 1 Profile 并不真实存在于蓝牙设备中,它只是被蓝牙标准预先定义的一些 Service 的集合,如果蓝牙设备之间要相互兼容,它们只要支持相同的 Profile 即可。一个蓝牙设备可以支持多个 Profile。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 2 Service 可以理解为蓝牙设备提供的服务,一个设备可以提供多个服务,比如电量信息服务、系统信息服务等。每个 Service 又包含多个 Characteristic 特性值,比如电量信息服务就会有个 Characteristic 表示电量数据,同时还会有一个 16bit 或 128bit 的 UUID 唯一标识该服务,像微信硬件平台的蓝牙智能灯的主服务 UUID 为 0xFEE7。16 bit 的 UUID 实际上是 128 bit 的缩短版,接收方收到后会补上蓝牙的 UUID 基数,目的是为了提高传输效率。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 3 Characteristic 是在 GATT 规范中最小的逻辑数据单元,由一个 value 和多个描述特性的 Desciptor 组成。实际上,在与蓝牙设备打交道,主要就是通过读写 Characteristic 的 value 完成。同样的,Characteristic 也是通过一个 16bit 或 128bit 的 UUID 唯一标识。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 总结一下,如下图所示,我们可以简单地理解为:每个蓝牙设备可能提供多个 Service,每个 Service 可能有多个 Characteristic,我们根据蓝牙设备的协议用对应的 Characteristic 进行读写即可达到与其通信的目的。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 在理解了上面的模式和概念后,接下来我们看看如何可以使用小程序提供的蓝牙接口。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 1 :扫描并发现蓝牙外围设备 扫描并发现蓝牙外设。如果蓝牙功能未开启,可监听蓝牙状态变化以便自动进入下一步,提升用户体验。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 2 :连接蓝牙外围设备 若之前连接过某个设备,可跳过扫描步骤,直接传入 deviceId 连接。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 3 :查找蓝牙外围设备的服务 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 4 : 读写指定服务的特性值 [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 基本上,小程序暴露出来的蓝牙接口都是系统级 API ,但在使用流程上对安卓和 iOS 两个平台做了统一,因此在使用这一套接口时也会出现一些因系统限制而导致的问题,在这里我们整理了一些常见的问题供开发者参考: br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 1 在安卓上,部分机型无定位权限或者是定位开关未打开时会搜不到设备。原因是蓝牙功能是可以获取到定位的,系统基于安全考量,使用蓝牙接口时必须要有定位权限,否则搜索不到; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 2 在安卓上,部分机型获取设备服务时会多出 00001800 和 00001801 UUID 的服务,这是系统行为,注意不要使用这两个服务; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 3 建立连接和关闭连接必须要成对调用。如果未能及时关闭连接释放资源,容易导致 state 133 GATT ERROR的异常; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 4 与蓝牙设备通信的 MTU(最大传输单元)系统限定为 20 字节,如果超过则会出错,这里应该根据蓝牙设备协议进行分片传输。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 更多有关小程序连接蓝牙功能的信息,可查阅[接口文档]。
2018-08-17 - 小程序同层渲染原理剖析
众所周知,小程序当中有一类特殊的内置组件——原生组件,这类组件有别于 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