关于移动端开发:移动端UI一致性解决方案

1. 背景1.1 行业现状与问题很多技术同学都晓得,挪动端往往比拟偏重业务开发,这会导致人员规模不断扩大,我的项目复杂度也会持续增长。而为了满足业务的疾速上线,很难去落实对立的设计规范,在开发过程中因为UI不足规范导致的问题一直凸显,具体体现在以下4个层面: 设计层面:因为UI不足标准化设计规范,在不同App及不同开发语言平台上设计格调不对立,用户体验不统一;设计资源与代码均不足对立管理手段,无奈实现积攒积淀,无奈适应新业务的开发需要。开发层面:组件代码实现碎片化,存在屡次开发的状况,品质难以保障;各端代码API不对立,保护拓展老本较高,变更主题、适配Dark Mode等需要难以实现。测试层面:反复走查,频繁回归,每次发版均需验证组件品质。产品层面:版本迭代效率低,版本需要吞吐量低,不具备业务的疾速拓展能力。1.2 外卖挪动端UI一致性状况近来年,美团外卖业务开始由发展期走入成熟期,这更要求对细分场景的疾速迭代。目前,外卖平台承载了餐饮、商超、闪购、跑腿、药品等多个业务品类,用户入口则笼罩了美团App外卖频道、外卖App、公众点评外卖频道等多个独立利用。因为后期偏重需要的疾速上线,设计层面不足标准化的标准束缚,UI设计格调不对立,也存在屡次开发的状况,目前的保护老本较高,在开发过程中逐步暴露出一些问题,次要体现在以下三个层面。 指标一:挪动端UI问题统计 在Ones(美团外部研发需要管理工具)中,单个版本的UI适配问题占比超过总Bug数的11.82%,亟待优化;交互适配问题在绝大多数版本中均有呈现,肯定水平上反映了其产生的普遍性。 指标二:需要承接率数据统计 用户侧UI需要吞吐率达18.3%,目前用户侧UI需要吞吐率较低,亟待解决。 指标三:需要入版状况统计 目前各版本UI同学都会提出肯定数量的视觉优化需要,但理论入版量仅为三分之一左右,未上线的起因均为RD开发工夫有余。 从久远角度来看,随着固有业务渗透率的一直饱和,将来一段时间内,美团外卖还有开辟新业务、进入新市场的需要,如国际化App、闪购App等,须要挪动端可能高效地组建新业务App。在此背景下,挪动端具备疾速调整适应的UI展示能力是重中之重。为了达到上述指标,须要PM/UI/RD独特保护一套设计规范,在产品上对立格调,在源头上做到对立设计,并在代码中对立进行实现。 1.3 UI一致性我的项目基于上述开发工作中的切实痛点,以及将来可预感的挪动端能力需要,迫切需要一套对立的UI设计规范,以此积淀设计格调,建设对立的UI设计标准。 UI一致性我的项目自2019年5月份被提出,是外卖UI设计团队与研发团队的共建我的项目,该我的项目是为了改善用户端体验一致性,晋升多技术计划间组件的通用性和复用率,升高整体视觉改版的研发老本。通过抽离成熟的业务场景,建设可提供高质量、可扩大、可对立配置的基于Android/iOS/MRN的组件代码库,使之具备反对多业务高层次的代码复用能力,进而进步UI业务中台能力,使我的项目放弃高度一致性。 为了帮忙团队晋升产研效率,外卖技术成立了袋鼠UI共建项目组,将门户建设、工具链建设以及组件建设对立治理统一规划,并将工具链的品牌确定为“积木”,此前咱们曾经写过两篇文章《积木Sketch Plugin:设计同学的贴心搭档》、《积木Sketch插件进阶开发指南》介绍过积木相干的内容,本文次要介绍UI一致性。 UI一致性是绝大部分研发团队面临的共性问题,大家对落地设计规范,进步UI中台能力,晋升产研效率具备强烈的诉求。通过UI一致性的建设,不仅能够在品牌上实现体验降级,更能够全面提高产研效率,为业务的疾速迭代提供无力反对和无效保障。对立的品牌符号、品牌特色,有助于加深产品在用户心目中的印象。对立的用户界面和交互模式,能帮忙用户加深对产品的相熟感和信任感。而一个好的设计语言能够在体验上为产品加分,也可能更好的发明一致性体验。 2. UI一致性整体计划为了帮忙更多的业务部门定制合乎一致性准则的专属设计格调,外卖技术部在实践中一直总结经验,开发了一套通用的UI一致性解决方案。该计划通过UI一致性工具链落地我的项目建设,并打造一整套的闭环UI开发流程,目前曾经获得了肯定的成绩,以下系具体计划的介绍。 2.1 计划全景外卖UI一致性套件由积木工具链、代码组件库、定制化设计云合作平台以及文档化阐明(官网)四局部组成。 积木工具链:通过建设蕴含雷同设计元素的对立物料市场,PM通过Axure插件拾取物料市场中的组件产出原型稿;UI/UE通过Sketch插件落地物料市场中的设计规范,产出符合要求的设计稿。将来,心愿通过高保真原型输入,能够给中后盾我的项目、非依赖体验我的项目提供更好的服务体验,赋予产品同学间接向技术侧输入原型稿的能力。代码组件库(Android、iOS、MRN):设计稿中的组件与RD代码仓库中组件一一对应。文档化阐明:官网详细描述了代码组件库的集成形式、组件的应用办法,升高开发上手难度,只须要了解接口和职责即可进行业务开发。定制化设计云合作平台:与美团外部的印迹团队(云合作平台)合作开发,在RD的设计稿中标注了哪些是代码组件库中已有的元素,防止反复开发,同时关联了官网中该组件的应用阐明,是代码组件库与官网的纽带。 2.2 接入指南设计师逐渐将设计语言积淀为设计规范(包含组件、色彩、字体、图片等)上传至官网供整个设计团队查阅,同时将其量化并内置于积木Sketch插件中;开发同学则将其代码化,针对Android/iOS/MRN三端进行组件库开发。设计师应用积木Sketch插件绘制设计稿,能够保障设计元素均从既定的设计标准中获取,产出合乎业务设计规范的设计稿,而代码组件库中也有对应的实现。绘制实现的设计稿上传至印迹云合作平台,交付开发同学进行设计稿还原。开发同学拿到设计稿后,就能够晓得本次需要哪些组件已内置于代码组件库中,并能够点击设计稿中的链接,间接查看组件的应用阐明。 2.3 计划落地尽管UI一致性在落地上会减少开发同学不少的工作量,推动一致性建设也是一个艰巨的工作,因为老本较高,且无奈量化评估收益,很多团队最终未达到预期成果,但一旦无效运作起来后,团队将取得丰富的回报。UI一致性的建设须要设计者对现有状态有足够的意识,对业务有充沛了解,以及优良的设计能力,同时还要一直地进行实际和优化。为了保障一致性我的项目的胜利落地,防止“大功告成”,咱们制订了一系列的推动措施: 我的项目小组不能脱离日常需要开发工作。这样能够保障设计师所积淀的设计元素始终来自于最新的业务场景,同时我的项目产出能够疾速利用到最新的版本中得以验证。优先选择受视觉因素影响较大、投入产出比高的模块场景进行革新,化繁为简,确定最小验证闭环 (MVP,Minimum Viable Product),在实践中一直优化,进而跑通整个流程。我的项目推动由UI同学按版本提出需要,挪动端排期并落地施行,由UI对立验收。建设阶段性指标,并实现最近三期工作的具体布局,定期复盘实现状况,保障我的项目的继续推动。2.4 一致性成绩通过一段时间的UI一致性建设,在资源一致性方面,外卖App团队曾经实现了近百个Iconfont的替换工作,无效减小了安装包的体积。在组件代码库建设方面,实现组件替换三十多处,中等业务需要均匀节约3pd人力;在工具链方面,依据UI/UE提供的数据,对于强依赖设计资源的需要,在应用积木Sketch插件后,提效可能达到30%以上,对于UI资源依赖不强的流程需要,均匀提效能够达到50%以上。 3. 设计体系建设细化来看,UI一致性整体计划次要分为两个局部,一个是设计体系建设,另一个则是工具链建设。设计体系建设是根底,次要是设计师积淀设计格调,建设对立的UI设计标准的工作,而工具链建设则是撑持,是开发人员通过开发一系列的工具将开发过程闭环,实现设计体系落地。 3.1 外卖DPLDPL(Design Pattern Library)是一份面向UED设计人员的文档化阐明,形容了设计模式库的标准以及利用场景等,外卖DPL次要包含组件搭建标准以及资源一致性两局部。DPL的反面是技术实现,个别体现在Android/iOS/RN代码框架中,比方阿里的FusionDesign库、腾讯的QMUI库等,这些封装好的代码组件面向程序开发人员。在未建设DPL模型之前,开发同学拿到设计稿进行视觉还原后,须要批改屡次,能力最终通过设计师的验证,极大影响了开发效率,还升高了需要吞吐率。 而通过DPL实现设计-开发流程的闭环,UI同学因为设计规范的标准化,可使出稿效率、走查效率显著晋升,反复组件甚至无需走查;对于RD同学来说,组件库中的组件在配置正确的状况下,因为曾经通过了历史版本的测验,适配问题呈现较少,无需反复进行视觉的修改;对于设计团队来说,优良的设计体系具备包容性且充斥生命力,好的设计模式库可能帮忙实现规范化,从而加重界面开发的工作量,进步一致性;而对于设计师来说,建设DPL有助于缩小误用、滥用以及有效的翻新。 3.2 组件搭建在长期的版本迭代中,随着性能的一直减少以及UI的继续改版,新旧款式混淆,保护极为艰难。设计师通过将页面走查后果演绎梳理,制订设计规范,从而选取复用性高的组件进行组件库搭建。通过搭建组件库能够进行标准管制,防止控件的随便组合,缩小页面之间的差别;组件库中组件满足业务特色,同时能够应答一直变动的环境,具备云端动静调整能力,能够在标准更新时进行对立调整。 在不影响需要实现以及设计成果的前提下,只有在方案设计中尽可能应用组件,晋升组件设计稿中的覆盖度,才可能真正通过组件库来提效。而除了在新的需要中应用组件,还须要将已有页面内容尽量替换成组件,能力防止页面降级时的反复批改问题,真正进步产研效率。在进行组件库建设时要留神以下几点。 抉择适合粒度 组件的粒度抉择曾是困扰咱们很久的一个问题,尽管有构建设计零碎的外围实践——原子设计实践为领导,即依照“原子、分子、组织、模板、页面”五个层面进行页面设计。这一实践对于从零开发新利用没有任何问题,但进行一致性革新的App,往往曾经暴露出很多设计问题,曾经存在数百个成熟的线上页面,革新存在十分大的艰难,必须依据具体业务抉择适合粒度。在进行组件制作前,我的项目同学对外卖的近百个页面进行了梳理,对应用到的组件进行了分类,并依据组件的应用频率进行排序,制订了逐渐替换打算。从而防止了组件库做的很全、破费了很多的人力,但理论很多组件都用不上,或者开发的组件过少,笼罩场景有余等问题。 咱们将走查后果与设计师重复交换,发现复用性较高的组件大体能够分为两类:第一类“根底控件”,也就是相似于标签、按钮、开关等具备根底性能的元素,对应原子理论中的原子;第二类“业务组件”,相似于商品卡片等,是由“根底控件”组成(比方商品卡片由“标签控件”与“图片控件”组成),同时“业务组件”还能互相组合,成为更高阶的“简单组件”,相似于原子理论中的分子。“业务组件”的组合又是变幻无穷的,不同款式的业务组件能够组成相似“商家列表”、“菜品列表”等“模板”,而“模板”与“根本控件”组合在一起,就成为了“页面”。 具备拓展性 组件必须具备肯定的可配置属性能力晋升实用场景。可配置属性体现在三个方面:组件反对部分元素展现暗藏,例如商品卡片的题目、阐明、价格可依据接口数据管制展现逻辑;组件反对多种款式,例如商品卡片的左图右文排列、上图下文排列;组件反对业务方配置主题,如调整高亮色、调整对齐形式等。 反对对立治理 组件治理性能对外卖UI一致性起着至关重要的作用,这次要体现在两方面:首先是设计格调积淀,目前袋鼠UI曾经造成了本人的独特格调,外卖设计团队依据设计规范,对合乎UI一致性外卖业务场景的组件一直进行形象及建设,积淀出越来越多的通用业务组件,这些组件须要及时裁减到Library中,供团队成员应用;另外一个作用则是放弃团队应用的均为最新组件。因为各种起因,组件的设计元素(色调、字体、圆角等属性)可能会产生变更,须要及时揭示团队成员更新组件,从而放弃所有页面的一致性。 3.3 资源一致性UI设计语言与本身业务关联性很强,美团很多业务包含外卖、酒旅、团购等都有一套本人的设计零碎。“通用”意味着无奈满足具备业务特色的需要,不同业务的组件、色调零碎、动效、字体款式等千差万别,其中任意一环的缺失都会导致一致性被毁坏。 设计语言并不是一个形象的概念,大家提到美团就想起美团黄,想到袋鼠,想到菜品卡片列表,想到骑着摩托车衣着印有“美团外卖”衣服的骑手,通过设计语言能够传播品牌主张和设计理念。目前,袋鼠UI曾经造成了一套属于本人的独特格调,对于一致性元素解决有了一套本人的规范,对于产品的设计者而言,必须将这种风格化连续,能力使咱们整个我的项目具备高度的一致性,能力放弃“袋鼠特色“,保障吸引力。 3.3.1 图片 建设插画库 插图作为一种视觉语言,是品牌辨认度的要害外围元素,与单纯的文案信息不同,图形化在直观形容固有信息的同时,也在塑造情感背景,使用户更具沉迷感和共情性。插画在晋升产品用户体验的同时实现商业指标,在表白成果及生产效率上有独特的劣势,在谋求效率的互联网产品中被大量地使用。 因为之前产品中的插图未经零碎整合,而插画师的个人风格显著,不同的设计师在图形化的工作协同中,格调很难复现,而单纯由一名设计师去实现整体业务的插画建设工作也存在肯定危险。不同设计师之前画过的元素无奈互通,造成很多元素反复设计、格调不对立,不足系统性地创作和整顿,无奈最大化地晋升生产效率,并且影响产品的品质感。所以插图体系在放弃品牌一致性、晋升工作效率以及躲避危险上尤为重要。 应用Iconfont ...

November 27, 2020 · 1 min · jiezi

蚂蚁金服联合IDC发布中国金融级移动应用开发平台白皮书-金融机构加速执行移动优先战略

11月4日,蚂蚁金服联合国际数据公司IDC在第二十七届中国国际金融展上发布《移动金融科技助力新时代金融机构转型升级——中国金融级移动应用开发平台白皮书》(以下简称《白皮书》)。《白皮书》指出,中国⾦融市场正在经历剧烈的变⾰,⾦融业务呈现出移动化、智能化、场景化态势,移动应⽤需求大量爆发,推动着⾦融机构加速执⾏移动优先战略。 移动端成金融机构零售转型的重要载体,战略地位日益凸显《白皮书》认为,随着IT领域的新兴技术⾰命,企业数字化转型浪潮正在不断重塑各⾏各业的运⾏格局和发展⻛貌。以金融业为例,利率市场化、⾦融脱媒以及互联⽹跨界竞争等多重因素动摇了传统金融机构的盈利基础,使中国金融市场正在自内而外地发生巨大变革。 在此背景下,金融机构纷纷开启零售转型战略,以金融科技创新为抓手,为企业发展赋能。移动端作为金融机构重要的对外渠道之一,已成为零售转型的重要载体。利用优质的移动端设计提供精细化服务,吸引更多的个人客户长期驻留,是金融机构应对市场挑战、实现长期发展的必然选择,移动优先战略成为金融机构面向未来的发展共识。 《白皮书》显示,在⾦融科技的推动下,⼀⼤批传统线下业务通过技术创新转移⾄线上运营,⽤⼾不再受到银⾏服务时间和空间的限制,随时随地的享受移动端带来的便捷性,⽤⼾体验获得了⾰命性提升;保险公司⼤⼒拓展移动渠道,通过保险移动展业,以更加便捷⾼效地⽅式实现全业务触点的销售及管理,降低获客成本;证券企业则重点利⽤新技术提升信息和数据处理的时效性,满⾜客⼾对移动端⾏情资讯的传递和承载需求。 一站式移动开发平台mPaaS,助力金融机构移动优先战略落地《白皮书》指出,金融业务的移动化、场景化、智能化趋势,推动金融移动应用需求的爆发性增长,对金融机构围绕移动端的业务开发和保障能力提出极大挑战。金融机构迫切需要引入新一代移动设计开发思想,利用先进的移动应用开发平台实现在开发、运维、管理以及快速迭代方面的一系列变革。 蚂蚁金服移动金融技术总监祁晓龙认为,金融级移动应用开发平台应具备统一的开发框架,拥有强大的技术整合和集成能力,有助于打造基于移动中台的能力输出,满足金融行业对移动开发平台技术先进性、安全合规性和高稳定性、高可用性等一系列特殊要求。 基于对金融市场的洞察和全新用户行为的理解,蚂蚁金服移动开发平台mPaaS借助统一的客户端开发框架和蚂蚁特有的金融级移动中台能力,有效地加快研发效率,增加对APP动态管控及千人千面的数字化运营能力。同时基于“后台连接服务”构建与服务端的数据、多媒体传输通道,确保全链路的稳定、安全和高效。 移动端创新技术方面,mPaaS提供智能推荐、语音识别、图像识别、生物识别、小程序等能力,改善产品体验,助力业务创新。 安全合规方面,mPaaS提供IPv6、国密、容灾等特色行业能力,满足监管合规要求。同时辅以应用加固、安全键盘、IFAA生物认证及数据加密能力,充分保障数据在客户端本地、传输过程中的安全性。 目前mPaaS已服务中国农业银行、广发银行,华夏银行,西安银行、国寿保险、财通证券等众多B端客户,为国内国际用户都带来优质的移动端体验。 蚂蚁金服全栈式金融科技体系,助力金融业全域数字化转型科技是面向未来的核心驱动力。基于对未来的洞察和蚂蚁金融科技开放的实践深耕,蚂蚁金服在金融领域构建了一个自底向上的全栈式金融科技体系,从具有金融级别支撑能力的分布式计算平台等底层技术,到以人工智能、区块链等为代表的应用技术,再到智能风控、生物核身等金融级专有技术,以及一站式的移动开发平台mPaaS,形成完整的技术堆栈,助力金融机构快速打造新一代超级移动金融平台与大数据智能运营体系,推动金融机构转型升级。 据悉,包括蚂蚁金服移动开发平台mPaaS、分布式中间件SOFAStack、分布式关系数据库OceanBase等在内的产品和解决方案正通过阿里云新金融统一对外输出,服务各种类型的金融机构。而在未来,还会有越来越多的蚂蚁金服技术产品通过阿里云新金融对外输出。在第二十七届中国国际金融展上,这些世界级的解决方案进行了亮相,并吸引了诸多关注。

November 4, 2019 · 1 min · jiezi

移动端事件

移动端基础事件移动端事件虽然少,但是却可以利用它们来完成很多效果 touchstart 手指按下事件,类似mousedowntouchmove 手指移动事件,类似mousemovetouchend 手指抬起事件,类似mouseup注意:移动端事件最好还是使用addEventListener来添加。 移动端事件与PC端事件的区别触发点的不同PC端 mousemove 必须在被添加该事件的元素上,不需要鼠标按下就可以触发。mouseup 必须在被添加该事件的元素上抬起,抬起时才能触发。移动端 touchmove 按下时,必须在被添加该事件的元素上,但是按下后,即使不在该元素上,也能触发。touchend 即使不在被添加该事件的元素上,抬起时,也能触发。触发顺序不同不管添加的顺序如何,事件的触发顺序遵循如下。 touchstart -> touchend -> mousedown -> click -> mouseup而且 PC 端事件在移动端约有 300ms的延迟,是为了解决双击才会有这个延迟,如果没有延迟,那么双击链接时,相当于直接单机跳转。 touchstart和click的区别touchstart是只要手指触碰到元素,就会触发,而click必须是触碰了之后手指抬起才能触发。 移动端事件问题事件点透出现事件点透的条件如下 有两层重叠元素上面的元素带有touch事件,下面的元素带有click事件上层的元素点击后点击后需要display:none当满足上面的条件的时候,由于click事件会有延迟,而touch事件会立马触发,所以当点击了上面的元素的时候,会触发下面元素的click事件,这就是事件点透 解决方案 下面的元素不要添加click事件,而且不要使用默认带有click事件的元素,如a、input等把上面的事件也换成click事件,这样就不会立即触发,都会有延迟,下面的事件就不会触发了在上层元素的事件中通过event.preventDefault()取消事件的默认动作取消事件默认动作的作用往往在实际开发当中,我们不会使用系统提供的一些效果,而是自己通过事件来封装想要的效果,如滑屏等。 所以可以取消根元素的一些默认动作,但是由于在根结点上面阻止默认动作可能会存在一些问题,所以可以把代码放到一个容器中,然后取消这个容器的所有默认动作。代码如下: const box=document.querySelector('div');box.addEventListener('touchstart或者touchmove',ev=>{ ev.preventDefault(); //取消事件的默认动作});对于touchstart和touchmove取消它们的默认动作,分别又有不同的效果 touchstart 阻止浏览器的滚动条阻止用户双指缩放touchmove 解决ios10+及部分安卓通过设置viewport禁止用户缩放的功能解决事件点透问题阻止图片文字被选中问题阻止了长按元素会弹出系统菜单阻止了浏览器回弹效果阻止了浏览器的滚动条阻止了鼠标的事件阻止了input框的输入功能事件对象当然和PC端一样,移动端事件也有自己的事件对象,重要的几个属性如下 touches: 被添加事件元素上必须至少有一根手指,其他手指放不放在该元素上无所谓。表示位于当前屏幕上的所有手指。targetTouches:位于当前DOM元素上的手指列表changedTouches:触发当前事件的手指列表

June 6, 2019 · 1 min · jiezi

移动端页面在手机中调试

简介移动端页面运行在PC端,在移动端访问页面,实时的在PC端查看console、network等,并进行移动端的页面调试。 工具1.Chrome(PC版、移动版,版本尽量一致)2.手机(本人是Android,小米5 plus)3.USB线 步骤1.手机打开『USB调试』 例如:小米5 plus开启『USB调试』a. 我的设备->全部参数->MIUI版本(双击两次),会提示已处于开发者模式。b. 退出到『设置』,到『系统和设备』下选择『更多设置』,找见『开发者选项』,点进去开启『USB调试』。 2.手机和PC连接USB,手机会提示接入信息,点击确定。 3.PC端chrome地址栏访问 chrome://inspect/#devices,会出现 4.调试(举例展示)PC端在『Elements』选中标签。 在移动端表现:

May 27, 2019 · 1 min · jiezi

ios12中遇到的带input弹窗的错位问题

问题描述:使用fixed定位的弹窗,在ios12的系统里,软键盘调起后,页面整体上移,当软键盘消失时,视觉上页面已经回到原始位置,但其实弹窗的焦点位置仍在软键盘调起时的位置。 解决办法:这也是参考某位大佬的解决办法 document.body.addEventListener('focusin', () => { // 软键盘弹出的事件处理 this.isReset = false})document.body.addEventListener('focusout', () => { // 软键盘收起的事件处理 this.isReset = true setTimeout(() => { // 当焦点在弹出层的输入框之间切换时先不归位 if (this.isReset) { window.scroll(0, 0) // 失焦后强制让页面归位 } }, 300)})尝试解决的其他方法尝试不使用fix定位,选择的absolute,判断input失焦时,使用window.scroll(),但是需要解决的问题很多 不同手机的input框在软键盘收起时情况不一样。苹果手机软键盘收起时,input框就失焦,但是小米手机键盘收起时,input框不失焦使用absolute定位后,软键盘出现页面会上移,软键盘消失时,页面不能恢复原来的位置

May 18, 2019 · 1 min · jiezi

如何在手机上查看电脑上的Vue项目

在PC端写好的Vue项目,一般都是npm run dev编译之后再本地浏览器地址栏输入localhost:8080来查看 为什么是输入localhost:8080,原因在于vue项目的配置文件config文件夹下的index.html中,host意为主机,localhost意为本地主机,port意为端口,8080就是默认的端口,当然你可以随意更改 因为是在本地主机启动的项目,所以只能在写代码的那台电脑查看,下面我们就来介绍如何在手机端查看1、保证手机和电脑在同一网络下将你的手机和电脑连接同一个WIFI,台式机的话。。。。,略过,以后会写台式机,哈哈2、查看电脑的内网IP内网IP哦,就是一般就是192.168....这样的打开CMD命令窗口,输入ipconfig,回车 3、修改Vue项目配置文件打开刚才上面提到的那个config文件夹下的index.js文件将host的值修改为你的IP地址 port改不改无所谓 4、重新启动Vue项目 5、在手机端打开浏览器,输入192.168.0.XXX:8080 OK,到此结束,该方法要注意两点:1、手机和电脑在同一网络下2、项目配置文件中设置的host一定要是电脑的IP,因为如果你电脑重启或者断网后,IP可能会变化

May 2, 2019 · 1 min · jiezi

Flutter 实现原理及在马蜂窝的跨平台开发实践

一直以来,跨平台开发都是困扰移动客户端开发的难题。在马蜂窝旅游 App 很多业务场景里,我们尝试过一些主流的跨平台开发解决方案,比如 WebView 和 React Native,来提升开发效率和用户体验。但这两种方式也带来了新的问题。比如使用 WebView 跨平台方式,优点确实非常明显。基于 WebView 的框架集成了当下 Web 开发的诸多优势:丰富的控件库、动态化、良好的技术社区、测试自动化等等。但是缺点也同样明显:渲染效率和 JavaScript 的执行能力都比较差,使页面的加载速度和用户体验都不尽如人意。而使用以 React Native(简称 RN)为代表的框架时,维护又成了大难题。RN 使用类 HTML+JS 的 UI 创建逻辑,生成对应的原生页面,将页面的渲染工作交给了系统,所以渲染效率有很大的优势。但由于 RN 代码是通过 JS 桥接的方式转换为原生的控件,所以受各个系统间的差异影响非常大,虽然可以开发一套代码,但对各个平台的适配却非常的繁琐和麻烦。为什么是 Flutter2018 年 12 月初,Google 正式发布了开源跨平台 UI 框架 Flutter 1.0 Release 版本,马蜂窝电商客户端团队进行了调研与实践,发现 Flutter 能很好的帮助我们解决开发中遇到的问题。跨平台开发,针对 Android 与 iOS 的风格设计了两套设计语言的控件实现(Material & Cupertino)。这样不但能够节约人力成本,而且在用户体验上更好的适配 App 运行的平台。重写了一套跨平台的 UI 框架,渲染引擎是依靠 Skia 图形库实现。Flutter 中的控件树直接由渲染引擎和高性能本地 ARM 代码直接绘制,不需要通过中间对象(Web 应用中的虚拟 DOM 和真实 DOM,原生 App 中的虚拟控件和平台控件)来绘制,使它有接近原生页面的性能,帮助我们提供更好的用户体验。同时支持 JIT 和 AOT 编译。JIT 编译方式使其在开发阶段有个备受欢迎的功能——热重载(HotReload),这样在开发时可以省去构建的过程,提高开发效率。而在 Release 运行阶段采用 AOT 的编译方式,使执行效率非常高,让 Release 版本发挥更好的性能。于是,电商客户端团队决定探索 Flutter 在跨平台开发中的新可能,并率先应用于商家端 App 中。在本文中,我们将结合 Flutter 在马蜂窝商家端 App 中的应用实践,探讨 Flutter 架构的实现原理,有何优势,以及如何帮助我们解决问题。Flutter 架构和实现原理Flutter 使用 Dart 语言开发,主要有以下几点原因:Dart 一般情况下是运行 DartVM 上,但是也可以编译为 ARM 代码直接运行在硬件上。Dart 同时支持 AOT 和 JIT 两种编译方式,可以更好的提高开发以及 App 的执行效率。Dart 可以利用独特的隔离区(Isolate)实现多线程。而且不共享内存,可以实现无锁快速分配。分代垃圾回收,非常适合 UI 框架中常见的大量 Widgets 对象创建和销毁的优化。在为创建的对象分配内存时,Dart 是在现有的堆上移动指针,保证内存的增长是程线性的,于是就省了查找可用内存的过程。Dart 主要由 Google 负责开发和维护。目前 Dart 最新版本已经是 2.2,针对 App 和 Web 开发做了很多优化。并且对于大多数的开发者而言,Dart 的学习成本非常低。Flutter 架构也是采用的分层设计。从下到上依次为:Embedder(嵌入器)、Engine、Framework。<center>图 1: Flutter 分层架构图</center>Embedder是嵌入层,做好这一层的适配 Flutter 基本可以嵌入到任何平台上去; Engine层主要包含 Skia、Dart 和 Text。Skia 是开源的二位图形库;Dart 部分主要包括 runtime、Garbage Collection、编译模式支持等;Text 是文本渲染。Framework在最上层。我们的应用围绕 Framework 层来构建,因此也是本文要介绍的重点。Framework1.【Foundation】在最底层,主要定义底层工具类和方法,以提供给其他层使用。2.【Animation】是动画相关的类,可以基于此创建补间动画(Tween Animation)和物理原理动画(Physics-based Animation),类似 Android 的 ValueAnimator 和 iOS 的 Core Animation。3.【Painting】封装了 Flutter Engine 提供的绘制接口,例如绘制缩放图像、插值生成阴影、绘制盒模型边框等。4.【Gesture】提供处理手势识别和交互的功能。5.【Rendering】是框架中的渲染库。控件的渲染主要包括三个阶段:布局(Layout)、绘制(Paint)、合成(Composite)。从下图可以看到,Flutter 流水线包括 7 个步骤。<center>图 2: Flutter 流水线</center>首先是获取到用户的操作,然后你的应用会因此显示一些动画,接着 Flutter 开始构建 Widget 对象。Widget 对象构建完成后进入渲染阶段,这个阶段主要包括三步:布局元素:决定页面元素在屏幕上的位置和大小;绘制阶段:将页面元素绘制成它们应有的样式;合成阶段:按照绘制规则将之前两个步骤的产物组合在一起。最后的光栅化由 Engine 层来完成。在渲染阶段,控件树(widget)会转换成对应的渲染对象(RenderObject)树,在 Rendering 层进行布局和绘制。在布局时 Flutter 深度优先遍历渲染对象树。数据流的传递方式是从上到下传递约束,从下到上传递大小。也就是说,父节点会将自己的约束传递给子节点,子节点根据接收到的约束来计算自己的大小,然后将自己的尺寸返回给父节点。整个过程中,位置信息由父节点来控制,子节点并不关心自己所在的位置,而父节点也不关心子节点具体长什么样子。<center>图 3: 数据流传递方式</center>为了防止因子节点发生变化而导致的整个控件树重绘,Flutter 加入了一个机制——Relayout Boundary,在一些特定的情形下 Relayout Boundary 会被自动创建,不需要开发者手动添加。例如,控件被设置了固定大小(tight constraint)、控件忽略所有子视图尺寸对自己的影响、控件自动占满父控件所提供的空间等等。很好理解,就是控件大小不会影响其他控件时,就没必要重新布局整个控件树。有了这个机制后,无论子树发生什么样的变化,处理范围都只在子树上。<center>图 4: Relayout Boundary 机制</center>在确定每个空间的位置和大小之后,就进入绘制阶段。绘制节点的时候也是深度遍历绘制节点树,然后把不同的 RenderObject 绘制到不同的图层上。这时有可能出现一种特殊情况,如下图所示节点 2 在绘制子节点 4 时,由于其节点 4 需要单独绘制到一个图层上(如 video),因此绿色图层上面多了个黄色的图层。之后再需要绘制其他内容(标记 5)就需要再增加一个图层(红色)。再接下来要绘制节点 1 的右子树(标记 6),也会被绘制到红色图层上。所以如果 2 号节点发生改变就会改变红色图层上的内容,因此也影响到了毫不相干的 6 号节点。<center>图 5: 绘制节点与图层的关系</center>为了避免这种情况,Flutter 的设计者这里基于 Relayout Boundary 的思想增加了Repaint Boundary。在绘制页面时候如果遇见 Repaint Boundary 就会强制切换图层。如下图所示,在从上到下遍历控件树遇到 Repaint Boundary 会重新绘制到新的图层(深蓝色),在从下到上返回的时候又遇到 Repaint Boundary,于是又增加一个新的图层(浅蓝色)。<center>图 6: Repaint Boundary 机制</center>这样,即使发生重绘也不会对其他子树产生影响。比如在 Scrollview 上,当滚动的时候发生内容重绘,如果在 Scrollview 以外的地方不需要重绘就可以使用 Repaint Boundary。Repaint Boundary 并不会像 Relayout Boundary 一样自动生成,而是需要我们自己来加入到控件树中。6.【Widget】控件层。所有控件的基类都是 Widget,Widget 的数据都是只读的, 不能改变。所以每次需要更新页面时都需要重新创建一个新的控件树。每一个 Widget 会通过一个 RenderObjectElement 对应到一个渲染节点(RenderObject),可以简单理解为 Widget 中只存储了页面元素的信息,而真正负责布局、渲染的是 RenderObject。在页面更新重新生成控件树时,RenderObjectElement 树会尽量保持重用。由于 RenderObjectElement 持有对应的 RenderObject,所有 RenderObject 树也会尽可能的被重用。如图所示就是三棵树之间的关系。在这张图里我们把形状当做渲染节点的类型,颜色是它的属性,即形状不同就是不同的渲染节点,而颜色不同只是同一对象的属性的不同。<center>图 7:Widget、Element 和 Render 之间的关系</center>如果想把方形的颜色换成黄色,将圆形的颜色变成红色,由于控件是不能被修改的,需要重新生成两个新的控件 Rectangle yellow 和 Circle red。由于只是修改了颜色属性,所以 Element 和 RenderObject 都被重用,而之前的控件树会被释放回收。<center>图 8: 示例</center>那么如果把红色圆形变成三角形又会怎样呢?由于这里发生变化的是类型,所以对应的 Element 节点和 RenderObject 节点都需要重新创建。但是由于黄色方形没有发生改变,所以其对应的 Element 节点和 RenderObject 节点没有发生变化。<center>图 9: 示例</center>7. 最后是【Material】 & 【Cupertino】,这是在 Widget 层之上框架为开发者提供的基于两套设计语言实现的 UI 控件,可以帮助我们的 App 在不同平台上提供接近原生的用户体验。Flutter 在马蜂窝商家端App 中的应用实践<center>图 10: 马蜂窝商家端使用 Flutter 开发的页面</center>开发方式:Flutter + Native由于商家端已经是一款成熟的 App,不可能创建一个新的 Flutter 工程全部重新开发,因此我们选择 Native 与 Flutter 混编的方案来实现。 在了解 Native 与 Flutter 混编方案前,首先我们需要了解在 Flutter 工程中,通常有以下 4 种工程类型:1. Flutter Application标准的 Flutter App 工程,包含标准的 Dart 层与 Native 平台层。2. Flutter ModuleFlutter 组件工程,仅包含 Dart 层实现,Native 平台层子工程为通过 Flutter 自动生成的隐藏工程(.ios /.android)。3. Flutter PluginFlutter 平台插件工程,包含 Dart 层与 Native 平台层的实现。4. Flutter PackageFlutter 纯 Dart 插件工程,仅包含 Dart 层的实现,往往定义一些公共 Widget。了解了 Flutter 工程类型后,我们来看下官方提供的一种混编方案(https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps),即在现有工程下创建Flutter Module 工程,以本地依赖的方式集成到现有的 Native 工程中。官方集成方案(以 iOS 为例)a. 在工程目录创建 FlutterModule,创建后,工程目录大致如下:b. 在 Podfile 文件中添加以下代码:flutter_application_path = ‘../flutter_Moudule/‘该脚本主要负责:pod 引入 Flutter.Framework 以及 FlutterPluginRegistrant 注册入口pod 引入 Flutter 第三方 plugin在每一个 pod 库的配置文件中写入对 Generated.xcconfig 文件的导入修改 pod 库的 ENABLE_BITCODE = NO(因为 Flutter 现在不支持 bitcode)c. 在 iOS 构建阶段 Build Phases 中注入构建时需要执行的 xcode_backend.sh (位于 FlutterSDK/packages/flutter_tools/bin) 脚本:"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build该脚本主要负责:构建 App.framework 以及 Flutter.framework 产物根据编译模式(debug/profile/release)导入对应的产物编译 flutter_asset 资源把以上产物 copy 到对应的构建产物中d. 与 Native 通信方案一:改造 AppDelegate 继承自 FlutterAppDelegate方案二:AppDelegate 实现 FlutterAppLifeCycleProvider 协议,生命周期由 FlutterPluginAppLifeCycleDelegate 传递给 Flutter以上就是官方提供的集成方案。我们最终没有选择此方案的原因,是它直接依赖于 FlutterModule 工程以及 Flutter 环境,使 Native 开发同学无法脱离 Flutter 环境开发,影响正常的开发流程,团队合作成本较大;而且会影响正常的打包流程。(目前 Flutter 团队正在重构嵌入 Native 工程的方式)最终我们选择另一种方案来解决以上的问题:远端依赖产物。<center>图 11 :远端依赖产物</center>iOS 集成方案通过对官方混编方案的研究,我们了解到 iOS 工程最终依赖的其实是 FlutterModule 工程构建出的产物(Framework,Asset,Plugin),只需将产物导出并 push 到远端仓库,iOS 工程通过远端依赖产物即可。依赖产物目录结构如下:App.framework: Flutter 工程产物(包含 Flutter 工程的代码,Debug 模式下它是个空壳,代码在 flutter_assets 中)。Flutter.framework:Flutter 引擎库。与编译模式(debug/profile/release)以及 CPU 架构(arm*, i386, x86_64)相匹配。lib.a & .h 头文件: FlutterPlugin 静态库(包含在 iOS 端的实现)。flutter_assets: 包含 Flutter 工程字体,图片等资源。在 Flutter1.2 版本中,被打包到 App.framework 中。Android 集成方案Android Nativite 集成是通过 Gradle 远程依赖 Flutter 工程产物的方式完成的,以下是具体的集成流程。a.创建 Flutter 标准工程$ flutter create flutter_demo默认使用 Java 代码,如果增加 Kotlin 支持,使用如下命令:$ flutter create -a kotlin flutter_demob.修改工程的默认配置修改 app module 工程的 build.gradle 配置 apply plugin: ‘com.android.application’ => apply plugin: ‘com.android.library’,并移除 applicationId 配置修改 root 工程的 build.gradle 配置在集成过程中 Flutter 依赖了三方 Plugins 后,遇到 Plugins 的代码没有被打进 Library 中的问题。通过以下配置解决(这种方式略显粗暴,后续的优化方案正在调研)。subprojects { project.buildDir = “${rootProject.buildDir}/app”}app module 增加 maven 打包配置c. 生成 Android Flutter 产物$ cd android$ ./gradlew uploadArchives官方默认的构建脚本在 Flutter 1.0.0 版本存在 Bug——最终的产物中会缺少 flutter_shared/icudtl.dat 文件,导致 App Crash。目前的解决方式是将这个文件复制到工程的 assets 下(在 Flutter 最新 1.2.1 版本中这个 Bug 已被修复,但是 1.2.1 版本又出现了一个 UI 渲染的问题,所以只能继续使用 1.0.0 版本)。d.Android Native 平台工程集成,增加下面依赖配置即可,不会影响 Native 平台开发的同学implementation ‘com.mfw.app:MerchantFlutter:0.0.5-beta’Flutter 和 iOS、Android 的交互使用平台通道(Platform Channels)在 Flutter 工程和宿主(Native 工程)之间传递消息,主要是通过 MethodChannel 进行方法的调用,如下图所示:<center>图 12 :Flutter 与 iOS、Android 交互</center>为了确保用户界面不会挂起,消息和响应是异步传递的,需要用 async 修饰方法,await 修饰调用语句。Flutter 工程和宿主工程通过在 Channel 构造函数中传递 Channel 名称进行关联。单个应用中使用的所有 Channel 名称必须是唯一的; 可以在 Channel 名称前加一个唯一的「域名前缀」。Flutter 与 Native 性能对比我们分别使用 Native 和 Flutter 开发了两个列表页,以下是页面效果和性能对比:iOS 对比(机型 6P 系统 10.3.3):Flutter 页面:iOS Native 页面:可以看到,从使用和直观感受都没有太大的差别。于是我们采集了一些其他方面的数据。Flutter 页面:iOS Native 页面:另外我们还对比了商家端接入 Flutter 前后包体积的大小:39Mb → 44MB在 iOS 机型上,流畅度上没有什么差异。从数值上来看,Flutter 在 内存跟 GPU/CPU 使用率上比原生略高。Demo 中并没有对 Flutter 做更多的优化,可以看出 Flutter 整体来说还是可以做出接近于原生的页面。下面是 Flutter 与 Android 的性能对比。Flutter 页面:Android Native 页面:从以上两张对比图可以看出,不考虑其他因素,单纯从性能角度来说,原生要优于 Flutter,但是差距并不大,而且 Flutter 具有的跨平台开发和热重载等特点极大地节省了开发效率。并且,未来的热修复特性更是值得期待。混合栈管理首先先介绍下 Flutter 路由的管理:Flutter 管理页面有两个概念:Route 和 Navigator。Navigator 是一个路由管理的 Widget(Flutter 中万物皆 Widget),它通过一个栈来管理一个路由 Widget 集合。通常当前屏幕显示的页面就是栈顶的路由。路由 (Route) 在移动开发中通常指页面(Page),这跟 web 开发中单页应用的 Route 概念意义是相同的,Route 在 Android 中通常指一个 Activity,在 iOS 中指一个 ViewController。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是 Android 还是 iOS,导航管理都会维护一个路由栈,路由入栈 (push) 操作对应打开一个新页面,路由出栈 (pop) 操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。<center>图 14 :Flutter 路由管理</center>如果是纯 Flutter 工程,页面栈无需我们进行管理,但是引入到 Native 工程内,就需要考虑如何管理混合栈。并且需要解决以下几个问题:1. 保证 Flutter 页面与 Native 页面之间的跳转从用户体验上没有任何差异2. 页面资源化(马蜂窝特有的业务逻辑)3. 保证生命周期完整性,处理相关打点事件上报4. 资源性能问题参考了业界内的解决方法,以及项目自身的实际场景,我们选择类似于 H5 在 Navite 中嵌入的方式,统一通过 openURL 跳转到一个 Native 页面(FlutterContainerVC),Native 页面通过 addChildViewController 方式添加 FlutterViewController(负责 Flutter 页面渲染),同时通过 channel 同步 Native 页面与 Flutter 页面。每一次的 push/pop 由 Native 发起,同时通过 channel 保持 Native 与 Flutter 页面同步——在 Native 中跳转 Flutter 页面与跳转原生无差异一个 Flutter 页面对应一个 Native 页面(FlutterContainerVC)——解决页面资源化FlutterContainerVC 通过 addChildViewController 对单例 FlutterViewController 进行复用——保证生命周期完整性,处理相关打点事件上报由于每一个 FlutterViewController(提供 Flutter 视图的实现)会启动三个线程,分别是 UI 线程、GPU 线程和 IO 线程,使用单例 FlutterViewController 可以减少对资源的占用——解决资源性能问题Flutter 应用总结Flutter 一经发布就很受关注,除了 iOS 和 Android 的开发者,很多前端工程师也都非常看好 Flutter 未来的发展前景。相信也有很多公司的团队已经投入到研究和实践中了。不过 Flutter 也有很多不足的地方,值得我们注意:虽然 1.2 版本已经发布,但是目前没有达到完全稳定状态,1.2 发布完了就出现了控件渲染的问题。加上 Dart 语言生态小,学习资料可能不够丰富。关于动态化的支持,目前 Flutter 还不支持线上动态性。如果要在 Android 上实现动态性相对容易些,iOS 由于审核原因要实现动态性可能成本很高。Flutter 中目前拿来就用的能力只有 UI 控件和 Dart 本身提供能力,对于平台级别的能力还需要通过 channel 的方式来扩展。已有工程迁移比较复杂,以前沉淀的 UI 控件,需要重新再实现一套。最后一点比较有争议,Flutter 不会从程序中拆分出额外的模板或布局语言,如 JSX 或 XM L,也不需要单独的可视布局工具。有的人认为配合 HotReload 功能使用非常方便,但我们发现这样代码会有非常多的嵌套,阅读起来有些吃力。目前阿里的闲鱼开发团队已经将 Flutter 用于大型实践,并应用在了比较重要的场景(如产品详情页),为后来者提供了良好的借鉴。马蜂窝的移动客户端团队关于 Flutter 的探索才刚刚起步,前面还有很多的问题需要我们一点一点去解决。不过无论从 Google 对其的重视程度,还是我们从实践中看到的这些优点,都让我们对 Flutter 充满信心,也希望在未来我们可以利用它创造更多的价值和奇迹。路途虽远,犹可期许。本文作者:马蜂窝电商研发客户端团队。(马蜂窝技术原创内容,转载务必注明出处保存文末二维码图片,禁止商业用途,谢谢配合。)参考文献:Flutter’s Layered Designhttps://www.youtube.com/watch?v=dkyY9WCGMi0 Flutter’s Rendering Pipelinehttps://www.youtube.com/watch?v=UUfXWzp0-DU&t=1955s Flutter 原理与美团的实践https://juejin.im/post/5b6d59476fb9a04fe91aa778#comment 关注马蜂窝技术,找到更多你想要的内容 ...

March 26, 2019 · 4 min · jiezi

IDE 插件新版本发布,开发效率 “biu” 起来了

近日,Cloud Toolkit正式推出了面向 IntelliJ 和 Eclipse 两个平台的新款插件,本文挑选了其中三个重大特性进行解读,点击文末官网跳转链接,可查看详细的版本说明。本地应用一键部署到任何机器上IDE 内置的命令行终端文件上传到服务器添加机器到 IntelliJ 或 Eclipse 中需要重点提下的是,虽然这个插件是阿里云官方插件,但了解到我们的开发者,还有不少原来的机器,以及线下环境,因此,这个插件不仅仅适用于阿里云 ECS,任何支持标准 SSH 协议的机器,都适用滴!在 IntelliJ 或 Eclipse,可以通过插件提供的图形引导界面,将机器配置到 IDE 中去。内置终端 Terminal在 IDE 内,开发者可以直接通过内置的终端 Terminal,即可一键快速登陆远程服务器。部署应用到远程服务器Cloud Toolkit 帮助开发者将本地应用程序一键部署到线下自有 VM,或阿里云 ECS、EDAS 和 Kubernetes 中去。当您每次修改完代码后,是否正在经历反复地打包?采用 SCP 工具上传?使用XShell或SecureCRT登陆服务器?替换部署包?重启?现在开始,请把这些重复繁琐的工作交给 Cloud Toolkit 吧!部署到 ECS完成编码后,无需在一系列运维工具之间切换,只需在 Cloud Toolkit 的图形界面上选择目标 ECS 实例,即可将应用部署至 ECS 指定目录部署到线下自有 VM支持 SSH 标准协议,你甚至可以将应用部署到任意机器中去部署到 EDAS针对阿里云 EDAS 开发者, 可以将本地代码和云端应用进行关联,实现自动化的部署部署到 Kubernetes针对阿里云 Kubernetes 开发者, 可以将本地代码和云端容器进行关联,实现自动化的镜像上传和部署文件上传Cloud Toolkit 帮助开发者在 IDE 内,一键将本地或者远程 URL 文件上传到服务器指定目录下去,无需在各种 FTP、SCP 工具之间频繁切换。更为重要的是,文件上传完毕后,还支持命令执行,比如:文件解压缩、程序启动等。官网https://toolkit.aliyun.com文档中心https://yq.aliyun.com/articles/665049需求Bug反馈https://www.wenjuan.com/s/emIFb2/本文作者:银时,Cloud Toolkit产品经理,《从Paxos到ZooKeeper》作者。立即点击下载官网:https://toolkit.aliyun.com本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 17, 2019 · 1 min · jiezi

移动端适配之二:visual viewport、layout viewport和ideal viewport介绍

上一篇博文,可算把像素这个东西讲清楚了。在这篇博文里面,将继续介绍viewport相关的内容。很多博客都会提到PPK所讲的三个viewport,有的讲的比较复杂,看的云里雾里,我这里也大概介绍一下,三个viewport主要是相对于移动端而言的。visual viewport这个是浏览器给我们用的、能真正用来显示网页内容的区域,可以通过下面的js命令获取:window.innerWidthwindow.innerHeight正如上篇博客所说的,前端里面能获取到的像素基本上都是CSS像素,所以这个的单位也是CSS像素。对于iPhone X,浏览器全屏状态下,其window.innerWidth的值为375。上篇博客中还提到screen.width和screen.height,主要是用来获取整个屏幕的大小的,而window.innerWidth和window.innerHeight只是获取浏览器可用显示区域的大小,也就是浏览器中间负责显示的部分。当浏览器全屏时,要去掉状态栏、标签栏、任务栏等区域,当浏览器非全屏时,其值更小。由于在移动端,浏览器一般都是全屏的,所以大多数情况下screen.width与window.innerWidth的值相等,也有的博客中说用screen.width和screen.height来获取visual viewport的大小,就是这个原因。visual viewport是我们可以直观看到的,不严谨的说,就是差不多等于手机屏幕的大小,偏向于一个物理概念。layout viewport网页最早是出现在电脑上的,上一篇博客中提到,电脑的物理像素可能比手机还要低,但是电脑的设备无关像素(或者说是分辨率吧,更严谨一些)是明显大于手机的设备无关像素的,毕竟电脑的屏幕尺寸远比手机大啊。那些在电脑上的网页,如果没有经过专门的优化,直接搬到手机上看,那么问题就来了,网页会被挤得变形,相信这种问题大家都遇到过。所以呢,手机厂商为了解决这个问题,设置了一个layout viewport。这是一个虚拟的窗口,其大小比手机屏幕大,加载网页时,直接把HTML渲染在这个虚拟的窗口中,这样就不会样式错乱了。在查看的时候,毕竟手机的visual viewport小啊,那就只能通过滚动条来看了。做个比喻,layout viewport就是一张大白纸,HTML的内容就写在这个大白纸上,visual viewport就是一个放大镜,上下左右移动,可以显示其中的一部分。Layout viewport的大小可以通过document.documentElement.clientWidth和document.document.clientHeight,实际使用中可能会有一些兼容问题,这跟DOCTYPE声明有关。不同浏览器的layout viewport大小不同,常见的有980px、1024px。ideal viewportLayout viewport是为了能将电脑上的网页正确的显示到手机上。当浏览器拿到一个网页时,首先会渲染到这个layout viewport里面。可是现在有很多网页会针对手机做专门的设计,比如现在的一些H5活动页,设计的尺寸就是在手机上看的。此时如果还是把网页渲染到这个大的layout viewport上,实在是有点不合适了。所以,还应该有个ideal viewport,这个ideal viewport应该与手机屏幕大小的相同,确切来说,等于visual viewport的大小。把页面渲染到这个ideal viewport里面,就能在visual viewport中完美显示。小结根据我的理解小结一下:layout viewport和ideal viewport都是用来渲染页面,layout viewport较大,用来渲染电脑上的页面,ideal viewport较小,用来渲染专门针对手机设计的页面;而visual viewport是用来查看layout viewport和ideal viewport的,是用来查看渲染的结果的。visual viewport是很具体的,而layout viewport和ideal viewport是比较抽象的。某种程度来说,layout viewport和ideal viewport可以理解成是两种尺寸,承载页面渲染的盒子,可以设置成layout viewport的尺寸,也可以设置成ideal viewport的尺寸,而且在默认情况下是layout viewport的尺寸。如果我们设置HTML中body为width:100%,那么这个body的实际宽度,将由这个盒子的宽度决定。在下一篇博文中,将介绍如何用meta标签来设置viewport,也就是设置这个承载HTML页面渲染的盒子的尺寸,从而达到最佳的显示效果。

December 27, 2018 · 1 min · jiezi

Eruda 一个可能被人遗忘的调试神器

引言 日常工作中再牛逼的大佬都不敢说自己的代码是完全没有问题的,既然有问题,那就也就有调试,说到调试工具,大家可能对于fiddler、Charles、chrome devtools、Firebug、还有Safari远程调试等比较熟悉,甚至有些是我可能也没有用过的; 这里喷一句吧,谁都别给我提IE啊,IE那个不叫调试工具,那叫坑爹神器,话说最近不是又甩锅了,把自己的革命老根据地都甩了。 俗话说预先善其事必先利其器,今天想给大家分享的是一个可能被人们忽略的小工具,为什么说被人们忽略呢?因为发现github上它才4.6k Star、457 Fork、Watch 173次,也就是说千万开发者中知道它的人可能不超过5w,于是决定分享一波,此文重在引导,希望能帮大家开发中带来一点点便利、效率的提升:这里是IT平头哥联盟,我是首席填坑官—苏南,用心分享 做有温度的攻城狮。公众号:honeyBadger8,群:912594095。Eruda是什么? Eruda是什么?Eruda 是一个专为前端移动端、移动端设计的调试面板,类似Chrome DevTools 的迷你版(没有chrome强大 这个是可以肯定的),其主要功能包括:捕获 console 日志、检查元素状态、显示性能指标、捕获XHR请求、显示本地存储和 Cookie 信息、浏览器特性检测等等。 虽说日常的移动端开发时,一般都是在用Chrome DevTools浏览器的移动端模式模拟各种手机型号来进行开发和调试,确保功能/页面展示等都没有问题了,才会提交测试; 但是前面都讲了,只是模拟、模拟,当下手机品牌可算是千千万,手机中各种类浏览器,甚至APP都有自己不一样的特色 腰间盘突出,有的还特别突出,有病我们得给它治啊,不然测试、产品、需求、领导都不会放过我们,比如下图场景。如何使用?它支持npm方式的,这个是不是很开心??外链,没错,就是外链的形式引入,对于它,我觉得npm的方式没有什么太大意义,直接以外链的引入更方便,也能减少项目资源包的大小,更便于控制是否要加载这个资源。方式一,默认引入:<script src="//cdn.jsdelivr.net/npm/eruda"></script><script>eruda.init();</script>方式二,动态加载:DEBUG && loadJS(‘http://cdn.jsdelivr.net/eruda/1.0.5/eruda.min.js', ()=>{ eruda.init();});//苏南的专栏 交流:912594095、公众号:honeyBadger8方式三 ,指定场景加载://比如线上 给自己留一个后门,//我们一般的做法是喜欢给某个不起眼的元素,添加一个点击事件,要点它十次、八次以后才开启 debug 模式;;(function () { var src = ‘http://cdn.jsdelivr.net/eruda/1.0.5/eruda.min.js'; if (!/eruda=true/.test(window.location) && localStorage.getItem(‘active-eruda’) != ’true’) return; document.write(’<scr’ + ‘ipt src="’ + src + ‘"></scr’ + ‘ipt>’); document.write(’<scr’ + ‘ipt>eruda.init();</scr’ + ‘ipt>’);})();方式四 ,npm: npm install eruda –save…… 加载的方式很多小而美这里小,不是指它的包小啊,知道它的同学都知道,其实它的包并不小(约100kb gzip);100kb不小了,用形容妹子的话来说就是:丰满,直接说它胖,你就死定了;这里的小而美是指小巧功能也强大,界面也好看;说了这么多 来看看它到底长啥样吧:功能清单consoleconsole 的作用就不用废话了,大家都懂;早期在console诞生之前,我们的调试功能都是alert过多,包括现在的移动端,在手机上我们想看到参数值、数据、节点等都以alert打印为多数,但过于粗暴、而且一不小心有可能带到线上去了;eruda 能帮我们解决这个问题;所有的日志、错误都能帮我们捕获到甚至我们还能像chrome,直接在控制台执行js代码;Elementseruda 它没有在PC端这么直观,但也因为在移动端展示的方式局限性,它能把每一个父节点下的每一个子节点全部列出来;你点击某个子节点时,列出当前节点全部的属性、样式、盒子模型等;查看标签内容及属性;查看Dom上的样式;支持页面元素高亮;支持屏幕直接点击选取;查看Dom上绑定的各类事件。甚至也能使用Plugins 插件,做到跟PC端一样,形成 dom tree;Network现在的项目大多都是前后端分享的形式了,前端处理的业务越来越多、各种请求资源等;干的越多承担责任也越多、锅也越多,又大又平的那种哦~所以 Network 的必要性不言而喻,它能捕获请求,查看发送数据、返回头、返回内容等信息,它对于我们平时前后端联调出现的问题定位是有很大帮助的,比如:后端说你请求参数少了,前端你看了代码逻辑没有问题,但在手机上就是调不通,Network 能很直接明了的看到你请求带了什么。Resources它跟 Chrome Devtools 里的 Application + Source,两者的结合体;Resources 它能查看 Cookie、localStorage、sessionStorage等信息,并且还能执行清除操作(Application);它还查看当前页面加载脚本及样式文件;查看页面加载过的图片等资源(Source);好吧,感觉说的再多,也不如上图直接:Sources/InfoSources:查看页面源码;格式化html,css,js代码及json数据。Info:主要输出URL信息及User Agent;及其他的一些手机系统信息,同时也支持自定义输出内容哦。高阶用法以上刚才介绍的是它的一些基本的功能,也是我自己在工作中用的较多的;最近发现新版本功能要强大不少,之前一直用的1.0.5,好像是没有插件这一项的;大概看了一下,都蛮强大,包括上面的Dom tree,插件这部分并没有都实际应用过,所以也就不打肿脸充胖子了,有兴趣的同学可以自己看看。如果觉得已经的插件都满足不了你的需求,它还支持自定义插件自己编写。结尾: 以上就是今天给大家带来的分享,工作中用了蛮久,挺方便的,对于定位移动端的疑难杂症问题、甚至留下后门定位线上问题都有很大帮助,如文中有理解不到位之处,欢迎留言指出。 线上问题我们一般的做法是喜欢给某个不起眼的元素,添加一个点击事件,要点它十次、八次以后才开启 debug 模式; 上面二维码确实是真实的官方Demo,不用担心有套路,也有链接:https://eruda.liriliri.io/Github 地址:https://github.com/liriliri/eruda其他vue/react/java/大厂面试题等资源免费获取 大家好 这就是2018年的我~月入三万 还能少了你一个鸡蛋如何给localStorage设置一个有效期?作者:苏南 - 首席填坑官链接:http://susouth.com/交流:912594095、公众号:honeyBadger8本文原创,著作权归作者所有。商业转载请联系@IT·平头哥联盟获得授权,非商业转载请注明原链接及出处。 ...

December 24, 2018 · 1 min · jiezi

主流移动滚动插件分析以及思路方案拓展(原生onscroll 事件实现的滚动加载不逊色)

背景现在流行的各种移动端滚动加载、上拉刷新组件,滑动越来越流畅,体验很好;例如 better-scroll、vue-infinite-scroll、iscroll、vue-scroll 等等比较好的方案去解决业务上的问题,可是笔者总会听到使用过程中也会产生一些奇怪的问题,或许会引起很多人的共鸣比如很多 better-scroll 小白会在一开始发现不能滚动,或者滚动加载异步数据better-scroll页面怎么不能滚动滚动加载了重复的数据(分页)、明明下面有数据,却拉不动等等各种姿势不对。笔者曾经已入坑了vue-infinite-scroll 和vue-scrollvue-infinite-scroll 是利用原生scroll 滚动,优点是原生滚动可以在列表加载了很多的时候不会卡顿,(黑科技)ios在微信上点两下回到顶部,但是不能滑动加速,插件本身不支持下拉刷新,而且在v-if 的组件下用到滚动比如一个侧边栏,会报错$mount error , 发现原因是我组件还是隐藏的时候,插件默认给我去跑$mount 钩子 ,我是通过改源码解决的,好坑,不过官方还是很快地发现了这个bug, 所以我觉得仍然是比较靠谱的vue-scroller 坑比较多,现在好像已经好久没维护了,不知道大家有无遇到过, 在同一个页面用vue-scroll 然后弹窗要 textarea 输入,超过行数产生滚动条,但坑爹的是textarea 竟然不能滚动了,看源码发现是touchMove 的事件里禁止了原生滚动,提了链接issue,我只能手动改源码了, 有图有真相对于滚动插件的实现原理以及优缺点对better-scroll、vue-scroll 这类滚动插件是要父元素container 定位在body, 禁止原生滚动,然后通过touch 事件,改变transform: translateY去实现,然后 refresh 去更新模拟滚动条长度对于 vue-infinite-scroll 这一类是通过原生onscroll 事件,判断scrollTop到达底部触发loadMore 加载异步数据, 当然原生的scroll 的缺点是 滚动点透,比如说在body上有一个弹窗滚动,滚动到底部之后会发现body 在滚了;第二个问题是,在finger触摸滚动而未结束滚动时,如果要做一些动作会有延时,不能像touchMove、touchEnd 那么灵敏我也是看了这篇文章得到了启发知识点1:移动web滚动问题在移动端,使用滚动来处理业务逻辑的情况有很多,例如列表的滚动加载数据,下拉刷新等等都需要利用滚动的相关知识,但是滚动事件在不同的移动端机型却又有不同的表现,下面就来一一总结一下。滚动事件:即onscroll事件,形成原因通俗解释是当子元素的高度超过父元素的高度时且父元素的高度时定值window除外,就会形成滚动条,滚动分为两种:局部滚动和body滚动。onscroll方法: 一般情况下当我们需要监听一个滚动事件时通常会用到onscroll方法来监听滚动事件的触发。如果在浏览器上调试这个方法在浏览器上很好用,但是如果跑在手机端就没有想象中的效果了。body滚动:在移动端如果使用body滚动,意思就是页面的高度由内容自动撑大,body自然形成滚动条,这时我们监听window.onscroll,发现onscroll并没有实时触发,只在手指触摸的屏幕上一直滑动时和滚动停止的那一刻才触发,采用了wk内核的webview除外。body滚动局部滚动 局部滚动:在移动端如果使用局部滚动,意思就是我们的滚动在一个固定宽高的div内触发,将该div设置成overflow:scroll/auto;来形成div内部的滚动,这时我们监听div的onscroll发现触发的时机区分android和ios两种情况,具体可以看下面表格:不同机型onscroll事件触发情况:body滚动 局部滚动ios 不能实时触发 不能实时触发android 实时触发 实时触发ios wkwebview内核 实时触发 实时触发wkwebview内核:这里说明一下关于ios的wkwebview内核是ios从ios8开始提供的新型webview内核,和之前的uiwebview相比,性能要好,具体大家可以自行查看关于wkwebview的相关概念。body滚动和局部滚动demo:这里我需要指出的是在采用wkwebview内核的页面中scroll是可以实时触发的,如果使用的是原本的uiwebview则不能够实时触发,手q目前使用的是uiwebview而新版微信使用的是wkwebview,大家可以分别使用来尝试一下下面的demo:局部滚动body滚动分别用ios手q和微信和android手q体验会有不同的结果。知识点2:关于模拟滚动有了上面介绍的关于滚动的知识,理解的模拟滚动就不难了。正常的滚动:我们平时使用的scroll,包括上面讲的滚动都属于正常滚动,利用浏览器自身提供的滚动条来实现滚动,底层是由浏览器内核控制。模拟滚动:最典型的例子就是iscroll了,原理一般有两种:1). 监听滚动元素的touchmove事件,当事件触发时修改元素的transform属性来实现元素的位移,让手指离开时触发touchend事件,然后采用requestanimationframe来在一个线型函数下不断的修改元素的transform来实现手指离开时的一段惯性滚动距离。2).监听滚动元素的touchmove事件,当事件触发时修改元素的transform属性来实现元素的位移,让手指离开时触发touchend事件,然后给元素一个css的animation,并设置好duration和function来实现手指离开时的一段惯性距离。这两种方案对比起来各有好处,第一种方案由于惯性滚动的时机时由js自己控制所以可以拿到滚动触发阶段的scrolltop值,并且滚动的回调函数onscroll在滚动的阶段都会触发。第二种方案相比第一种要劣势一些,区别在于手指离开时,采用的时css的animation来实现惯性滚动,所以无法直接触发惯性滚动过程中的onscroll事件,只有在animation结束时才可以借助animationend来获取到事件,当然也有一种方法可以实时获取滚动事件,也是借助于requestanimationframe来不断的去读取滚动元素的transform来拿到scrolltop同时触发onscroll回调。模拟滚动的fps值波动较大,这样滚动起来会有明显的卡顿感觉,各位体验的时候如果滚动超过10屏之后就可以明显感觉到两着的区别。在使用模拟滚动时,浏览器在js层面会消耗更多的性能去改变dom元素的位置,在dom复杂层级深的页面更为高,所以在长列表滚动时还要使用正常滚动更好。知识点3:滚动和下拉刷新下拉刷新的元素在页面顶部,正常浏览时不可见的。当在页面顶部往下滚动时出现下拉刷新元素,当手指离开时收起。以上两点时实现一个下拉刷新组件的基本步骤,结合我们上述关于滚动的描述,我们可以这样实现下拉刷新:方案1:借助iscroll的原理,整个页面使用模拟滚动,将下拉刷新元素放在顶部,当页面滚动到顶部下拉时,下拉刷新元素随着页面的滚动出现,当手指离开时收回,此方案实现起来较为简单直接借助iscoll即可,但是使用了模拟滚动之后在正常的列表滚动时性能上不如正常滚动。方案2:页面使用正常滚动,将下拉刷新元素放置在顶部top值为负值(正常情况下不可见),当页面处于顶部时下拉,这时监听touchmove事件,修改scrollcontent的tranlateY值,同时修改下拉刷新元素的tranlateY值,将两者同时位移来将下拉刷新元素显示出来,手指离开时(touchend)收回,这种方案满足了在正常列表滚动时使用原生的滚动节省性能,只在下拉刷新时使用模拟滚动来实现效果。方案3:方案2的改良版,唯一不同是将下拉刷新元素和scrollcontent放在一个div里,将下拉刷新元素的margintop设为负值,在下拉刷新时,只需要修改scrollcontent一个元素的tranlateY值即可实现下拉,在性能上要比方案2好。在采用了上述方案之后,还会有一个性能上的问题就是:当页面的列表过长,dom元素过多时,在模拟滚动,下拉刷新这段时间内,页面也会有卡顿现象,这里采取了一个优化策略即:1) 列表较长时dom数量较多时,在触发下拉刷新的时机时将页面视窗之外的dom元素隐藏或者存放在fragment里面。2) 在刷新完成之后手指离开(touchend)时将隐藏的元素显示出来。3) 需要注意的是,隐藏和显示视窗外的元素这个操作在下拉刷新时只会执行一次,并且只有在下拉刷新时才会执行。看完这篇文章,会感觉 better-scroll 也没那么牛逼了自己的看法滚动加载、上拉刷新一直是个前端界内一直在解决的题目,最近越发感觉自己要有自己的滚动插件,一个是用别人的东西不顺手,有问题只能在百度碰运气,解决不了问题可能要换框架或者改源码,时间也是挺耗的我还是喜欢 原生的onscroll 滚动加载,自己现在也是用这个实现的滚动加载,屡试不爽 在大神面前惭愧地贴下代码进入页面时 document.addEventListener(“scroll”, this.isToBottom);离开页面时 document.removeEventListener(“scroll”, this.isToBottom);敬请期待现在笔者正开发一个用两百行代码就可以实现原生onscroll滚动加载、下拉刷新,能够两次点击回到顶部、尽力打造稳定、解决问题、兼容性好、入手快、维护简单、源码简易的一个插件,区别于better-scroll 这种流畅感较好的插件,也算是一个 符合大众的一个解决方案,没有最好的只有最适合自己的

November 17, 2018 · 1 min · jiezi

iOS Push详述,了解一下?

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由WeTest质量开放平台团队发表于云+社区专栏作者:陈裕发, 腾讯系统测试工程师商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。原文链接:http://wetest.qq.com/lab/view/380.htmlWeTest 导读本文主要对iOS Push的在线push、本地push及离线(远程)push进行梳理,介绍了相关逻辑,测试时要注意的要点以及相关工具。小小的Push背后蕴藏着大大的逻辑!Push种类一、在线push在线push:当用户在线(APP在前台)时,收到的状态栏的消息提醒,称为在线push。这个功能与苹果系统无关,是我们自己的APP开发的一种功能,该push与设置中是否打开“通知”无关。这里以iOS Qzone为例,当APP在前台时,自己发的说说被点赞了,收到的在线push如下:1.png Qzone在线push 二、离线(远程)push离线push:当APP在离线(kill掉进程、切到后台、锁屏)时,收到的消息提醒,称为离线push。离线push是需要经过苹果的APNs服务器才可以推送到某台设备的某个APP上的,这是和本地push的本质区别。push与设置中是否打开“通知”有关。这里最简单的以大家常用的手机QQ为例,当APP在后台、锁屏或者被kiil了进程时,收到了消息:2.png 离线push 1、静默push静默push用的场景不较少,这里只做简要介绍。首先我们看看离线(远程)push与静默push的区别:普通离线(远程)push:收到推送后(有文字有声音),点开通知,进入APP后,才执行– (void)application:(UIApplication didReceiveRemoteNotification:(NSDictionary fetchCompletionHandler:(void result))handler )application )userInfo (^)(UIBackgroundFetchResult静默push:收到推送(没有文字没有声音),不用点开通知,不用打开APP,就能执行(void)application:(UIApplication )application)userInfo didReceiveRemoteNotification:(NSDictionary fetchCompletionHandler:(void (^)(UIBackgroundFetchResultresult))handler,用户完全感觉不到。所以静默push又被我们称做 Background Remote Notification(后台远程推送)。静默推送是在iOS7之后推出的一种推送方式。它与其他推送的区别在于允许应用收到通知后在后台(background)状态下运行一段代码,可用于从服务器获取内容更新。三、本地push本地push:本地推送和远程推送的功能是一样的,都是要提醒用户去做某些事情。但是和远程推送不同的就是本地推送是不需要设备联网的,而远程推送是必需要设备联网的,因为只有联网状态下,才能和苹果的APNs服务器建立长连接,从而推送消息。本地推送是由App自己设定的,并且发送给安装此App的这台设备,属于一对一的对应关系。比较典型的应用是闹钟类似的场景。该push与设置中是否打开“通知”有关。最容易看到本地push的场景,可以直接在手机设置一个计时器,计时器时间到了就会弹出本地push:3.png 本地push4.png由于本地push原理和作用相对于在线push和离线push都更为简单明了,下文主要介绍在线push和离线push。本地push实现一、 iOS10以前本地push弹出方式试验过iOS10以前的本地push方法在iOS10+的系统也能使用,不过可能有些参数不生效。1、立即展示( iOS10以前)本地push稍微简单,有两种方式可以调用,一种是presentLocalNotificationNow方法,立即展示本地push:5.png2、延迟展示( iOS10以前)另一种是用scheduleLocalNotification方法按计划来弹本地推送:6.png如果使用这种方法,需要对推送的时间进行设置,举个例子,设为5秒后:7.png二、设置本地push内容( iOS10以前)8.png其中alertBody是消息内容锁屏与不锁屏时效果如下:9.png 本地push效果applicationIconBadgeNumber是消息数量,我们可以看到这里设置为66:10.png 消息数三、处理本地push ( iOS10以前)1、 App没有启动情况下处理本地push这种情况下,当点击通知时,会启动App,而在App中,开发人员可以通过实现AppDelegate中的方法:- (BOOL)application:(UIApplication)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions,然后从lauchOptions中获取App启动的原因,若是因为本地通知,则可以App启动时对App做对应的操作,比方说跳转到某个画面等等。11.png2、App运行在后台及前台上面的2种情况的处理基本一致, 不同点只有当运行再后台的时候,会有弹窗提示用户另外一个App有通知,对于本地通知单的处理都是通过AppDelegate的方法:- (void)application:(UIApplication )application didReceiveLocalNotification:(UILocalNotification *)notification来处理的。12.png四、iOS10以后本地push弹出方式iOS10以后,本地通知可以由使用 UNUserNotificationCenter来管理。创建方法:13.png接下来需要需创建一个包含待通知内容的 UNMutableNotificationContent 对象:14.png在iOS上可以通过以下几种触发器来触发本地push:● UNCalendarNotificationTrigger 传送本地通知的日期和时间。● UNTimeIntervalNotificationTrigger 传递本地通知之前必须过期的时间。● UNLocationNotificationTrigger 用户必须达到的地理位置才能提供本地通知。● UNPushNotificationTrigger 表示通知是从Apple推送通知服务发送的对象。假如以时间间隔(TimeInterval)来触发,则设置触发器代码为:15.png推送本地push的代码为:16.png在线、离线(远程)push流程一、在线push流程在线push相对简单,因为是内部实现,具体流程如上面所示。1、判断app是否在线此处可以根据APP自身的后台策略如上一次与后台交互的时间等方法来判断APP是否在线或者离线。认为在线,会发送在线push,否则,发送离线push。2、在线push特点● 在线push有以下几个特点:● 不需要经过苹果APNs。● 需要自己实现长链接。● 代码在app内部实现。二、离线(远程)push流程17.png离线push流程主要流程为:● 服务器端将消息先发送到苹果的APNs● 由苹果的APNs将消息推送到客户的设备端● 由iOS系统将接收到的消息传递给相应的App。简而言之离线push是苹果系统的行为,与app状态无关,能够直接推送到指定手机的指定app。在进一步了解离线push前,我们有必要先了解几个名词。1、离线push名词解释—APNsAPNs:Apple Push Notification service(苹果推送通知服务)。APNs主要用于以下场景:当用户主动杀掉 APP,或者 APP 进入后台超过约定时长时,APP会被kill,这样保障了前台 APP 的流畅性,也延长了手机的使用时长,获得了较好的用户体验,但是这也意味着,服务器无法主动和用户交互(如推送实时消息等),所以苹果推出了 APNs,允许设备和服务器分别与苹果的推送通知服务器保持长连接状态。关于APNs的更新有以下几点:● iOS 8以后,APNs推送的字节是2k,iOS8以前是256字节● iOS 9以后APNs支持HTTP/2协议栈,优化长连接,具有标准的HTTP返回和管道复用技术● iOS 10以后,推送的字节是4k,APNs可根据推送消息的唯一标示符查询某条消息是否被用户阅读,可更新某一推送消息,而不用发重读的多条消息关于APNs更全面的介绍可以看官方文档:https://developer.apple.com/l…—payload什么是payload?对于每一条发送给APNs的推送消息,都包含一个payload,通常是组成了一个JSON的Dictionary,这其中必不可少的是aps属性,它对应的value也是一个Dictionary,包含一些但不限于以下内容:标题、副标题、内容、附件、category等,如18.png—device token什么是device token?我们看一下官方的简介:device token: APNs uses device tokens to identify each unique app and device combination. It also uses them to authenticate the routing of remote notifications sent to a device.(device token是APNs用于区分识别每个iOS设备和设备上不同app的一个标识符,还可以用于APNs通过它将推送消息路由到指定设备上)即:device token里包含了device id和bundle id的信息,但是device id和bundle id不会确定唯一的device token。但是,这里有个坑,查资料得知,iOS8及之前的iOS系统,对于同一部手机,如果卸载后重装APP的话,device token是不会变的,在token变了以后,老的token,就被认为是无效了,苹果不会对这部分无效的token推送。但是,对iOS9及以后的iOS系统,对于同一部手机,卸载后重装APP的device token是会发生变化的,而且老的token不会无效,还可以正常推送,这应该是苹果的一个bug,但是苹果也没有修复这个问题,所以这个需要开发者自己来解决,否则容易出现一个app收到多个push的问题。官方的说法是:To protect user privacy, do not use device tokens to identify user devices. Device tokens change when the user updates the operating system and when a device’s data and settings are erased. As a result, apps should always request the current device token at launch time.(即此举为了保护用户隐私,device token会在更新系统、擦除设置重置后变化,在一定时间后会过期)2、离线push详细流程知道了以上概念后我们重新来看一下离线(远程)push的详细流程:19.png 离线push详细流程1) 首先是应用程序注册消息推送。2) iOS跟APNS Server要deviceToken。应用程序接受deviceToken。3) 应用程序将deviceToken发送给PUSH服务端程序。4) 服务端程序向APNS服务发送消息。5) APNS服务将消息发送给iPhone应用程序。值得注意的是,当由于用户反复卸载重装程序(虽然概率很小)等原因导致多个device Token指向同一台设备的同一个app,又把多个device Token发给APNs时,用户就会收到多条push。苹果APNs是不会对多个device Token是否指向同一台设备的同一个app做校验的,所以需要后台来做去重等处理保证用户不会收到多条push。三、对离线(远程)push的响应1、iOS 7以上对离线(远程)push时的响应iOS 7以上关于接受离线push有两个函数20.png那么这两个函数有什么区别呢?其实这两个方法都是用来处理离线push的。差别就是,如果app在前台是收到离线(远程)push,那么就会调用21.png相对的,如果在后台或者杀进程情况下,点击收到的离线push,那么就会调用,如果没有实现22.png则会调用23.png若实现了前者,就只调用前者。2、iOS 10以上对离线(远程)push的响应iOS10对push的处理主要增加了两个方法24.png其中前者是对APP在前台时收到push时的处理,后者是点击push进入APP执行的函数。用得比较多的是后者,我们可以举个例子,点击push进入APP后如何获取push的消息、角标、标题等内容:25.pngiOS 10关于push的一些新特性iOS10新增的UserNotifications框架,主要有了这样几方面的更新:● 用UserNotifications框架替换了原先与通知相关的接口,通知文字可分为title、subtitle和body三部分,通知可携带附件● 系统在展示通知之前,可以唤起app附带的service extension,并且允许它改动通知的内容● 用户在对通知右滑查看、下拉或者3d touch的时候,通知会展开,展开后页面的布局可以由app附带的content extension来决定一、push的多样性iOS10以前的push只有文字,甚至没有标题。iOS10以后的push更加多样化,可以有主标题,副标题,甚至还有附件,这里以我司的腾讯新闻为例(有标题,内容,和附件):26.png 腾讯新闻push3D touch点入详情以后:27.png 腾讯新闻push详情这里我们惊奇的发现,除了可以携带图片这样的附件、push还能展开详情以外,进入详情以后,下面还多了“打开”、“收藏”、“不感兴趣”这些选项,这里就涉及到以下iOS10的新特性。二、push携带附件因为payload有大小限制,所以如果remote notification想要携带附件,那么payload上只能带上如附件下载地址之类的信息,等通知到达客户端后由service extension下载附件到本地,然后在初始化UNNotificationAttachment对象时传入附件在本地的URL。28.png初始化UNNotificationAttachment对象时,可以传入option参数。这里的option参数可以强制指定附件的类型,可以选择是否展示缩略图,以及缩略图截取自附件的哪一帧、哪一部分。目前iOS10通知只将几种格式的图片、音频和视频作为附件,附件的大小也有一定限制,具体可以看官方文档中的限制说明。关于附件的更加详细的说明,可以参考官方文档:https://developer.apple.com/d…三、携带action的通知上面提到的“打开”、“收藏”、“不感兴趣”这些选项其实就是push携带的action,其实从iOS8开始,通知已经可以携带action了。而在iOS10中,通知的action被放在了更明显的位置,与action相关的接口也有了很大变化。决定一个通知应该有哪些action呢?在payload中,这是由category字段决定的。如果我们希望一个通知能携带若干个action,我们就需要将若干个action和一个category绑定起来。通知到达前端后,系统会根据category的名字来决定要给这个通知展示哪些action:29.png怎么得知用户选了哪个action并做出相应操作呢?这需要给UNUserNotificationCenter指定一个delegate:30.png然后在delegate的类中实现31.png方法:通过response.notification.request.content.categoryIdentifier和response.actionIdentifier就可以得知用户选择的action了。四、改变push内容这里主要讲应用的比较多的离线(远程)push的改变push方法1、改变本地push内容本地push,只要request的id一样,那么就可以更新推送:更新的例子:31.png32.png此外,还有删除所有推送等,都在UNUserNotificationCenter.h中实现。2、改变离线(远程)push内容目前远程push只支持更新push内容,更新需要通过新的字段apps-collapse-id来作为唯一标示。方法是在HTTP/2 请求头中使用相同的apns-collapse-id,这样收到同样的apns-collapse-id的push时,push内容便会更新。使用场景:比较容易理解的一个场景就是球赛比分,比如现在是1:0,如果变成1:1的话,只需要刷新原来的新闻,这样用户就不会因为同一场比赛收到多条push。五、两个extension有两个与push相关的extension,可能我们会好奇这两个extension有什么不同,为什么需要两个?它们分别实现什么功能呢?33.png push相关extension1、notification service extension给app添加notification service extension后,系统会在收到通知后唤醒它,并允许它修改通知的内容,之后再展示这个通知。service extension只对remote notification起作用,local notification是无法唤起它的。如果想要让系统唤起service extension的话,payload必须符合这样几个条件:1) 必须增加mutable-content字段并为1,这表示允许客户端修改这个通知:payload(举例)如下:34.png2)这个通知必须展示一个alert,如果只是一个修改badge的通知的话,是不会唤起service extension的3)静默推送是不能唤起service extension的,所以payload中不能有”content-available” : 1字段所以,通过这个notification service extension,你可以在接收到推送之后、展示推送之前处理一些事情,比如说更新一下推送内容,或者在后台做一些其他事情。2、notification content extension另一项notification content extension用于完全自定义推送展开后的视图。上面腾讯新闻的展开后的视图就是通过这个notification content extension实现的。依然以腾讯新闻为例子:35.png 展开界面这里Notification Content Extension大展拳脚的地方,在这里可以自定义绘制不同的内容,将希望展现给用户的额外信息可以加载这里。下半部分的notification action的实现就是在上面提到的“携带action的通知”。测试要点36.pngQ&AQ:离线push,支持角标(badge)在本地角标数值上+1这样的操作吗?A:不支持。如果是自己实现push服务的话,需要自己的后台将角标值badge发送个APNs服务器,有些APP使用第三方push SDK除外。Q:如果重复收到离线push,可能是什么情况?A:1)iOS9之后卸载重装后生成新的deviceToken,后台对多个deviceToken都发送了push2)后台对注销了的账号也发送了push。总而言之一般是后台的逻辑出现了问题,而不是APNs服务器出现问题。Q:直接卸载APP,还能收到离线push吗?A:不会收到。直接卸载APP,虽然后台不知道APP被卸载了,仍然会对之前的账号发送push,但是由于手机上没有对应APP,所以并不会收到push。Q:为什么有时候全新安装APP就立马有红点角标?A:这是因为卸载该APP时有红点角标。每个 APP 的角标都是存在 iOS 手机系统里的,开发无法修改,所以此时卸载前有角标,重新安装也会有角标。但是,APP 卸载之后超过一天的时间再重装,那么角标就会被系统清空,届时也不会有新安装的 APP 就有角标的情况存在。相关工具Knuff离线push工具下载链接:https://github.com/KnuffApp/K…使用方法也比较简单37.png比如我的payload输入如下:38.png得到的应该是有“Knuff测试”文字,和角标数变为999,我们可以看下结果,与预料是一致的:39.png 预期结果有了这个工具也更加方便了我们的iOS push的调试。参考资料iOS推送之远程推送(iOS Notification Of Remote Notification):https://www.jianshu.com/p/4b9…相关阅读系统负载能力浅析搭建公众号自动回复功能【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

October 26, 2018 · 2 min · jiezi

Logan:美团点评的开源移动端基础日志库

前言Logan是美团点评集团移动端基础日志组件,这个名称是Log和An的组合,代表个体日志服务。同时Logan也是“金刚狼”大叔的名号,当然我们更希望这个产品能像金刚狼大叔一样犀利。Logan已经稳定迭代了一年多的时间。目前美团点评绝大多数App已经接入并使用Logan进行日志收集、上传、分析。近日,我们决定开源Logan生态体系中的存储SDK部分(Android/iOS),希望能够帮助更多开发者合理的解决移动端日志存储收集的相关痛点,也欢迎更多社区的开发者和我们一起共建Logan生态。Github的项目地址参见:https://github.com/Meituan-Di…。背景随着业务的不断扩张,移动端的日志也会不断增多。但业界对移动端日志并没有形成相对成体系的处理方式,在大多数情况下,还是针对不同的日志进行单一化的处理,然后结合这些日志处理的结果再来定位问题。然而,当用户达到一定量级之后,很多“疑难杂症”却无法通过之前的定位问题的方式来进行解决。移动端开发者最头疼的事情就是“为什么我使用和用户一模一样的手机,一模一样的系统版本,仿照用户的操作却复现不出Bug”。特别是对于Android开发者来说,手机型号、系统版本、网络环境等都非常复杂,即使拿到了一模一样的手机也复现不出Bug,这并不奇怪,当然很多时候并不能完全拿到真正完全一模一样的手机。相信很多同学见到下面这一幕都似曾相识:用(lao)户(ban):我发现我们App的XX页面打不开了,UI展示不出来,你来跟进一下这个问题。你:好的。于是,我们检查了用户反馈的机型和系统版本,然后找了一台同型号同版本的手机,试着复现却发现一切正常。我们又给用户打个电话,问问他到底是怎么操作的,再问问网络环境,继续尝试复现依旧未果。最后,我们查了一下Crash日志,网络日志,再看看埋点日志(发现还没报上来)。你内心OS:奇怪了,也没产生Crash,网络也是通的,但是为什么UI展示不出来呢?几个小时后……用(lao)户(ban):这问题有结果了吗?你:我用了各种办法复现不出来……暂时查不到是什么原因导致的这个问题。用(lao)户(ban):那怪我咯?你:……如果把一次Bug的产生看作是一次“凶案现场”,开发者就是破案的“侦探”。案发之后,侦探需要通过各种手段搜集线索,推理出犯案过程。这就好比开发者需要通过查询各种日志,分析这段时间App在用户手机里都经历了什么。一般来说,传统的日志搜集方法存在以下缺陷:日志上报不及时。由于日志上报需要网络请求,对于移动App来说频繁网络请求会比较耗电,所以日志SDK一般会积累到一定程度或者一定时间后再上报一次。上报的信息有限。由于日志上报网络请求的频次相对较高,为了节省用户流量,日志通常不会太大。尤其是网络日志等这种实时性较高的日志。日志孤岛。不同类型的日志上报到不同的日志系统中,相对孤立。日志不全。日志种类越来越多,有些日志SDK会对上报日志进行采样。面临挑战美团点评集团内部,移动端日志种类已经超过20种,而且随着业务的不断扩张,这一数字还在持续增加。特别是上文中提到的三个缺陷,也会被无限地进行放大。查问题是个苦力活,不一定所有的日志都上报在一个系统里,对于开发者来说,可能需要在多个系统中查看不同种类的日志,这大大增加了开发者定位问题的成本。如果我们每天上班都看着疑难Bug挂着无法解决,确实会很难受。这就像一个侦探遇到了疑难的案件,当他用尽各种手段收集线索,依然一无所获,那种心情可想而知。我们收集日志复现用户Bug的思路和侦探破案的思路非常相似,通过搜集的线索尽可能拼凑出相对完整的犯案场景。如果按照这个思路想下去,目前我们并没有什么更好的方法来处理这些问题。不过,虽然侦探破案和开发者查日志解决问题的思路很像,但实质并不一样。我们处理的是Bug,不是真实的案件。换句话说,因为我们的“死者”是可见的,那么就可以从它身上获取更多信息,甚至和它进行一次“灵魂的交流”。换个思路想,以往的操作都是通过各种各样的日志拼凑出用户出现Bug的场景,那可不可以先获取到用户在发生Bug的这段时间产生的所有日志(不采样,内容更详细),然后聚合这些日志分析出(筛除无关项)用户出现Bug的场景呢?个案分析新的思路重心从“日志”变为“用户”,我们称之为“个案分析”。简单来说,传统的思路是通过搜集散落在各系统的日志,然后拼凑出问题出现的场景,而新的思路是从用户产生的所有日志中聚合分析,寻找出现问题的场景。为此,我们进行了技术层面的尝试,而新的方案需要在功能上满足以下条件:支持多种日志收集,统一底层日志协议,抹平日志种类带来的差异。日志本地记录,在需要时上报,尽可能保证日志不丢失。日志内容要尽可能详细,不采样。日志类型可扩展,可由上层自定义。我们还需要在技术上满足以下条件:轻量级,包体尽量小API易用没有侵入性高性能横空出世在这种背景下,Logan横空出世,其核心体系由四大模块构成:日志输入日志存储后端系统前端系统最佳实践日志输入常见的日志类型有:代码级日志、网络日志、用户行为日志、崩溃日志、H5日志等。这些都是Logan的输入层,在不影响原日志体系功能的情况下,可将内容往Logan中存储一份。Logan的优势在于:日志内容可以更加丰富,写入时可以携带更多信息,也没有日志采样,只会等待合适的时机进行统一上报,能够节省用户的流量和电量。以网络日志为例,正常情况下网络日志只记录端到端延时、发包大小、回包大小字段等等,同时存在采样。而在Logan中网络日志不会被采样,除了上述内容还可以记录请求Headers、回包Headers、原始Url等信息。日志存储Logan存储SDK是这个开源项目的重点,它解决了业界内大多数移动端日志库存在的几个缺陷:卡顿,影响性能日志丢失安全性日志分散Logan自研的日志协议解决了日志本地聚合存储的问题,采用“先压缩再加密”的顺序,使用流式的加密和压缩,避免了CPU峰值,同时减少了CPU使用。跨平台C库提供了日志协议数据的格式化处理,针对大日志的分片处理,引入了MMAP机制解决了日志丢失问题,使用AES进行日志加密确保日志安全性。Logan核心逻辑都在C层完成,提供了跨平台支持的能力,在解决痛点问题的同时,也大大提升了性能。为了节约用户手机空间大小,日志文件只保留最近7天的日志,过期会自动删除。在Android设备上Logan将日志保存在沙盒中,保证了日志文件的安全性。详情请参考:美团点评移动端基础日志库——Logan后端系统后端是接收和处理数据中心,相当于Logan的大脑。主要有四个功能:接收日志日志解析归档日志分析数据平台接收日志客户端有两种日志上报的形式:主动上报和回捞上报。主动上报可以通过客服引导用户上报,也可以进行预埋,在特定行为发生时进行上报(例如用户投诉)。回捞上报是由后端向客户端发起回捞指令,这里不再赘述。所有日志上报都由Logan后端进行接收。日志解析归档客户端上报的日志经过加密和压缩处理,后端需要对数据解密、解压还原,继而对数据结构化归档存储。日志分析不同类型日志由不同的字段组合而成,携带着各自特有信息。网络日志有请求接口名称、端到端延时、发包大小、请求Headers等信息,用户行为日志有打开页面、点击事件等信息。对所有的各类型日志进行分析,把得到的信息串连起来,最终汇集形成一个完整的个人日志。数据平台数据平台是前端系统及第三方平台的数据来源,因为个人日志属于机密数据,所以数据获取有着严格的权限审核流程。同时数据平台会收集过往的Case,抽取其问题特征记录解决方案,为新Case提供建议。前端系统一个优秀的前端分析系统可以快速定位问题,提高效率。研发人员通过Logan前端系统搜索日志,进入日志详情页查看具体内容,从而定位问题,解决问题。目前集团内部的Logan前端日志详情页已经具备以下功能:日志可视化。所有的日志都经过结构化处理后,按照时间顺序展示。时间轴。数据可视化,利用图形方式进行语义分析。日志搜索。快速定位到相关日志内容。日志筛选。支持多类型日志,可选择需要分析的日志。日志分享。分享单条日志后,点开分享链接自动定位到分享的日志位置。Logan对日志进行数据可视化时,尝试利用图形方式进行语义分析简称为时间轴。每行代表着一种日志类型。同一日志类型有着多种图形、颜色,他们标识着不同的语义。例如时间轴中对代码级日志进行了日志类别的区分:利用颜色差异,可以轻松区分出错误的日志,点击红点即可直接跳转至错误日志详情。个案分析流程用户遇到问题联系客服反馈问题。客服收到用户反馈。记录Case,整理问题,同时引导用户上报Logan日志。研发同学收到Case,查找Logan日志,利用Logan系统完成日志筛选、时间定位、时间轴等功能,分析日志,进而还原Case“现场”。最后,结合代码定位问题,修复问题,解决Case。定位问题结合用户信息,通过Logan前端系统查找用户的日志。打开日志详情,首先使用时间定位功能,快速跳转到出问题时的日志,结合该日志上下文,可得到当时App运行情况,大致推断问题发生的原因。接着利用日志筛选功能,查找关键Log对可能出问题的地方逐一进行排查。最后结合代码,定位问题。当然,在实际上排查中问题比这复杂多,我们要反复查看日志、查看代码。这时还可能要借助一下Logan高级功能,如时间轴,通过时间轴可快速找出现异常的日志,点击时间轴上的图标可跳转到日志详情。通过网络日志中的Trace信息,还可以查看该请求在后台服务详细的响应栈情况和后台响应值。未来规划机器学习分析。首先收集过往的Case及解决方案,提取分析Case特征,将Case结构化后入库,然后通过机器学习快速分析上报的日志,指出日志中可能存在的问题,并给出解决方案建议;数据开放平台。业务方可以通过数据开放平台获取数据,再结合自身业务的特性研发出适合自己业务的工具、产品。平台支持PlatformiOSAndroidWebMini ProgramsSupport√√√√目前Logan SDK已经支持以上四个平台,本次开源iOS和Android平台,其他平台未来将会陆续进行开源,敬请期待。测试覆盖率由于Travis、Circle对Android NDK环境支持不够友好,Logan为了兼容较低版本的Android设备,目前对NDK的版本要求是16.1.4479499,所以我们并没有在Github仓库中配置CI。开发者可以本地运行测试用例,测试覆盖率可达到80%或者更高。开源计划在集团内部已经形成了以Logan为中心的个案分析生态系统。本次开源的内容有iOS、Android客户端模块、数据解析简易版,小程序版本、Web版本已经在开源的路上,后台系统,前端系统也在我们开源计划之中。未来我们会提供基于Logan大数据的数据平台,包含机器学习、疑难日志解决方案、大数据特征分析等高级功能。最后,我们希望提供更加完整的一体化个案分析生态系统,也欢迎大家给我们提出建议,共建社区。ModuleOpen SourceProcessingPlanningiOS√ Android√ Web √ Mini Programs √ Back End √Front End √团队介绍周辉,项目发起人,美团点评资深移动架构师。姜腾,项目核心开发者。立成,项目核心开发者。白帆,项目核心开发者。招聘点评平台移动研发中心,Base上海,为美团点评集团大多数移动端提供底层基础设施服务,包含网络通信、移动监控、推送触达、动态化引擎、移动研发工具等。同时团队还承载流量分发、UGC、内容生态、整合中心等业务研发,长年虚位以待有志于专注移动端研发的各路英雄。欢迎投递简历:hui.zhou#dianping.com。

October 12, 2018 · 1 min · jiezi

团战开黑必备“良药”了解一下!

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由腾讯游戏云发表于云+社区专栏第十八届亚运会在印度尼西亚首都雅加达进行得如火如荼,电子竞技作为2018亚运会的表演赛项目,首次登上亚运会的舞台。对于团队合作的电竞赛事来说,队友间的“语音”交流不可或缺。实时与队友流畅沟通战术,交流操作已成为电竞选手在比赛中取得好成绩的一大关键。随着移动设备性能大幅攀升,移动游戏也从场景简单的休闲类游戏发展为更追求操作和游戏体验的竞技类和大型MMO类等重度游戏,游戏中嵌入实时语音功能也已成为了标配,但在手游发展早期,多人实时语音往往是下面这样的:语音延迟语音不流畅(丢包)语音延迟、丢包带来的游戏体验可谓是非常差,还不如最初的打字、发信号。技术突破随着近几年互联网多媒体技术的快速发展,这两个问题已经得到了很好的解决。腾讯云游戏多媒体引擎(Game Multimedia Engine,简称GME),是一个专门针对游戏场景定制的,可覆盖休闲社交类、MOBA 类、MMORPG 等多种游戏类型,能提供包括多人实时语音、语音消息、语音转文本 3D位置语音、趣味变声、伴奏K歌等功能,满足多样化的游戏语音诉求。在游戏场景下可以实现超低时延、流畅优先的实时游戏语音自由对讲,让玩家体会对战类游戏的乐趣。一般情况下,游戏中场景比较复杂,实时语音互通控制在500ms以内就不会引起用户的不适感,而GME自研的技术能在复杂的游戏语音场景中将时延的时间控制在300ms以内,保证玩家流畅的通话体验,并通过先进的FEC前向纠错和智能的丢包重传和PLC丢包补偿技术,来取得开黑场景下通话延时和网络抗性的平衡,即使在网络损伤的情况下,也能有极佳的音质进行顺畅的沟通了。GME针对游戏场景的音频编解码器还进行深度优化,码率、延时、系统资源消耗等关键技术指标达到业界领先。在不同场景下GME可提供不同的音质体验和不同的抗网络损伤技术,实时语音音质在网络无损场景下的平均MOS分达到4.38(满分5分),平均延时低于200ms;通过先进的丢包恢复技术、丢包补偿算法以及优秀的网络抗性,即使在50%以上丢包、1000ms的网络抖动下,也能保持顺畅的沟通和很好的音质,力求给玩家带来最佳的游戏体验。此外,游戏语音的处理有特定的门槛,除了采集、处理、编码、传输、解码、渲染等各个环节本身需要的技术能力和经验之外,还需要很强的工程实力:解决几千种机型的适配和音频兼容性问题,以及海量高并发的处理能力。面对这些问题,GME团队在服务数个亿万用户量级产品的过程中已经积累了丰富经验。能力过硬,接入门槛较低,可满足多样化的游戏音频诉求的GME,从研发至今,不断发展完善,已为400多个产品提供音频技术支持。为产品提供技术保障的同时,也为用户带来更好的感官体验。了解更多腾讯云游戏多媒体引擎(Game Multimedia Engine,简称GME)请戳此处。问答游戏语音回调memberID不一致?相关阅读3行代码,为QQ轻游戏加上语音互动能力一个域名引发的血案……实时语音趣味变声,大叔变声“妙音娘子”Get一下 【每日课程推荐】新加坡南洋理工大学博士,带你深度学习NLP技术

September 3, 2018 · 1 min · jiezi