关于客户端:基于优酷业务特色的跨平台技术-GaiaX-开源解读

作者:别志华、祁强 GaiaX 跨端模板引擎,是在阿里优酷、淘票票、大麦内宽泛应用的 Native 动态化计划,其外围劣势是性能、稳固和易用。本系列文章《GaiaX 开源解读》,将带大家看看过来三年GaiaX的倒退过程。 GaiaX 开源地址:https://github.com/alibaba/GaiaX GaiaX的起源 GaiaX是为了解决多端卡片化UI组件的研发效力瓶颈问题孵化而来的,在过来三年中通过屡次迭代,逐渐实现了从设计研发一体化到模板研发体系的演进工作。 就像大部分优良的开源框架一样,GaiaX是从优酷本身的业务特色和业务问题登程,提出的一套解决方案,再通过一直提炼和拓展,从而造成了当初的模板研发体系,其目标是帮忙设计、开发、测试来提高效率,解决以后优酷App迭代过程中碰到的研发效力瓶颈问题。 上面会从优酷的业务特色、客户端研发效力的瓶颈问题、提出解决研发效力问题的思路这三个方面别离来进行介绍,带大家进一步理解GaiaX的起源。 业务特色 优酷是阿里巴巴文化娱乐团体的外围用户引擎,是中国当先的在线视频平台,现反对PC、电视、挪动三大终端,兼具版权、自制、合制、自频道、直播等多种内容状态。 在优酷继续改善经营效率和老本的背景下,优酷App曾经通过技术手段反对了电视、挪动、车载等多种设施场景,并一直为数千万用户提供更优质的应用体验。通对优酷App业务场景的剖析,咱们能够提炼出三个次要的业务特色: 内容散发场景为主。优酷App的业务状态次要蕴含散发场景和生产场景:散发场景的指标是以简略、高效的模式的疏导用户返回生产场景;生产场景的指标则是进行视频内容的生产或者会员权利的生产。 卡片化的UI组件。优酷App的UI组件彼此之间具备高度的类似相性、能够被结构化,并且能够组合应用。例如:左图右文的组件、上图下文的组件、单行多列、多行多列、Banner图、多类型组合等。 多屏多设施。优酷App目前反对Phone(Android、iOS)、Pad(Android、iOS)、Mac(iOS)、车机(Android)、电视(Android)等设施,为了升高研发老本和进步迭代效率,研发团队逐步在这些设施上应用同一份代码,一个UI组件会在多个设施和不同尺寸的屏幕上进行展现,这就对UI组件的实现提出了更多的适配要求。 研发效力的瓶颈问题咱们都晓得,想要解决一个问题的前提是将一个问题定义分明,那么该如何定义优酷App迭代过程中碰到的研发效力问题呢? 软件行业倒退至今,各家公司内的软件开发迭代流程虽略有不同但也大抵一样,能够将迭代流程分为业务环节、产品环节、设计环节、研发环节、测试环节、公布环节,各个环节彼此配合、相互协作能力顺利将产品公布。 在迭代流程中的各个环节也别离对应着各自的工夫周期,不同的工夫周期内所做的事和碰到的问题也不一样。一个迭代通常蕴含的工夫周期有:业务周期、产品周期、设计周期、研发周期、测试周期、公布周期。上面别离简要介绍一下每个周期内各自的痛点和瓶颈问题: 业务周期:需要变更、紧急需要、上游依赖方排期艰难,冀望上游依赖方能疾速消化需要和上线迭代。产品周期:业务变更频繁、上游排期艰难、需要迭代开发须要跟版公布、上游需要吞吐不现实。设计周期:业务和产品需要变更频繁、设计稿短少规范化、设计稿内容不能复用、设计走查不不便,冀望能将设计稿规范化、进步设计稿元素复用率。研发周期:双端开发、需要并发、排期短、跟版公布、设计稿不标准、性能保障,冀望能进步组件开发效率、能缩小人力投入、需要能疾速公布、组件性能能有保障。测试周期:测试周期短、BUG收敛慢、代码改变范畴不明确。公布周期:紧急集成导致公布节点不稳固。当咱们将注意力集中到研发周期内,咱们的痛点问题就十分清晰且明确: 需要开发须要多平台且要多份人力。需要吞吐量有余产品排期须要进行PK。UI组件构建过程中复用率低无奈疾速开发UI组件。设计稿不标准须要开发本义。短少适合的效率工具帮助UI走查。当问题被定义分明后,剩下的就是提出计划、落地计划、查看计划成果了。 上面让咱们看看GaiaX团队解决问题的思路是怎么的。 解决问题的思路当综合业务背景和研发效力的瓶颈问题一起来看,能够拆解进去诸多业务需要的因素,这些都是在研发周期内的痛点问题,例如:内容散发、多业务、多屏多设施、UI组件卡片化、UI组件的构建效率、UI组建的复用水平、进步需要吞吐量,这些需要因素都是在解法计划中要有所体现并进行针对性解决的。 解决问题的思路是找到一个具备如下解法因素的技术计划:UI组件可被规则化易于复用、具备跨平台能力可缩小人力投入、具备极致的性能可让开发人员不必重复调优、能复用现有优酷的技术栈和技术能力来保障一致性、具备动态性可动静公布需要以满足业务疾速迭代的需要、通过简略的解决就可适配多屏幕多设施。 通过一系列调研后,发现市面上并没有比拟贴合咱们解法因素的技术计划,于是咱们开启了自研的路线,现实中的GaiaX应具备以下技术个性,以解决研发效力的瓶颈问题:UI组件模板化、跨平台、媲美Native的性能与体验、动态性、根底组件可灵便插拔、易于适配不同的屏幕尺寸和设施。 在提出计划思路后,上面就是进行计划的落地,这部分内容会在 《GaiaX 开源解读》系列文章的第二篇:《跨端动态化模板引擎详解,看完你也能写一个》中进行具体的进行介绍,也请各位读者继续的关注。 GaiaX技术状态的倒退续接文章的上一节内容,在提出了解决研发效力瓶颈问题的思路之后,在本节内容中探讨和讲述一下GaiaX的技术状态问题。 跨平台技术曾经通过了三轮的技术演进,在每轮技术演进中都有各自的代表作和各自的核心理念,上面简要介绍一下: 基于WebView的跨平台框架。创立 HTML 并将其显示在App的 WebView 中,对于平台提供的一些零碎服务,通过JS Bridge来调用,一个残缺HTML5页面的展现要经验浏览器控件的加载、解析和渲染三大过程,这种开发模式开发的App既有原生利用代码又有Web利用代码,因而又被称为Hybrid App(混合应用程序)。以React Native为代表的“泛Web容器计划”跨平台框架。这种计划放弃了WebView渲染,采纳原生自带的UI组件作为外围的渲染引擎,所以这种计划的性能比第一代计划要好很多。代表作就是:ReactNative、Weex。这种计划放弃了JS作为开发语言,应用JSX进行UI页面的搭建。以Flutter为代表的应用“自绘引擎”的形式来进行UI渲染的跨平台框架。Flutter既不必WebView进行组件渲染,也不应用原生组件进行渲染,它齐全搞了一套跨平台UI渲染框架,渲染引擎依附跨平台的Skia图形库来实现,手机平台只须要提供一块画布即可。同时开发语言应用既反对JIT又反对AOT的Dart语言,既晋升了执行效率,也为反对动态化提供可能。下面三类跨平台技术,没有先进与否的区别,它们都有各自的利用场景和各自的瓶颈问题,在优酷App中也应用到了下面这三类跨平台技术: 基于“WebView”的跨平台框架,常常用于营销页面、流动页面,这种快用快消的场景之中;基于“泛Web容器计划”的跨平台框架,阿里外部应用的是Rax,目前常被用于收银台页面、大作业页面,这类对动态性、性能有要求的场景之中;基于“自绘引擎”的Flutter跨平台框架,目前常被用于一些通用二级页之中,这类场景对开发成本、性能有所要求,但对灵活性要求不高。当理解了这三类跨平台技术以及它们在优酷中的利用场景后,再联合上一节提到的优酷碰到的研发效力瓶颈问题,能够晓得这三类技术计划均无奈较好的解决碰到的问题,而任何技术的演进都是一直在已有技术根底之上一直揉捏、组合、提取、精炼的,上面让咱们看看GaiaX是采纳何种技术状态来解决研发效力问题的。 IDE搭建+双端渲染 因为是为了解决客户端同学在App研发过程中碰到的研发效力问题,在技术状态上思考了统一性、一致性、易用性、可拓展性、动态性之后,GaiaX决定采纳自定义DSL来搭建UI,并应用Native原生UI组件进行渲染的技术状态。自定义的DSL被称作模板,其中蕴含三个子DSL:一个用来形容UI元素之间的层级和嵌套关系 - index.json;一个用于形容UI元素的款式和布局信息 - index.css;一个用于形容视图元素的数据绑定信息和动静扩大信息 - index.databining。 自定义DSL好处多多。模板可独立存在,这样能够通过网络对模板进行下发和更新,来达到动静更新模板的目标,从而解除惯例开发中渲染产物对UI形容信息的耦合。款式的形容应用的是规范CSS标准,这样不仅业务方更容易接受和了解,也为将渲染产物拓展到H5、小程序上留下了可能性。模板可自在扩大能力边界,这也为起初减少JS逻辑留下了口子,也能让模板在肯定状况下有更强的动态性。除了以上这些,为了缩小开发人员的应用老本,也为了升高SDK的保护老本,GaiaX团队也提供了用于模板搭建的IDE,应用方通过拖拽、搭建就能构建出本人冀望的UI模板。 至此,一个应用自研IDE搭建模板并应用Native原生UI组件渲染的技术状态就此产生,因为从搭建、到解析、再到渲染都把握在本人手中,优酷App迭代过程中碰到的研发效力瓶颈问题都能够得以解决。 动态化能力 上文中咱们具体阐明了GaiaX的根本技术状态,但其中对动态化的能力却未进行具体阐明,置信很多读者对这个局部都很感兴趣。在GaiaX中,将动态化的能力分成了三类,别离是模板的动态化、模板的弱逻辑动态性、模板的强逻辑动态性,每类动态化的能力都解决了不同场景的问题,并且能够被渐进应用,上面别离介绍一下: 模板的动态化:模板是一个UI信息形容的汇合,当被下发到客户端后其内容就无奈被扭转了,如果有了紧急需要导致UI内容布局形式和内容产生了重大的变动该怎么解决?为了解决这类问题,咱们为模板赋予了版本号的概念,只须要将模板在IDE中从新进行编辑、上线、公布,就能够将新模板下发到用户的App中,来实现模板的动静更新,从而达到UI动静更新的目标。模板的弱逻辑动态性:在一些惯例需要中,常常须要依据一些条件暗藏或显示某些区域,或者依据数据扭转文字的色彩、字号、大小等。对于这类常态化的需要,咱们在数据绑定文件中,提供了应用表达式进行计算的能力,来达到依据数据动静扭转UI款式和显示内容的目标。模板的强逻辑动态性:在一些更为简单的需要中,不仅须要和服务端进行交互取回数据,也须要进行更为简单的业务逻辑解决和UI交互解决,这种状况在数据绑定文件中应用表达式解决往往无奈很好的实现需求,此时就须要更为弱小的逻辑解决能力了。为了解决此类问题,咱们引入了JS引擎,其专用于简单逻辑的解决,因为JS引擎不参加到UI绘制逻辑,其对模板的加载和渲染性能都是没有负面影响的。GaiaX动态化能力就全副介绍完了,在适合的场景选用适合的技术计划,能力用起码得人力实现业务需要,从而进步研发效力。 容器化能力 下面两节提到了GaiaX的渲染能力和动态化能力,作为跨平台的技术计划提供给业务方基本上曾经够用了,然而为了更进一步进步复用性和升高研发老本,咱们又提出了容器化的概念,进一步封装了GaiaX并依据应用场景将其分为三种类型: 第一种类型:业务方不须要编写任何Native代码,仅须要搭建和配置模板,通过应用容器中的主动埋点、主动路由、预置事件等能力,就能实现业务二级页、非核心的频道页以及一些动态组件、坑位的开发和上线工作。这个局部外围在于加强容器的各种原子化能力,例如:埋点、路由、事件、气氛、暗黑模式等,让业务通过模板编辑和配置就能实现UI搭建、逻辑解决、事件处理。第二种类型:业务方通过继承或者蕴含插件的模式,写大量的Native业务代码,通过减少一些自定义的配置,就能反对业务的频道、组件、坑位的开发。这个局部的外围在于容器层可能提供可扩大的能力,能让业务自定义实现。第三种类型:业务方须要写局部Native代码,用于解决非凡业务逻辑,再配合主动埋点、主动路由等原子化能力,来时实现业务迭代。三种容器化类型的作用域彼此配合,根本能够笼罩业务迭代中的大部分场景,通过借助应用GaiaX容器化的能力,业务方能够不间接应用GaiaXSDK,不仅升高了学习老本,也能再一次进步研发效率。 配套设施的加强 GaiaX在优酷落地的过程中,也碰到了模板查验效率低、模板开发效率低的问题,为了解决这些问题并造福开发和测试,也别离提供了实时预览工具以及设计稿D2C的性能。 应用实时预览工具,能够让开发者在IDE上搭建好模板后,通过扫码的形式,将模板渲染在指标设施上,并且能够随着IDE的改变实时更新UI成果,相似于Flutter的热更新,这样不仅更直观、也能让模板的开发效率更高。 应用设计稿D2C工具,能够让开发者将设计稿间接导出成模板文件,省去了模板中一部分的搭建工作(例如:层级构造和款式调整),开发者只须要解决数据绑定逻辑和UI更新逻辑,即可实现模板UI组件的开发,让开发效率再上一个台阶。除了实时预览工具和设计稿D2C工具外,GaiaX还提供了模板管理器、Ribut抓包与Mock神器、数据监控等配套设施,让GaiaX模板研发体系更为残缺,进一步推动解决研发效力的瓶颈问题。 GaiaX开源我的项目布局GaiaX目前曾经在Github上开源(https://github.com/alibaba/GaiaX),并且在紧锣密鼓的迭代开发中。此外,咱们也布局了将来几个月的我的项目路线图,以便让感兴趣的同学理解咱们的动向。 系列文章《GaiaX开源解读》系列文章预报如下,欢送继续关注: 已上线《基于优酷业务特色的跨平台技术》已上线《跨端动态化模板引擎详解,看完你也能写一个》《给Stretch(Rust语言编写的跨平台FlexBox布局计算库)新增个性,我掉了好多头发》《表达式作为逻辑动态化的根底,咱们是如何设计的》《向经典致敬 ReactNaitve与GaiaX渲染核心技术剖析》《为了保障双端一致性,咱们做了哪些致力》《一条龙的模板研发体系,你不来看看么》团队介绍咱们来自优酷产品技术与翻新核心利用技术部,从日常工作中实在存在的研发效力及痛点问题登程,咱们自研推出了GaiaX动静模板技术体系,指标是解决多端卡片化UI组件的研发效力问题,帮忙晋升开发者体验及效率。目前GaiaX我的项目曾经在GitHub开源【https://github.com/alibaba/GaiaX】,也殷切的心愿宽广终端(挪动端及前端)技术爱好者与咱们进行技术交换与共建。 ...

November 17, 2022 · 1 min · jiezi

关于客户端:客户端堆栈还原原理及实现

本文首发于微信公众号“Shopee技术团队”摘要MDAP(Multiple Dimension Analysis Platform)作为一个多维实时监控剖析平台,可能反对业务利用侧自定义指标的监控与剖析,并在自定义监控剖析能力上,实现了对挪动端利用性能数据的专项监控剖析能力,以满足业务日益增长的数据分析需要。 这是 MDAP 系列的第三篇文章,前文回顾: 《MDAP:可观测性数据分析平台设计与实际》《机器学习在基于 URL 的客户端监控剖析中的优化和实际》《Android 卡顿与 ANR 的剖析实际》1. 背景在软件调试及谬误排查过程中,无论是客户端 App 还是后端服务,一个常见的伎俩是通过谬误堆栈定位异样所在的源码地位,从而间接在源码层面分析问题根因。MDAP 平台的挪动端性能剖析能力很大水平上也依赖于对堆栈数据的采集和剖析,它的性能监控能力如下图所示。 其中,蓝色粗体局部的性能都须要 MDAP SDK 采集堆栈并上报到 MDAP 后盾,帮忙开发者定位异样问题,这意味着 MDAP 服务端须要反对 Android、iOS、Web H5、React Native 四类堆栈的高性能还原能力,以应答海量堆栈的上报。 1.1 堆栈还原的基本概念所谓的堆栈,通常特指某一时刻的函数调用上下文关系。咱们晓得,函数的调用和返回会在内存的栈空间中新建和销毁栈帧,而栈帧就是一次函数调用的上下文关系。若能获取到某一时刻栈空间中的栈帧散布,即可晓得该时刻的函数调用状况。 因而,堆栈通常可用于异样问题的排查。在这种场景下,咱们通常只会关怀函数名称(有时候还蕴含类名)、函数参数(若有)、文件名称、以及行号,而对诸如函数外部的局部变量等信息是不会太关怀的。 堆栈还原的含意就是将栈空间中的原始栈帧信息转换为源码级别的函数调用关系,这个过程通常也称为堆栈符号化。 另外,对于一些高级语言(例如 Java),其运行时环境(JVM)会间接输入相似于源码模式的堆栈信息,然而因为打包过程中会对源码进行压缩和混同,因而这类堆栈也须要一个转换过程,通常会称为反混同。 也就是说,依据语言和运行环境的不同,堆栈还原其实蕴含了不同的含意,其对应的堆栈还原原理天然也不尽相同。 1.2 堆栈的分类目前咱们公司内罕用的客户端平台能够分为四类: AndroidiOSWeb H5React Native其中,Android 平台比拟非凡,它不仅反对多种开发语言:Java(Kotlin)和 C++,并且它们两者之间是两套绝对比拟独立的体系(通常称为 Android Java 和 Android Native),因而其堆栈格局及堆栈还原原理齐全不同。 而在其余平台上,即便应用不同的开发语言,其堆栈构造及堆栈还原原理都是根本对立的。 因而,基于客户端平台能够将堆栈类型分为以下几类: 上一大节也提到过,堆栈还原蕴含了两层不同的含意,这也对应着两类截然不同的堆栈还原基本原理。从这个角度,能够进一步把上述堆栈分为两大类: 基于地址关系映射 Android NativeiOS基于符号关系映射 Android JavaWeb H5React Native基于地址关系映射,即客户端采集到的堆栈就是内存空间中 stack 区的栈帧排列,通常合乎以下特点: 应用编译型语言编写,通常为 C 语言及其衍生语言;编译后间接生成指标操作系统上的可执行文件;客户端采集的原始堆栈理论是内存栈空间中每一层函数调用的返回地址,运行时无奈获取源码级别的堆栈;堆栈还原实际上就是将堆栈中的函数地址转换为源代码中函数名称及对应的行(列)号。基于符号关系映射,客户端并不能间接从 stack 区中获取相干函数相干调用信息,它的根本特点如下: 通常应用解释型语言编写,例如 JavaScript;不能间接运行在操作系统之上,其运行时通常是一个操作系统之上的中间层,例如 JVM、V8 引擎等;通过这个中间层能够在运行时获取源码级别的堆栈,例如函数名和行列号;在工程化实际中,通常基于平安或者性能方面思考,代码构建时会将源代码进行压缩与混同,因而运行时获取的堆栈中的函数名和行列号都是通过混同转换的;堆栈还原的实质,就是将堆栈中混同过后的函数名称和行列号反混同为实在源码中的函数名称及对应的行列号。2. 计划调研综合上述背景介绍,咱们能够提炼出 MDAP 对于堆栈还原服务的两个根本要求: ...

August 29, 2022 · 9 min · jiezi

关于客户端:Android-卡顿与-ANR-的分析实践

本文首发于微信公众号“Shopee技术团队”摘要针对客户端开发的“终生之敌”——卡顿和 ANR 问题,本文将深刻分析零碎音讯队列机制和常见的卡顿与 ANR 成因,并介绍监控工具 LooperMonitor 如何借助多维分析平台 MDAP 的智能聚合和可视化看板,为业务方提供更精准、易用的剖析能力。 这是 MDAP 系列的第三篇文章,前文回顾: 《MDAP:可观测性数据分析平台设计与实际》《机器学习在基于 URL 的客户端监控剖析中的优化和实际》前言卡顿和 ANR,这是一个所有客户端开发同学都非常关注的话题,也是一个无奈绕过的话题。 卡顿的体现是 App 呈现丢帧、滑动不晦涩、用户的触摸事件响应慢;当产生十分重大的卡登时,App 甚至可能会弹出 Application not responding 的弹窗,提醒用户以后 App 无响应。 卡顿和 ANR 除了会影响用户的应用体验外,对于电商平台来说,在订单高峰期更是会间接影响成交量,导致理论的支出损失。能够说卡顿与 ANR 是客户端开发同学的终生之敌。 然而卡顿和 ANR 问题的剖析与解决又具备肯定的难度,尤其是 ANR。次要起因是 ANR 是主线程忙碌导致要害的零碎音讯不能及时执行而触发的,导致主线程忙碌的起因很多,同时系统对 ANR 的认定阈值又比拟久,最低也是 5s 起步,在这段时间内,有可能呈现了设施 CPU 资源缓和或主线程执行了一些耗时音讯的场景,这些场景都有可能是“导致雪崩产生的那几片雪花”。 目前业内支流的监控 SDK,其基本思路都是监听 ANR 信号,并在 ANR 产生现场抓取线程堆栈和零碎 ANR 日志,此时的堆栈抓取是一种预先策略,除了一些非常明显的比方线程死锁或者以后正好存在异样耗时的业务逻辑外,对更费解和简单的起因就无能为力了,这种“预先策略”往往导致上报的 ANR 数据里充斥着大量的“有效堆栈”。比方经典的 MessageQueue.nativePollOnce: 大量堆栈都聚合到 MessageQueue.nativePollOnce 这里了,难道是因为主线程调用 nativePollOnce 在 jni 层始终阻塞没有被唤醒吗?如果只借助这么一份堆栈数据的话,咱们无奈找到剖析思路,这些 ANR 问题是很难被解决的。 为了解决这些痛点,ShopeeFood 团队和 Shopee Engineering Infrastructure 团队通过深入研究零碎音讯队列机制和常见的卡顿与 ANR 成因,实现了一套新的监控工具 LooperMonitor,作为 APM-SDK 根底能力的一部分,与卡顿和 ANR 上报联合,借助多维分析平台 MDAP(Multi-dimension-analysis-platform)的智能聚合和可视化看板,旨在为业务方提供更精准和易用的剖析能力。 ...

August 29, 2022 · 9 min · jiezi

关于客户端:机器学习在基于-URL-的客户端监控分析中的优化和实践

本文首发于微信公众号“Shopee技术团队”摘要传统的客户端监控剖析场景中,采纳依照具体的 URL 进行统计分析的办法,在面对一个利用可能会拜访成千上万条 URL 时,后果就差强人意,不能显著地标识出利用拜访的哪些 URL 存在潜在问题。 MDAP 平台在进行客户端监控剖析时,通过应用概率统计和机器学习的计划,将若干条类似的 URL 归一化成同一条规定模型,而后基于该规定模型进行相干统计分析,从而进步了基于 URL 的客户端监控剖析的可用性及准确性,进而进步了 MDAP 用户对本人利用品质的监控剖析。 这是 MDAP 系列的第二篇文章,前文回顾:《MDAP:可观测性数据分析平台设计与实际》 1. 引言URL 作为客户端监控剖析的一个重要元素,传统的基于 URL 的统计分析形式间接应用原始 URL 值进行统计分析,比方: SELECT `url`, count(1) as `cnt` FROM `web_analysis_tab` WHERE `app_name` = 'app_1' GROUP BY `url`;应用上述查问语句进行统计分析的后果是十分蹩脚的,次要体现在以下几个方面: 利用开发者无奈疾速地、精确地通过剖析后果定位、发现潜在的利用问题、危险;统计后果过于扩散,用户可能会失去查看统计分析后果的趣味;平台解决过滤离散数据的统计分析,可能存在较大的零碎开销,包含:查问效率、网络传输、页面展现等。比方,假如 app_1 拜访过 1,000,000 个不同值的 URL,而其 URL 规定模型有余 100 个。 初版的 MDAP(Multi-Dimension Analysis Platform,多维度剖析平台)用户和开发者同样面对此类问题的困扰。为了更好地服务 MDAP 用户,帮助 MDAP 用户疾速、无效地剖析本人的利用品质。MDAP 平台基于概率统计实践和机器学习技术,依据利用上报的 URL,主动学习出相应的 URL 模型,利用衍生字段 url_pattern 而非原始 url 进行统计分析,从而大幅度缩小了基于 URL 统计分析过于散列的状况,使得统计分析后果更加收敛,进而更不便用户应用 MDAP 对本人的利用品质进行剖析查看。 ...

August 26, 2022 · 4 min · jiezi

关于客户端:ZEGO-自研客户端配置管理系统-云控

一、惯例客户端配置的弊病客户端配置信息通常会通过一个动态文件进行治理,或寄存在本地或者通过近程获取。存在本地最大的问题是不易更新,所以通常做法是通过近程获取。 咱们通过两种常见的场景来看看动态文件治理的客户端配置存在的问题: 1、一些配置参数的值是要依赖客户本地环境参数(机型、零碎版本、客户端版本、网络环境、硬件设施),客户本地环境参数产生了变动,那么配置参数要有相应的值来匹配,否则有可能会导致客户端的用户体验变差甚至性能不可用。举个例子,是否开启硬件编码减速,在 macOS + 零碎版本的条件下要敞开,而在 iOS 下要关上。 2、客户端新开发的性能心愿能依据用户或者用户所在地区、操作系统、机型等维度进行灰度上线。 通过下面列举的两种场景能够看到,动态文件要反对依据用户的环境来匹配适合的配置信息,有一个方法就是枚举所有可能的状况,客户端依据用户本地环境匹配其中一种状况,然而带来的问题也是不言而喻的: 1、客户端配置文件过大,客户拉取配置文件的工夫会变长,影响客户体验;服务器的流量和带宽存在不必要的节约; 2、配置文件内容结构复杂,不易于治理,难以保护; 3、客户端解析配置文件的逻辑简单且不灵便。 随着客户端的性能减少和优化,配置参数也会越来越多,会进一步加剧以上问题。 二、ZEGO 自研客户端配置管理系统 —— 云控系统思考到动态文件的客户端配置在开发和日常经营治理中可能产生的种种问题,为了达到最好的用户体验,同时保障开发、经营管理效率,ZEGO 自研了客户端配置管理系统,外部也称之为“云控系统”。 以下是云控系统的简易版架构图: 三、解决客户端配置信息动态化需要云控系统通过以下几方面解决了客户端配置信息动态化需要: 1、ZEGO Settings SDK(云控 SDK)对立了配置信息拉取和解析能力:各产品只须要集成了 ZEGO Settings SDK 就领有了对立的配置信息拉取、解析、配置变更告诉能力,下层产品依据告诉自行决定是否立刻失效。 2、动静获取配置信息:客户端通过 ZEGO Settings SDK 将用户环境信息发送给配置服务(云控服务),配置服务依据用户环境信息计算返回定制化的、无冗余的客户端配置信息;客户端信息也能够依据用户或者用户所在地区、操作系统、机型等维度进行灰度上线。 3、牢靠的寰球接入的通信链路:客户端通过 ZEGO 自研的智能路由 MSDN 零碎,依靠支流云商的寰球节点,寰球 200+ 机房部署无死角笼罩,保障了配置服务(云控服务)寰球牢靠稳固的拜访通信链路。 4、分布式云服务的配置信息存储节点 220+,任一节点有异样,其余节点都能迅速补上,保障了配置信息存储服务的高可用。 通过以上几个方面解决了配置文件动态化的需要,保障了最好的用户体验同时,进步了开发、日常配置管理效率,同时为实时解决单个用户环境问题提供了可能性。 总结以上是对于 ZEGO 即构科技如何反对动态化的下发客户端配置零碎的内容分享。 通过云控系统解决了用户级别的客户端个性化配置,为保障最佳的客户端用户体验提供了保障。 此外,ZEGO 自研客户端配置管理系统反对多产品多模块的客户端配置信息管理,进步了各产品模块客户端配置信息的治理能力,为疾速解决客户的线上问题提供了可能性。

March 18, 2022 · 1 min · jiezi

关于客户端:Apple-App-Clip苹果轻应用详解

前言App Clip (Apple 官网称之为「轻利用」)是你已有 App 的一个轻量级版本,它领有你 App 的局部性能,能够把它看做已有 App 的 lite 版本。 App Clip 体积十分小,压缩前包体积最大不超过 10 M,且无需经 App Store 下载,因而它能保障及时性,显示 App Clip 卡片的同时它简直就曾经装置在了设施上,响应迅速,如果你迫切希望用户拜访某些性能兴许 App Clip 能满足你的需要。 本文波及到的名词含意约定如下: App:App 指具备残缺性能的应用程序,即上架 App Store 的那个应用程序。 App Clip:指本文要钻研的轻利用。 浏览本文你将学到什么是 App Clip?App Clip 能做什么?App Clip 不能做什么?如何让用户发现你的 App Clip?如何开发一个 App Clip?可返回 GitHub 获取本文配套 Demo(https://github.com/yaoming88/...) 什么是 APP Clip?App Clip 要留神的点很多,为了残缺精确表述,同时防止挂出太多官网文档使读者精力扩散,笔者翻译了局部官网文档,如你曾经理解可跳过「什么是 APP Clip?」局部间接看编码局部。App Clip 是用户快速访问和体验 App 性能的绝佳形式。App Clip 是 App 性能的一小部分,能够在须要时随取随用。App Clip 采纳轻量级文件,运行速度快,不便用户疾速地关上应用。不论是从餐厅叫外卖、租辆车还是首次设置新的联网电器,用户在几秒之间就能开始并实现对你 App 的体验。在他们体验过后,你能够提供从 App Store 下载残缺 App 的机会。 ...

January 4, 2022 · 4 min · jiezi

关于客户端:淘宝客户端安全生产体系建设

作者:秦静超(非台) “  本文次要讲述了淘宝客户端的平安生产,即在阿里内外成熟的技术计划的根底上,淘宝客户端是如何来建设本身的平安生产体系,从研发、构建、公布、应急四个阶段再次推动效率和用户体验一直失去降级。 ” 平安生产首先咱们将“客户端平安生产”定义为:为预防客户端研发生命周期过程中产生体验相干的事变,而采取的一系列措施和流动。 为此淘宝客户端建设了“‘研发、构建、公布、应急’一整套规范化流程及平台”。 图1 平安生产架构图 淘宝客户端平安生产,次要分四个阶段:研发期、构建期、公布期和应急态,同时积淀开发过程数据,围绕数据线上线下异样复盘,为晋升代码品质、晋升开发能力,进一步欠缺平台做数据反对,从而晋升开发的良好研发环境,保障线上用户应用体验。 研发期:这一阶段次要是指开发同学把需要开发的阶段,往往是单模块的开发,这一阶段次要关注模块本身的品质,这一阶段平安生产平台次要通过需要治理、代码分支治理、单测治理、Code Review、测试申请|审批,一站式的形式为开发同学提供便当;构建期:这一阶段次要是指开发同学把曾经通过测试的代码,将代码提交到集成区做代码的集成测试,这一阶段平安生产平台次要通过品质卡口、包大小剖析、产物校验来确保集成进来的模块是否满足集成规范(反复的资源文件、代码等或高危的隐衷API、调试代码等或不合理的组件导出、DEBUG代码等),通过前置危险剖析,避免危险代码集成;公布期:这一阶段次要在通过测试后公布(灰度、正式)残缺的APP、配置变更、流动上线下线,这一阶段需关注APP稳定性数据、性能数据、业务数据与舆情数据(端到云的监控计划),确保公布的APP满足用户的体验要求;应急态:前三个阶段次要是躲避线上危险线,这一阶段则是在线上APP无奈满足用户失常应用时所进入的状态,线上外围指标稳定,会触发及时告警,从而通过钉钉疾速组建应急小队,对线上问题进行解决,剖析问题及问题背地的起因,疾速给出解决方案,通过预案回滚、降级解决等伎俩疾速化解线上危险,从而防止危险降级,避免故障产生。另外为了确保线上APP的高可用体验,淘宝端架构专门成立了“端侧日常保障”小组,次要从事版本值班、大促保障、应急解决、复盘优化等工作,从日常工作中一直的发现问题、总结思考、优化流程、改良研发环境,一直把局部须要人工染指的流程自动化、数据化、平台化,从而进步“研发、构建、公布、应急”阶段的研发体验和研发效率,最终把人力从反复的、低效的工作中释放出来,通过过程数据一直优化平安生产平台体验,从而保障下层业务衰弱继续倒退。让开发有更多的工夫和精力从事更高维度的研发工程,晋升开发的成就感。 研发期研发期次要是开发同学开发为主,这里平台提供了代码覆盖率(单测),外围模块中间件的单测代码覆盖率须要满足80%及以上,外围变更须要双人CR(含TL),非核心变更须要技术专家及以上CR。 构建期品质卡口随着淘宝业务一直扩张,线上问题时有发生,淘系的场景十分丰盛,本地的测试、Review、Monkey、甚至灰度等伎俩都不能笼罩到所有的场景。但问题一旦到了线上,所破费的老本都急剧减少。 通过对历史线上问题进行一些剖析,发现其中的相当一部分问题,能够通过 “动态代码剖析”, “二进制产物剖析” 等办法提前发现,那么为什么不能用技术的伎俩来提前发现问题,阻断它们溜到线上呢。例如: 同名 Category 办法抵触问题, 导致手淘很多性能异样;@{} 初始化没判空问题;oc block持有 c++ this指针导致 User After Free 问题;objc_msgSend 发送 alloc 导致内存泄露问题;一些零碎api不再平安,比方vm_remap;组件导出;线程透露;......这些问题上线后可能就引起很难定位的问题,然而在代码阶段,通过动态剖析等伎俩就能够阻断他们的产生。因而客户端品质卡口平台应运而生,将手淘客户端已有问题扫描工具和规定整合,联合DevSecOps卡口设计的凋谢卡口接入平台,造成残缺的客户端线下问题发现、治理推动和集成卡口的能力,缩小线上问题。 技术计划上,在Android Lint+Spotbugs+Clang Static Analyzer(Android),OCLint(iOS)+Clang Static Analyzer根底上联合淘系具体平台和具体问题进行改良,以满足淘系的技术需要(如扫描线程原生接口应用,辅助淘系整体线程架构迁徙)。 包大小包大小是客户端的十分重要的性能指标。从用户视角看,同样的性能和服务,用户偏向于抉择安装包比拟小的App,能够让用户用更少的流量下载和更新,肯定水平进步用户的下载率和更新降级率,对于推广和新增都会有较为重要的影响;从技术视角看,安装包中的每个文件都在瘦身的范畴,对不同的文件类型,须要有针对性的瘦身计划,所以瘦身是一个大工程,蕴含了很多方面的技术。 Android采纳了图片压缩(TinyPng,Webp),反复资源合并、shrinkResource 严格模式、分包、Proguard、ARSC瘦身、下无用代码(代码插桩剖析)、无用业务下线、远端so、检测so调试信息。 iOS采纳了图片压缩(TinyPng,Webp)、编译优化(不导出符号、oz、lto)、selectorRef无用资源下线,剔除反复代码、业务下线、共享动静库技术(<iOS9)、Ld链接器压缩。 产物校验产物校验产生在APP公布前的最初一个环节,次要来剖析本次公布与上一次公布外围变更存在哪些具体的差别,来确保本次公布的正确性。这一环节次要进行了外围代码变更剖析(启动、CrashSDK、监控SDK),须要关注外围代码变更带来的可能的危险;组件导出剖析,避免不必要的组件导出,受到内部攻打;签名校验,避免签名出错导致APP无奈失常上架;等等。 公布期监控告警淘系高度重视手机用户的稳定性和性能,通过高可用的度量指标的建设,稳定性和性能的治理,自动化和数据平台的建设,开发了一套系统化的解决方案及平台EMAS-MOTU,全方位的晋升手机淘宝稳定性和性能。 变更管控淘系是一个高频流动经营APP集,咱们发现有一些故障是由变更导致的(含流动上线,配置上线等),具备强相关性。因而淘系积淀了变更管控平台,变更管控平台次要作用是监控剖析平台发现的异样数据(Crash、ANR、卡断、透露等)与变更的相关性。 变更管控平台的外围思路是为每一次变更产生一个惟一的变更ID,并在本次变更下发的过程中,将变更ID退出到监控信息的变更ID集里,当监控信息上报时会带上所有的变更ID,服务能够对变更ID进行聚类分析,通过相关性确认雷同聚类问题是由哪些变更ID产生的,并对具体的变更留观或回滚,避免危险降级。 通过准确的变更相干灰度染色数据,管控相干灰度和全量公布,及时卡住异样公布,防止公布引起故障,同时也是进步公布效率的外围伎俩。 应急态定位追踪、度量、日志随着客户端性能一直细化与欠缺,模块化、跨团队的协同开发方式曾经成为了客户端开发规范的开发方式,模块化、跨团队的协同开发方式的诞生极大晋升了客户端交付与部署的效率,但同时能够看到这种模块化、跨团队的架构背地,原先运维与诊断的需要也变得越来越简单。为了适应客户端日益增长的性能需要,需实现面向用户视角的标准化的DevSecOps诊断与剖析零碎,包含追踪(Tracing),度量(Metrics),日志(Logging)。 Tracing :用于记录客户端行为范畴内的信息,它在单次申请的范畴内,解决信息。任何的数据、元数据信息都被绑定到零碎中的单个事务上。例如,用户进入页面,到数据渲染的过程。它是咱们排查客户端问题的利器;Metrics :用于记录可聚合的数据。他们具备原子性,每个都是一个逻辑计量单元,或者一个时间段内的柱状图。例如:队列的以后深度能够被定义为一个计量单元,在写入或读取时被更新统计;输出HTTP申请的数量能够被定义为一个计数器,用于简略累加;申请的执行工夫能够被定义为一个柱状图,在指定工夫片上更新和统计汇总。例如,从客户端发动的网络申请数,与正确收到网络数据的承受。它是咱们掂量业务宏观品质的利器;Logging :用于记录离散的事件。例如,应用程序的调试信息或错误信息。它是咱们诊断问题的根据。基于追踪(Tracing),度量(Metrics),日志(Logging)的设计准则,淘系借鉴了OpenTracing实现了全日志平台TLog。通过漏斗模型、比照模型通过数据横向比照能够疾速发现本身的性能瓶颈,放大范畴,晋升排查效率。 全景定位淘系是一个高频流动经营APP集,咱们发现一些故障是由变更导致的(含流动上线,配置上线等),具备强相关性。因而淘系积淀了全景定位平台,全景定位平台次要作用是监控分析线上的变更。全景定位平台的外围思路是当线上危险产生时,全景定位平台会被动去收集线上变更,并按工夫维度出现进去,开发可基于全景定位平台疾速查看线上变更,定位和排查线上危险与变更的相关性,对潜在危险的变更留观或回滚,直至危险打消。 全景定位与变更管控有肯定的相似性,都是对线上变更的监控与剖析,区别在于全景定位次要在危险产生后对变更进行剖析(业务在变动,无奈保障所有的变更都会接入变更管控),变更管控则次要危险产生前打标,危险产生后对变更进行剖析,因而全景定位是变更管控的一种补充与保障。 复原“对代码性能不合乎我的项目预期或代码不够强壮导致App运行时解体或异样的线上问题进行复原”。复原是淘系应答线上突发问题的重要伎俩,淘系目前对线上不同的场景采纳了不同的复原策略,目前次要复原策略有降级、预案、平安模式。 降级在淘系简单生态的环境下,大促期间还是会呈现因为各种重资源的业务叠加,导致卡顿,体验显著降落,内存水位暴涨,解体率也会随之飙升。因而从2018年开始,尝试开始对重资源、高风险业务,针对不同设施的性能状况进行多维度降级。目标是对用户体验进行分级,实现 “高端设施最炫体验,低端设施晦涩优先,紧急问题疾速降级” (随着工夫的推移,老的设施的软硬件条件,曾经无奈满足所有新技术、新业务的落地,须要有肯定的取舍,从而给每一个设施最佳的用户体验) 。 针对不同设施的软硬件不同个性,基于Listwise-SmartScorer模型,淘系对客户端设置了高、中、底三个维度,0-100(0示意设施性能优于市场上支流的0%的设施,100示意设施性能优于市场上支流的100%的设施)的动静的设施评分算法。 设施评分是个什么? 从机器学习的角度来看:它可能是一个分类问题,即设施分为高/中/低三类,咱们须要辨别这几个类;它可能是一个回归问题,即设施存在一个相对的分,咱们须要去拟合这个分。无论分类还是回归问题,都把设施评分定义为一个相对的值,而在理论体验中,咱们往往说“iPhone X”比“iPhone 8”快,而不是说“iPhone X” 90分,“iPhone 8” 70分,即设施评分是绝对的,同时因为设施的损耗,它的评分也是动静的,基于此,咱们把设施评分定义为一个排序问题。 在设施评分的根底上,实现对立降级平台,业务能够通过“高、中、底”或“0-100”选取相应的设施投放本身业务。 预案什么是预案?依据评估剖析或教训,对潜在的或可能产生的突发事件的类别和影响水平而当时制订的应急处理计划。预案能够升高可预估或不可预估的危险,缩小损失,而当下面临大多数的危险,都来自于各类变更,还有阿里最重要的大促场景下,大流量所带来的零碎、业务压力。预案有分提前预案和应急预案。 提前预案:也称定时预案,提前预估大促期间的零碎情况与业务情况,为了防止大促的业务峰值影响而进行的缓存预热、机器重启、无限降级、磁盘清理或者业务下线等等,个别对业务无影响或影响可控。 应急预案: 针对可能存在的应急状况,如超出预期的异样流量、零碎依赖超时及不可用、本零碎的非预期不可用等采取的应急伎俩,个别对业务有损,同时可能会带来客诉,资损等,须要对应的技术、业务等兜底,执行须要谨慎。在新增变更中,在Code Review环节,对代码有 “可灰度、可监控、可回滚” (稳定性三板斧)要求,即确保代码有应急预案,在线上代码呈现危险时,疾速回滚。 ...

November 26, 2021 · 1 min · jiezi

关于客户端:工作六年-我终于学会了这项技能-可惜晚了

在程序员的生涯中,无论是在跳槽还是降职的时候都有遇到各种各样被考查的状况,随着软件开发工作资格一直积攒,这些考查要求和条件逐步变高。 不会单纯用工作年限来判断候选人是否合格,因为毕竟写三年 CRUD 业务和做三年零碎研发的工作经验和工作品质是大不相同的,这样会存在不小的问题,尤其是在招聘高级开发工程师时。而算法题面试也一样,跳槽必刷题,曾经很难分辨候选人是当场想进去的,还是刷题记住的答案。传统的八股文、算法、前沿技术源码等常识内容曾经不再陈腐,而一些"软技能"像零碎设计面试将会考查环节中较为重要的一环。 为了能让面试更无效,采纳零碎设计题来考查高级开发工程师就是很好的抉择。因为零碎设计题覆盖面广,不像算法题那样在网上到处流传,候选人想刷也没法刷,只能拼技术。 这些软技能并不会间接考查所理解到的某个知识点或者是某一片知识点所连接起来的局部知识面,而是去查看一位工程师在整体层面和了解利用程序设计上的功底是否深厚;这类问题不像算法题那样在网上到处流传,想刷题也没法刷,只能拼技术。尽管不会牵扯到某个知识点,比方某个控件是如何实现并说出外部实现原理,然而会让谈下这个控件的长处有哪些或如何在原有的根底上进行改良; 这样能让整个考查环节变得更加无效,面试官就能精确地判断出候选人的技术实力,同时从而给到适合的待遇和定级。 在面试零碎设计题时,又该怎么评估候选人呢?对此,大家能够参考下图的总结。 在本文中,我将分享客户端中对于如果被问及到如何设计一个零碎的设计问题该如何进行答复,以及我是如何去解决零碎设计问题的秘诀。听起来很乏味?……keep reading 零碎设计问题波及的问题Eason去观看了很多不同类型的零碎设计教程视频,并做了一些对于这方面的总结,我感觉这些总结不仅仅是实用于零碎设计,也是另一种对本人技术栈的要求和查漏补缺的形式;上面是列举进去程序设计中必须要思考到的方面: 1.性能需要——首先须要定义利用的用例和一些性能。 2.非功能性要求——定义性能、教训和规模要求。 3.假如——定义问题的边界、任何规模束缚、特色等。 4.客户端-服务器通信——定义连贯选项,如 HTTP 申请、轮询、服务器端事件。 5.API 设计——定义所构建的性能端点。 6.数据模型——定义对象的数据模型字段。 7.利用流程(用例流程) ——定义并执行正在设计的性能的用户流程。 8.性能和工具——定义大家将如何收集数据和指标以查看应用程序的性能——内存利用率、CPU 利用率。 9.ADA — 确保大家定义了辅助性能以及应用程序的可拜访性。 10.国际化——随着应用程序在国内上的倒退,须要解决这款零碎利用的国内适配。 11.安全性——定义如何去爱护应用程序。 具体实现当初,让咱们通过一个例子来阐明设计一个零碎为何须要以上的这些构造:假如题目是被要求设计一个能展现出日常生活中的趣闻趣事App; 1. 性能需要 显示我四周乏味的中央列表。 向下滚动时加载更多 2. 非功能性需要 该列表应该加载十分快或具备低提早 滚动性能 数据异步加载且显示不应错位 列表不应抖动 3.假如 每天有多少沉闷用户?用户量级多少? API 是否可用?咱们须要明智地应用它们 4.客户端-服务器通信 惯例 HTTP 申请——这是客户端向服务器申请数据/资源的最通用用例场景。例如,获取提要中提要我的项目的列表个别会采纳哪些轮询形式? 定期轮询- 客户端能够一直向服务器发出请求以获取最新信息,但很多工夫服务器可能没有任何更新可提供。这不仅导致大部分工夫响应为空,而且在屡次设置 HTTP 连贯时会浪费资源。 长轮询——如果咱们有一个用例,咱们晓得内容不会从服务器频繁更新。在这种状况下,咱们能够做的就是应用长轮询。客户端会与服务器建设 HTTP 连贯,并放弃与服务器的连贯关上,以便服务器能够在有任何推送时向客户端推送更新。这样咱们就节俭了关上和敞开与服务器连贯的无用工作。并须要记住,这个长轮询连贯也可能超时,因而在这种状况下须要重新启动连贯。 WebSockets——在客户端和服务器都能够发动通信并且客户端和服务器之间一直来回的状况下,咱们能够应用 WebSockets。这个用例的一个很好的例子是聊天应用程序。 服务器端事件 (SSE) —客户端与服务器建设持久性和长期连贯。服务器应用此连贯发送数据。客户端处于侦听模式,因为只有服务器能力与客户端通信。如果客户端须要与服务器通信,则须要应用不同的协定。这个用例是说,咱们有一个社交媒体应用程序,列出了敌人的提要。然而当初,如果在咱们应用应用程序时刚刚产生了更新,客户端能够通过服务器端事件 (SSE) 协定轻松获取这些更新。 5. API 设计 ...

November 19, 2021 · 1 min · jiezi

关于客户端:客户端稳定性异常检测函数接口扫雷实践

作者:安晴 背景在过来的财年中,支付宝客户端高可用团队继续保障着支付宝客户端线上的高可用稳定性,但只有线上的应急快反能力是不够的,还须要线下提前发现客户端的稳定性危险建设危险开掘能力,欠缺整体的客户端高可用保障体系。通过总结过来1-2年的线上闪退问题能够发现其中NPE问题,RPC数据类型不匹配和config变更导致的闪退问题占比拟大,齐全能够在线下通过肯定机制提前发现。 基于此咱们对影响稳定性的因素进行分类,依照优先级构建了客户端的稳定性“扫雷”体系,次要包含函数接口扫雷,rpc&config&jsapi扫雷,scheme&播送&告诉扫雷,lottie&antmation&鸟巢模板变更扫雷等,通过这套扫雷能力建设咱们心愿能够完全避免线上呈现以上稳定性故障,同时以issue的模式推动研发优化代码,一直进步客户端稳定性,从事前的角度去欠缺客户端高可用保障体系。 技术计划依据教训剖析在客户端的函数调用中因为对函数参数查看不够导致的各类闪退问题占比在20%左右。现阶段计划Android端针对public和private的static动态接口,iOS端针对所有public接口进行Fuzz测试,通过对安装包扫描获取到全量的接口函数信息,而后对函数入参Fuzz制作各类异样场景,测试客户端的稳定性。 与传统的代码动态扫描相比,本计划在实在客户端上执行,贴近实在场景可制作实在的异样数据。整个技术实现计划包含以下几点: 代码扫描给出具体接口文件客户端无侵入实现批量接口反射执行能力参数Fuzz异样结构能力自动化测试用例管制真机执行用例和客户端异样复原issue上报剖析解决 代码函数扫描基于开源框架https://github.com/androguard...提供的apk扫描能力,可对某个支付宝版本apk生成全量的函数接口文件,目前Android和IOS端均可扫出大量函数接口数据进行测试。 客户端函数执行模块应用支付宝动静bundle能力,可在不侵入支付宝客户端的状况下进行函数接口稳定性测试,测试过程分为以下几个阶段: 1、扫描执行形式 因为全量的接口执行耗时过长,目前反对版本差别执行,通过计算大版本接口差别进行测试,可疾速失去版本间差别接口的稳定性数据,同时反对特定bundle代码扫描,可针对某个业务方的代码执行扫描检测,将函数接口稳定性检测能力输入到业务方。 2、参数fuzz异样结构 参数异样结构是一个十分业余的课题,目前曾经有大量基于机器学习的计划去结构异样数据笼罩更多代码逻辑,目前只是依照教训和业务语义结构了一组异样测试集,通过排列组合去遍历函数代码,目前1个接口依据参数个数可变异5-10个函数调用。从代码覆盖率后果看能够穿透60%接口的逻辑。 3、被测函数调用 被测函数调用过程次要解决的问题是执行效率,数据记录和断点续跑,执行效率依附接口分组和多线程执行解决,函数执行过程严格拆散保障后果可靠性。在接口扫雷测试过程中会呈现大量的闪退和ANR卡死状况导致测试进行,为了主动实现整个测试过程,须要具体记录测试过程数据,捕捉闪退时的要害数据保留并输入到客户端存储空间,为断点续跑提供要害数据。 4、闪退数据回放 执行完的测试用例无效保留到本地,程序反对间接回放异样用例验证代码修复后成果。 3 自动化脚本执行 自动化脚本的目标是唤起客户端执行测试用例,并且可能检测到客户端的异常情况,拉取测试数据确定断点地位,从新拉起客户端执行,保障整个测试流程能够齐全自动化的执行无需人工干预。 总结与瞻望支付宝客户端函数接口扫雷曾经执行10多个大版本,双端累计发现近千条无效问题,并且在工具的迭代中继续升高问题误报率,进步Fuzz变异能力笼罩更多危险问题。同时函数接口扫雷的问题曾经退出到客户端攻防演练中,作为实在闪退场景去攻打业务方。目前函数接口扫雷还有很多不欠缺的中央,像参数fuzz的智能变异结构,业务语义参数结构等还有待进步。将来还会持续补充客户端稳定性检测能力,力争将问题都在发版前解决。 关注咱们,每周 3 篇挪动技术实际&干货给你思考!

November 8, 2021 · 1 min · jiezi

关于客户端:Cube-技术解读-Cube-卡片技术栈详解

“ 此前,咱们上线了《Cube 技术解读》系列首篇文章《支付宝新一代动态化技术架构与选型综述》,本文为 Cube 系列第二篇文章,针对 Cube 卡片技术栈做了深刻解读,欢送大家关注。” 动静卡片的背景从Windows时代开始,应用程序图标就成了用户(流量)的主入口,并且始终继续到挪动端时代。图标即入口的形式,毛病是不直观,起码须要一次点击后能力接触到想要的信息。在此背景下,iOS零碎和局部Android零碎实现了把内容和服务前置的卡片,举个例子,如下图1所示,苹果左一屏的卡片承载天气&股市内容的展现。此外,鸿蒙零碎也提出了相似的卡片场景,作为某种流量入口。实际上,在利用外部的卡片作为内容展现以及服务入口的场景则更为广泛,图2和图3别离是支付宝首页和招行银行的理财页面,其中每个小矩形都是一个卡片。对于经营来说,卡片款式和内容能够随时配置,不必期待利用版本升级,是某种刚需。 Cube卡片概要Cube卡片是蚂蚁金服外部自研的一套跨平台动态化卡片解决方案,是服务于利用页面内的区域动态化技术,面向内容经营,帮忙产品技术进步开发效率和经营效率。每一个Cube卡片独立嵌在原生页面内的一个区域,区域内容通过卡片模版进行展现。卡片的定位大抵如下: 跨平台一致性 一套代码成果对齐动态化 界面构造&款式动态化业务逻辑动态化高性能 极致的性能极致的内存这里开展讲下高性能。Cube卡片谋求的是靠近native原生体验。咱们定义了两个维度: 一个是极致的性能。在Cube小程序能力的根底上,咱们去掉了一些简单的css能力,例如伪类伪元素、inline/block等,同时也对js的能力做了限度(Cube卡片应用quickjs作为脚本引擎)。此外,咱们还对quickjs做了一些优化,包含不限于离线atom编译优化,异步gc优化等。咱们也引入了wamr作为quickjs的“协处理器”,反对用户应用javascript和assemblyscript混合开发。这样用户能够用assemblyscript一些热点函数或者模块。 另一个维度是极致的内存。在信息瀑布场景有限下拉,Cube卡片的内存增长靠近Native卡片。咱们对卡片的能力做了比拟精密分级,通过在开发时配置,减小运行时的内存耗费。下图展现了一个简略卡片,如图所示Cube卡片的工程目录,以及钱包某个卡片的实在代码和运行成果。 Cube卡片的生产&工作流程 研发期本地开发Cube卡片配套独立的开发工具,反对卡片的编译、日志输入、实时预览等性能,vue作为以后开发模版的dsl语言,反对js、css编辑卡片款式。 卡片治理卡片本地开发实现后,通过卡片治理后盾将卡片编译产物上传公布,能够对卡片进行版本治理,卡片公布后就能够在客户端进行卡片的动静更新。 运行期为了不便端上业务接入Cube卡片,咱们引入了一个Cube卡片容器(CardSDK)的概念。CardSDK把一些和业务层/服务端分割严密的,且通用能力做了一些封装。例如咱们通过CubeCardSdk从服务端拉去卡片和业务数据。此外CardSDK也负责罕用的JSAPI、第三方组件的接入。这样Cube卡片可能更专一于卡片产品自身。 外围零碎架构Cube卡片的零碎架构次要包含JSEngine、CardEngine、RenderEngine和Platform几局部,绝大部分代码都是C++实现。 JSEngine次要负责卡片js逻辑执行和卡片数据变动监听,从而反对开发者在卡片外部写一些业务逻辑能力实现卡片内容和款式的动态变化。 因为卡片场景对性能要求较高,综合包大小和性能等方面思考,咱们抉择了quickjs作为咱们的js根底引擎库,同时实现了一个十分小的js响应式框架(JSFM),用来反对卡片内的逻辑代码能力。 CardEngine次要负责卡片数据的解析和绑定、卡片逻辑渲染、构建DOM指令、JSAPI治理、JSBinding、Native事件通信等。 卡片DOM树的初始化构建过程,咱们并没有把它放在js运行时,而是在卡片实例初始化链路中间接通过C++进行指令生成和树构建,一方面是为了放弃js框架更小更快,另一方面C++的运行效率更高。 RenderEngine后端渲染底座,负责卡片布局计算、款式解析、Layer计算、自绘制组件、同层渲染、光栅化上屏等过程,以及手势、动效等交互成果。 Platform平台相干接口,包含原子view封装、Canvas API、三方组件扩大协定、动画api等。 线程模型和数据模型线程模型Cube卡片生命周期内的次要线程包含业务线程和引擎线程,业务线程是卡片数据的初始化阶段由业务发动执行,是卡片生命周期的beforeCreate阶段。引擎线程是所有卡片生命周期运行阶段的共有线程,次要包含Bridge线程、Render线程、Paint线程和UI主线程。 Bridge线程js运行时线程,也是Dom节点数据查问和解决线程,因为基于Cube卡片小、快的定位,js逻辑只是卡片一个辅助能力,不具备过于简单业务逻辑能力,所以Bridge线程绝对较轻,并设计为单线程模式。 Render线程渲染相干数据计算线程,包含渲染树构建、节点层级计算、Layer分层绘制计算、手势数据计算以及渲染工作构建,Render过程次要波及树的递归计算过程,绝对渲染过程耗时很短, 设计为单线程模式。 Paint线程绘制线程,执行卡片节点分层绘制及光栅化工作。Paint线程并不是一个固定的线程,依据当前任务模型,Paint线程可能是主线程,也可能是一个线程池里的子线程;在同步渲染模式下,Paint线程间接是主线程;而在异步渲染模式下,通过一个线程池来实现Paint工作的并发渲染,进步渲染效率,例如在列表滑动场景。 UI主线程UI操作主线程,即为目前的平台线程,次要包含手势辨认、UI上屏和三方扩大组件的数据更新等。 除了以上波及的次要线程外,还有埋点和监控相干的playground后盾线程,整体优先级比拟低。整体的线程模型设计,最大限度缩小UI主线程压力,进步卡片并发渲染效率。但目前还有一些有余,包含UI线程切换频繁、Bridge线程越来越重等,前面会持续优化线程模型。 数据模型和线程模型对应的数据模型次要包含三棵树:NodeTree、RenderTree、LayerTree,初此之外,还存在一个长期的PaintTree; NodeTree卡片原始节点树,对应前端的Dom树,引擎会依据NodeTree做款式解析和布局计算; RenderTree渲染数据树,这是一颗变形树,很多状况下它的树层级构造和NodeTree是一样的,其实当初在设计定义引擎数据模型的时候,咱们探讨过到底要不要这棵树,有没有必要存在这样一颗和NodeTree层级一样的树,最终咱们还是保留了,起因是这棵树能够比拟灵便的调整树关系,如果把卡片分为布局阶段和渲染阶段,那么这颗树就是渲染阶段的源树。 事实证明咱们的决定是正确的, 咱们后续反对的zindex/static等能力,都是因为这棵树的存在能够在引擎层很好的去反对, 而不必在平台层去模仿实现这种层级变更能力从而导致很无限的场景反对,包含当前咱们做渲染快照技术也能够从这颗树去思考。 LayerTreeLayerTree树,顾名思义就是一个分层树,在RenderTree根底上对节点进行分层,同一层的节点在同一个渲染工作管线内做绘制光栅化,不同层之间互相独立,能够并发渲染。 PaintTreePaintTree是一个长期树,其实严格的说是一个拷贝树,是通过RenderTree拷贝一个子树,每次产生渲染时长期生成,当然也会做些节点优化解决,例如被齐全盖住的节点会被优化调,防止反复渲染。每一个layer上存在一个PaintTree,通过PaintTree进行节点绘制生成光栅化指令或位图。 高性能列表渲染对于列表内应用卡片的场景,次要思考的是卡顿影响,尤其是中低端机设备。Cube卡片反对异步渲染,所以在列表场景下能够很晦涩,同时因为反对多线程并发能力,能够多张卡片并发渲染,所以在异步渲染条件下也不会有显著的白屏成果。 Native技术优化咱们冀望卡片服务于页面内区域化内容动静展示和简略业务逻辑,更多的是面向挪动端开发者。即便咱们应用的卡片DSL语言形容是前端语言,咱们也心愿可能对CSS的应用做束缚、反对无限的CSS能力,但同时也心愿尽可能笼罩到一些开发者罕用的CSS能力。 所以咱们针对CSS能力做了一个专项工作,和前端团队技术同学一起做了Cube卡片CSS能力标准,对CSS能力做了束缚限度。即便如此,在Native渲染引擎下,想十分好的去反对这些能力,也是有很多艰难,包含zindex的反对、overflow等,因而咱们也基于一些依赖的平台能力也做了优化解决。 Layer容器咱们引入Layer容器概念,后面介绍数据模型时,提到了LayerTree,每一个Layer节点是一个独立的渲染容器,由平台View作为Layer容器来渲染其余虚构节点。如果依照惯例的做法是一个View对应一个渲染容器,咱们应用两个View组合为Layer容器(iOS应用CALayer),将内容层和逻辑层拆散,这样做的益处很多,例如layer节点的shadow绘制限度裁剪问题、内容层的画布切割优化等。 手势革新手势的优化革新次要为了解决平台零碎手势散发能力的限度,不论是Android平台还是iOS平台,系统对手势的散发解决都有一些限度,例如兄弟节点不能散发事件(iOS)、超过父节点区域无奈接管事件(影响overflow能力)等,所以须要对手势进行革新。 因为卡片渲染反对三方组件扩大,为了不影响扩大组件的事件响应,咱们基于Layer容器接管革新零碎手势行为,外部进行容器节点的手势散发治理,而对于存在三方组件混合渲染的场景,Layer容器和三方组件之间的手势散发放弃零碎行为。 光栅化Cube卡片渲染过程包含指令渲染和位图渲染两种渲染模式,这两种模式会在不同场景条件下切换,用来优化不同场景下性能,例如帧率和内存。位图渲染在Android上绝对比较复杂。默认是用Bitmap作为离线渲染的缓存,毛病是引入一次额定cpu/gpu内存拷贝并且无奈充分利用GPU资源,劣势是兼容性好。咱们尝试过应用textureview作为离线渲染缓冲,发现6.0以下设施存在重大的兼容性问题,而且不同设施之间的稳定性差异微小。 同时光栅化能力依赖平台零碎的Canvas API,有些高阶办法会波及硬件加速的限度,包含shadow api以及系统对glRender buffer的限度(Android平台),咱们也对大画布场景做了视图切割分段渲染来保障渲染性能。咱们共事也在着手用Skia Canvas api代替平台层的Canvas API。 同层渲染Cube卡片把三方组件当作独立一层layer独自进行数据更新,能够十分不便高效的接入扩大的三方组件。基于零碎的UI能力,使扩大组件在卡片内天生对立渲染。同时反对组件在不同卡片上的复用。在理论的业务场景中,同层渲染也带来了很多额定的问题。诸如地图/视频/动画等组件,个别会随同着较大的性能内存开销。这些开销对卡片的渲染会有负面影响,尤其在列表滚动时。对于地图/视频组件,咱们配合组件提供方case by case的解决问题,并且试图在卡片上线时设置卡点。对于动画组件,Cube继续的在扩大属性动画/帧动画能力,并且内置canvas能力。 ...

November 2, 2021 · 1 min · jiezi

关于客户端:端智能研发核心套件MNN-工作台深度剖析

作者:鹿尤 背景随着挪动互联网的疾速倒退,人工智能在挪动端上的利用越来越宽泛,端智能在图像识别、视频检测、数据计算等外围场景施展着重要作用。家喻户晓,Python 是算法进行端智能研发的首选语言,目前阿里巴巴内曾经建设了端智能的研发生态,蕴含 Python 虚拟机,系列的数据/视觉等运行时 Python 扩大库、Python 任务调度库,以及配套的工作公布零碎等等。 端智能的次要场景在数据计算、视觉内容了解这两个畛域,如果算法研发齐全在 PC 端的会简略的多,因为 PC 环境对于 Python 的研发有官网人造的反对,但在挪动端上,进行算法的部署、调试、验证,仍处在“刀耕火种”的时代,目前算法次要通过两种路径: 日志调试:齐全脱离运行时代码,通过在代码中插入日志,验证程序的运行逻辑和后果。PC 模型端侧环境:为了可能进行代码的断点调试,在 PC 侧搭建和装置端侧 Python 代码依赖的所有库,让 Python 程序能独立地运行和在和调试在 PC 端通用 IDE(如 PyCharm )上,数据输出则应用 Mock 的形式。通过打日志当然也能验证后果和定位问题,但一旦工程略微简单点,生产效率会非常低;而脱离挪动端环境,在 PC 侧独立运行,并不能反馈实在的运行环境(实时数据、运行性能等),无奈保障 PC 和端侧数据的一致性。 下面两种研发形式的次要问题是脱离了代码以后的运行环境,因此会产生调试信息的缺失。咱们晓得,每一种语言在开发者之间的风行都离不开配套的 IDE 调试工具,就像能够应用 Xcode 调试手机利用的 OC 代码一样,咱们须要一种能真正进行端智能算法在端侧部署和调试的工具和计划,来进步 AI 研发生产效率。 MNN 工作台MNN 是阿里巴巴开源的一个轻量级深度学习端侧推理引擎,外围解决深度神经网络模型在端侧推理运行问题,涵盖深度神经网络模型的优化、转换和推理。 开源地址:https://github.com/alibaba/MNN 除 MNN 引擎建设外,MNN 团队还深度参加了阿里外部的端AI利用实际。AI 利用门槛高、算法模型部署链路长是长期困扰咱们的问题。为了解决这两个问题,咱们将上百次实际中长期积攒的计划积淀为一站式 AI 研发范式 —— MNN 工作台。(www.mnn.zone 下载 MNN 工作台) MNN 工作台提供了端 AI 的研发范式,同时它也是一个对挪动端 Python 的通用调试编辑器,工作台不仅蕴含规范的端侧 Python 调试能力,还笼罩了阿里团体内端智能算法罕用的研发部署场景,是算法研发的效率工具。 ...

October 22, 2021 · 2 min · jiezi

关于客户端:FishLottie纯Dart如何实现一个高性能动画框架

作者:岑彧 背景Lottie是一个由Airbnb开源的横跨Android,iOS,Web等多端的一个动画计划,它以JSON的形式解决了开发者对简单动画实现的开发成本问题。 家喻户晓,闲鱼团队是比拟早在客户端侧抉择Flutter计划的技术团队,以后的闲鱼工程里也蕴含很多的Flutter界面。 而官网却始终没有提供Lottie-Flutter计划,以后也有一些第三方开发者提供了相干实现计划,基本上分为两种: 在Native端进行数据解析和渲染,再应用桥接的形式把渲染数据传输到Flutter端进行显示;在Flutter间接进行数据解析和应用Flutter绘图能力进行渲染显示。不过以后曾经开源的计划都存在一些问题,前者会在性能和显示存在一些问题,例如显示闪动白屏。后者在一些能力反对上存在一些性能缺点,例如不反对文本动画等。所以这始终是闲鱼团队乃至整个Flutter开发者个人的一个痛点。 我的项目架构闲鱼团队在调研了官网开源的lottie-android库之后,发现不论是数据解析能力,还是图形绘制能力。Flutter都提供了媲美Android的实现计划。所以参考lottie-android库实现了一个性能齐备,性能优异的纯Dart Package来提供Flutter上的Lottie动画反对。 如上图所示,整个我的项目由根底模块,接口层和控件层形成,而后反对矢量图形,填充描边等能力,详情可见Lottie反对能力,反对的能力也和lottie-android大致相同。 根底模块根底模块是与FlutterSDK提供的各种能力间接交互的中央,次要分为数据模型模块,动画绘制模块,数据解析模块和工具模块。首先对于整个框架来说,咱们首先能够拿到蕴含整个动画信息的JSON文件,所以须要先通过咱们的数据解析模块,把JSON文件外面蕴含的数据和信息解析并传递给数据模型模块,动画绘制模块负责拿到数据模型模块里的对象之后,调用Flutter提供的绘图能力来进行图形的绘制,而工具模块就次要负责获取屏幕信息,字符串解决,日志打印等工具类能力。 接口层接口层次要负责JSON数据的输出和动画绘制管制和调用,JSON信息通过数据解析模块最终会生成一个LottieComposition对象,这个对象里承载着整个JSON的动画信息。而后将这个对象传递给LottieDrawable,而后LottieDrawable会把对象传递传递给动画绘制模块,这样动画绘制模块就能够拿到动画信息,而后LottieDrawable再调用动画绘制模块来进行动画的绘制和刷新。 组件层组件层,这里次要是咱们继承Flutter的Widget实现的自定义组件,也是框架裸露给开发者的接口。开发者只须要新建一个LottieAnimationView,并把JSON文件的门路传递给它,反对Asset,Url,File三种模式,而后再把LottieAnimationView像一个一般Widget放到FlutterUI里,就能够实现一个简略的Lottie动画播放器了,当然也会裸露动画的管制接口以及控件的布局接口,只须要在新建LottieAnimationView的时候传入AnimationController,width,height,alignment等属性就能够实现对动画的进一步定制。 工作流程整体思路设计师在应用AE制作一段动画时,这个动画其实是由不同的图层组成的,AE提供了多个图层供设计师抉择,例如纯色层(通常当做背景)、形态层(绘制各种矢量图形)、文本层、图片层等,每一个图层都能够设置平移、旋转、放缩等变换。每个图层可能又蕴含多个元素,例如形态图层可能由多个根本矢量图形和钢笔门路图形组合成为一个具备设计感的图案,每个元素也可能蕴含本人的变换,除了根底变换之外,还能够设置色彩、形态这样的变换。以上图层和元素的动画就组成了一个残缺的动画。 如上图所示,咱们在AE中新建了一个纯色图层并填充上蓝色,而后新建了一个形态图层,并给这个形态图层增加了一个位移动画(即给形态图层1变换中的地位设置两个关键帧,并在关键帧上设置初始值和最终值),而后在形态图层中增加一个矩形门路和一个黄色的填充,而后同样的办法给矩形的大小和圆度设置动画,不过大小的关键帧为0秒到3秒,圆度的关键帧为3秒到5秒。所以就实现了一个矩形从左到右的同时,先变大而后变为圆形的动画。而后咱们通过Lottie提供的BodyMovin插件将以上的动画导出为JSON格局的文件,这个JSON文件里就蕴含了刚刚咱们的所有绘制和关键帧信息。 如上图所示,拿到这个JSON文件之后,咱们首先通过了数据解析把设计师在AE中制作的各种图层信息和动画信息都解析传递给一个LottieComposition对象,而后LottieDrawable获取到这个LottieComposition对象并调用底层的Canvas来进行图形的绘制,通过AnimationBuilder来进行进度的管制,进度发生变化时告诉Drawable进行重绘,绘制模块会获取到处于该进度时的各项属性值,而后就实现了动画的播放。 数据加载和显示咱们的组件层提供三种形式来进行JSON文件的获取,别离为asset(程序内置资源),url(网络资源),file(文件资源)。整个数据的加载和显示的流程图大抵如下所示,省略了底层绘制的细节: 这里以fromAsset形式举例,其余两种的加载形式和这种雷同,都对立由LottieCompositionFactory进行解决。这里咱们依据构造函数的不同将将加载形式分为三种,即asset,file和url。而后依据类型的不同调用LottieCompositionFactory里的不同加载办法将对应的内置资源、网络资源和文件资源加载进来并进行JSON文件的解析,而后最终的产物是一个LottieComposition对象,这个对象通过异步加载解析,在解析实现之后会告诉LottieAnimationView进行调用。咱们将加载实现的LottieComposition对象传递给咱们的绘制类,LottieDrawable会依据composition里的内容建设图层组,图层组里蕴含如形态,文本层等图层,和设计师在AE制作动画时创立的图层一一对应。每个图层有不同的绘制规定和办法,而后在LottieAnimationView里获取到零碎的Canvas传递给LottieDrawable并调用draw办法。这样就能够应用零碎画布绘制咱们本人的动画内容了。 动画绘制与播放实现了动画的加载与显示,咱们还须要让画面动起来。咱们通过AnimationBuilder的形式将AnimationController的value设置为LottieDrawable的progress,而后触发重绘使咱们的底层通过progress去获取以后进度的各项动画属性,这样就能够实现动画的成果了。时序图大抵如下所示: 咱们在LottieAnimationView里通过Flutter内置的AnimationController来管制动画,其中forward办法能够让Animation的progress从零开始减少,这也是咱们动画播放的开始。咱们一直调用setProgress函数将动画的进度设置到各层,最终达到KeyframeAnimation层,更新以后进度。进度扭转之后咱们须要告诉下层进行界面的重绘,最终将LottieDrawable里的一个isDirty的变量设为true。咱们在setProgress函数里,在实现进度设置之后咱们获取lottieDrawable的isDirty变量,如果这个变量为true,证实进度曾经更新,此时咱们调用重写的办法markNeedPaint(),这时候零碎会标记以后组件为须要更新的组件,Flutter会调用咱们重写的paint函数,对整个画面进行重绘。咱们和显示的流程一样,一层层进行绘制,在底层咱们会依据以后进度拿到KeyframeAnimation中对应的属性值,而后绘制进去的画面就会产生变动。通过这样一直的更新进度,而后从新获取以后进度对应的属性进行重绘,这样就能够实现动画的播放成果。 实现差别组件层Android端对于lottie-android来说,AnimationView和Drawable组成了整个组件层。AnimationView继承于ImageView,LottieDrawable继承于Drawable。整个工作的流程和下面所说的基本相同,开发者在xml文件中写入LottieAnimationView并设置JSON文件资源门路。而后AnimationView会发动数据获取和解析,解析实现之后把Composition对象传递给LottieDrawable,而后调用重写的draw办法来进行动画展现。 而后整个动画的播放,暂停,进度等管制都是通过开发者在代码中获取AnimationView的援用而后调用各种办法来实现的,然而其实真正的动画管制是由LottieDrawable里的ValueAnimator来管制的。在初始化LottieDrawable的同时也会创立ValueAnimator,它会产生一个0~1的插值,依据不同的插值来设置以后动画进度。LottieAnimationView里的暂停,播放等动画管制办法其实就是调用了这个ValueAnimator本身的对应办法来实现动画的管制。 Flutter端对于Flutter来说,并没有提供相似于ImageView和Drawable这样的组件让咱们继承和重写,咱们须要自定义一个Widget,自定义组件个别有三种形式: 原生组件的组合此处咱们显然不能应用这个办法,因为咱们须要获取零碎提供的画布来进行绘制。 实现CustomPainter在Flutter中,提供了一个自绘UI的接口CustomPainter,这个接口会提供一块2D画布Canvas,Canvas外部封装了一些根本绘制的API,开发者能够通过Canvas绘制各种自定义图形。咱们能够在重写的paint办法中获取到零碎的canvas,而后把这个canvas传递给咱们的LottieDrawable就能够实现动画的绘制了,而后在属性变动时导致画面须要刷新时在shouldRepaint返回true。然而这个计划会有一些问题无奈解决,咱们都晓得整个LottieAnimationView是作为一个Widget嵌入到FlutterUI当中的,咱们往往须要自定义动画播放区域(即LottieAnimationView)的大小,然而当开发者没有设定这个宽高值的时候或者是设定的尺寸大于父布局的尺寸的时候,咱们也要依据父布局对子布局的束缚来进行尺寸的适配和转换。然而在Flutter提供的这个CustomPainter中,没有裸露相应的接口让咱们获取到这个Widget所对应的RenderObject的constraint属性,也就无奈在开发者没有设置LottieAnimationView本身的width和height时依据父布局的束缚进行尺寸适配,所以放弃了这个实现计划。 自定义RenderObject咱们都晓得Flutter中的Widget只是一些轻量的款式配置信息,真正进行图形渲染的类是RenderObject。所以咱们天然也能够重写这个RenderObject类中的paint办法来获取零碎画布来进行绘制。这个计划会比上一个计划简单一些,咱们须要先定义一个继承于RenderBox的RenderLottie类,而后重写paint办法来把零碎的canvas传递给LottieDrawable,在须要进行刷新的中央调用markNeedPaint办法,就能够实现界面重绘。而后对于RenderObject来说,咱们能够获取到以后组件的constraint属性,也就是在开发者没有设置LottieAnimationView的尺寸或者是设置的尺寸超出复布局的时候咱们也能够自适应父布局的尺寸了。接下来须要定义一个继承于LeafRenderObjectWidget的组件LeafRenderLottie并重写createRenderObject办法并返回RenderLottie对象,重写updateRenderObject办法更新RenderLottie的进度等各项属性。这就实现了一个LottieWidget的实现。那咱们如何来进行动画的播放管制呢,咱们的LottieAnimationView是作为一个Widget嵌入到FlutterUI当中的,个别不会去获取它的援用来调用办法,那咱们就传入一个Flutter提供的AnimationController,而后在LottieAnimationView的build办法中返回一个AnimationBuilder并把AnimationController的进度值传给LeafRenderLottie,如果开发者没有传入AnimationController,咱们就提供一个默认的controller来进行简略的动画播放就能够了。要害代码如下所示: @override void paint(PaintingContext context, Offset offset) { if (_drawable == null) return; _drawable.draw(context.canvas, offset & size, fit: _fit, alignment: _alignment); }//RenderLottie的paint办法文本绘制Android端Android SDK里的Canvas提供了drawText的办法,能够应用画布间接绘制文本。Android实现计划如下: private void drawCharacter(String character, Paint paint, Canvas canvas) { if (paint.getColor() == Color.TRANSPARENT) { return; } if (paint.getStyle() == Paint.Style.STROKE && paint.getStrokeWidth() == 0) { return; } canvas.drawText(character, 0, character.length(), 0, 0, paint);}Flutter端然而在Flutter的Canvas里却没有这种办法,通过调研之后咱们发现Flutter提供了一个专门的TextPainter来进行文本的绘制。Flutter实现计划如下: ...

September 15, 2021 · 2 min · jiezi

关于客户端:Cube-技术解读-支付宝新一代动态化技术架构与选型综述

作者:入弦 “  如题目所述,笔者将继续更新《Cube 技术解读》系列文章。本文为Cube系列首篇文章,后续文章笔者会更侧重于技术详解,包含不限于:Cube卡片技术栈一篇,Cube小程序技术栈一篇,品质KITE&工具ACT一篇,性能优化一篇等,感激大家关注【阿里巴巴挪动技术】。” 背景支付宝客户端的动态化技术经验三个阶段。第一个阶段是native+web的hybrid模式,以webview为基石。第二阶段是实体组件模式,把html形容的组件和css款式信息映射到实体组件,并且把实体组件的事件传递到js层进行解决。第三阶段是实体组件+局部光栅化的hybrid模式,Cube是第三阶段的产物。 Cube起源于native页面的动态化诉求,产品状态体现于Cube卡片。随着小程序概念的呈现,Cube融入了支付宝小程序技术栈,产品状态为轻量级的支付宝小程序解决方案(绝对于应用浏览作为外围的web小程序)。这篇文章是一个综述,也是Cube系列的首篇文章。 技术选型&演进Cube的精确诞生工夫很难确定,大抵在16和17年之间,比RN(ReactNative)早晨一年。Cube诞生的次要起因是native页面的动态化诉求。钱包改版的频率高,给研发的压力很大,于是想到把高频改版的页面动态化。RN和Flutter的呈现,给了咱们一个很好的察看视角,即业界优良的科技公司是如何对待动态化这个话题以及它们的答案。起步阶段,咱们达成以下共识: 1、独立研发,自主可控。 咱们没有抉择基于RN的开源代码来实现咱们的动态化解决方案,也没有Flutter颁布源码后,切换到Flutter。这么做是思考到两点,第一点,技术栈的演进要把握在本人手里,不心愿被牵着鼻子走;第二点,开源我的项目的产品化老本并不低,前期的保护老本也不低; 2、服务业务,技术克服。首先,咱们没有足够能力和资源来撑持一个通用技术产品,服务于钱包业务是第一位的,简略说就是贴着业务走。其次,咱们回绝只求花里胡哨的技术demo,把外围能力做好,把产品成熟度做好,思考拿到业务价值是第一位的。 基于下面两个共识,咱们的技术选型如下: 抉择Javascript作为逻辑语言;抉择CSS的某个子集作为界面描述语言;自绘制(text/img/div/scroller)+ 原生组件 (input, animation,map, audio, video …)的混合渲染模式。阿里在前端的积攒比拟多,Cube抉择拥抱前端,采纳javascript和css是天然的事件。没有抉择v8,有两个判断:v8太重,内存速度和初始化速度都不现实;Cube的利用场景大概率不须要v8提供的jit能力。咱们额定引入了第三方的wamr ( https://github.com/bytecodeal... ) 作为webassemby引擎,且在编译构建工具上反对javacript和assemblyscript混合开发。Flutter开源后受到很多人的追捧,在很多文章和ppt上都看到了“Flutter齐全独立于平台层的渲染管线的劣势”表述,认为比RN映射实体组件的形式要高级很多。咱们不认为Flutter的渲染管线的性能优于操作系统的渲染管线,毕竟设施和操作系统能够垂直整合,利用一些设施个性。此外,是否自建渲染管线应该取决于业务诉求,而不应该自觉的谋求技术。 Cube的自建渲染管线仅限于自绘制标签,如前所述包含text/img/div/scroller,应用平台层的canvas api间接绘制在零碎的view上;如果某颗子树的标签都是自绘制标签,这颗子树会被“拍平”绘制在一个view上。自绘制标签以外的标签都是用映射原生组件的形式,并且封装了对立的实体组件映射些协定,提供给开发人员。目前Cube的业务场景次要集中在挪动端,也简略尝试过往linux/rtos平台移植。如果后续业务逐步扩大到linux/rtos,咱们会思考进一步欠缺自绘制,一个是把平台层的canvas api收敛到skia,另一个是内置layer compositor。 以后状态在承接业务的过程中,Cube大抵积淀了2种业务状态,别离是Cube卡片和Cube小程序。 Cube卡片的作用是给native页面赋予区域化的动静能力,进步业务迭代和经营效率。钱包接入的卡片也分为两类,一类是没有js能力的简略卡片,反对表达式和vif&vshow这类构建时管制DOM树的操作,谋求近似native的速度;另一类是具备js能力的简单卡片,用来反对一些简单的业务。Cube卡片在钱包曾经大规模利用,pv超过100亿,接入的场景参考截图,包含不限于首页、理财、我的等tab页,以及卡包、出行、领取后果页等二级页面。 Cube卡片的定位也是优先服务于钱包内的一二方业务,如果要想提供给三方开发者区域动态化的能力,咱们举荐小程序widget。此外,咱们正在着手把Cube卡片能力输入给中小型金融机构以及互联网公司。 Cube 是作为渲染引擎来引入小程序技术栈。小程序基础设施包含:容器,前端框架,渲染引擎,脚本引擎。容器能够了解成Appx/渲染引擎/脚本引擎之间的聚合层代码,提供包治理/JSAPI/平安管控/钱包外围服务等性能。挪动端上小程序默认的渲染引擎是UC,Cube小程序利用很无限。绝对于UC来说,Cube在包大小/启动速度/列表滑动流畅性/内存耗费上有一些劣势,然而劣势也非常明显——Cube反对的css能力有余,且Cube的开发工具不欠缺。基于此,从19年开始Cube投入了微小的人力来裁减css能力。Cube 是除浏览器内核外反对 CSS 较欠缺的渲染引擎,反对flex/inline/block等布局形式,伪类和伪元素,z-index以及绝对和相对定位层级治理。咱们也投入大量的精力试图建设相似devtools性能的工具。 这些致力肯定水平上改良了开发效力,但依然无奈满足前端同学的诉求。咱们逐步意识到,在浏览器性能不是次要瓶颈的场景下,前端开发者不大会承受浏览器的一个子集。于是,Cube小程序开始转向IoT场景,面向浏览器跑不起来,或者,体验极差的场景。Cube小程序作为某种利用开发栈,对试图建设三方开发者生态的客户是有肯定的吸引力。目前咱们次要的精力在电视大屏端,感兴趣的同学能够在天猫魔盒上体验Cube小程序,也能够在别的盒子以及智能电视上下载酷喵影视(https://acz.youku.com/wow/tva...!2~3~P~A )。 在卡片和小程序之间,实际上还有一个两头地带,即单页。这个页面能够是全屏,也能够是沉没在地面的半屏。Cube晚期尝试过h5单页,面向高频率营销场景。它的技术栈和小程序简直齐全一样,不同的是,h5单页没有容器的概念,从服务端下载到端上的不是小程序包而是嵌入了Cube构建产物的h5页面。h5单页接入过红包码业务和蚂蚁森林的二级页面,因为保护老本陆续下线。h5单页不胜利,并不意味着单页的需要不存在。近期摸索的小程序widget其实就属于单页的领域——咱们心愿widget可能让服务前置,承载肯定的交互逻辑,同时也限度它的能力,便于管控,适宜三方开发者。 技术架构Cube的外部有两个大的模块,一个是CubeKit,负责对接js引擎且封装平台差别,也包含了开发调试工具。另一个是CubeCore,是用c++代码实现的渲染外围逻辑。 对于Cube小程序,反对tinyApp-dsl子集,挪动端上应用jscore/v8作为js代码的执行引擎,IoT设施上应用quickjs;对于Cube卡片,反对基于精简vue的card-dsl。简略的卡片间接解析AST来渲染页面,简单卡片反对用户用js写一些简略逻辑,并且通过quckjs来驱动dom树的更新。 挪动端上,Cube和Web小程序共用一个容器代码。在IoT设施上,咱们继续投入人力到Appx和容器的垂直整合中。从目前的数据来看,IoT上的Cube小程序绝对挪动端的Cube小程序有不小的根底性能劣势。在电视端上Cube小程序的根底性能数据是:包体积5.5mb,内存耗费32mb(淘宝特价板小程序为例),冷启动耗时3~4s。随着垂直整合的深刻,将来Cube小程序的根底性能会进一步的改善。 质量体系这个话题,我放在技术架构里讲,起因是它自身是技术架构的一部分。做业务开发,测试人员能够遍历用户场景,有bug修bug。根底软件所承载的业务场景只是有限样本中很小的一部分,业务场景的回归没有问题,不可能保障引擎没有问题——最坏的状况是问题继续累积,直到某一天忽然暴发进去。这个时候再想解决问题,曾经积重难返。所以,根底软件的研发迫切需要某种提前裸露潜在问题的伎俩,这个伎俩不可能借助某个测试资源而是研发团队本人建设。 浏览器的WPT测试用例汇合给了一个很好的参考,Cube也建设了这样一套根底能力样本汇合以及配套的样本自动化执行框架KITE,投入到版本迭代&代码提交中。截止目前,咱们根本能做到单日粒度的主动巡检,撑持咱们在已有大量的业务场景的状况下对引擎做降级和重构,下图是引擎根底能力巡检工具的截图。 开发工具链这个话题,我也放在这里讲。Cube的间接客户不是用户,而是业务方的开发同学。在我的项目初期就要思考到工具这块,比方调试器的设计、预览容器、日志设计、低代码搭建平台等等。在扩大业务过程中,工具链某种程度上比Cube自身还要重要,毕竟它是客户的第一印象。咱们遇到过后期技术调研时,客户因为工具的不欠缺而回绝应用。业务接入后,除了能力上,业务方也会对工具提供各种要求(在帮助排查问题时也会发现新的工具需要),贯通产品的整个生命周期,也是维系客户粘性的重要工作。随着Cube大规模利用于业务后,咱们在工具上投入的精力逐步超过了性能&技术迭代自身。 回顾&将来布局回顾过去5年,Cube一路趔趔趄趄,中途差点夭折,能走到明天实属不易。从集体视角,Cube能活下来依赖“高低保持”。一方面,下面的决策者保持投入(19年及以前简直没有像样的业务价值);另一方面,一线的同学保持做一件事,没有技术谋求是不可能挺过途中的各种崎岖。咱们期待能Cube将来利用到物联网操作系统,毕竟利用开发技术栈是操作系统的核心技术之一。 Cube将来的布局持续保持“紧贴业务”和“技术克服”,把产品做好,把开发者服务好,把技术打磨好。重点的倒退方向如下: 鉴于Cube卡片能够运行在32MB内存/400Mhz的RTOS设施上,进一步摸索在物联网设施上的落地;推广Cube小程序在电视大屏端的利用和落地,摸索商业模式。如前所述,后续更新文章我会更偏重技术详解,针对卡片技术栈、小程序技术栈、品质KITE&工具ACT、性能优化等进行深刻解读与畅聊。如你对该系列文章感兴趣,亦或是对Cube感兴趣,你也能够继续关注咱们。 咱们下篇文章再见。 关注咱们,每周 3 篇挪动干货&实际给你思考!

September 13, 2021 · 1 min · jiezi

关于客户端:淘宝客户端诊断体系升级实战

作者:伝逸 淘宝作为一个航母级的利用,每天都有数亿的用户在应用。保障客户端的稳定性是咱们的首要指标。为此,咱们也提出了5-15-60的指标。即问题告警时,5分钟响应,15分钟定位,60分钟复原。然而现有的排查体系并不能很好的达到这个指标,剖析下来次要起因是: 监控阶段 通过Crash堆栈、异样信息进行聚合统计,不够精密精确,不够灵活;监控到异样后,端侧行为比拟繁多,只上报异样信息,无奈提供更多有用数据;手淘大部分问题都和线上变更无关,然而短少对变更品质的监控。排查阶段 监控上报的异样信息有余,依赖日志进行问题排查;Crash或异样时不会被动上传日志,须要手动捞取, 用户不在线获取不到日志;获取日志之后:短少分类,不足规范,日志芜杂,看不懂其余模块的日志;短少场景信息,无奈残缺的重现异样时用户的操作场景;短少整个生命周期相干的事件信息,无奈把握app的运行状况;各个模块上下游的日志信息无奈无效关联造成残缺链路;现有日志可视化工具性能较弱,无奈进步排查效率;问题排查靠人工剖析,效率低下,雷同问题短少积淀;各个系统间的数据短少关联,须要到多个平台获取数据。诊断体系降级思路针对以上现有问题,咱们从新设计了整个无线运维排查诊断体系的架构。在新的架构中,咱们引入了场景的概念。以往端上产生的异样都是一个个独立的事件,没有方法针对不同的异样做更精密的解决和数据收集。而引入场景概念后,一个场景可能是一个异样和多种条件的组合,针对不同的场景能够做配置,使得异样信息的收集更加丰盛,更加精准。 同时咱们从新定义了端侧异样数据,次要包含规范的Log日志数据、记录调用链路的Trace全链路数据、运行时相干的Metric指标数据以及产生异样时的现场快照数据。平台侧能够利用异样数据进行监控和告警。也能够对这些数据进行可视化的解析,针对业务的差别,平台提供了插件化的能力来解析数据。利用这些语义化后的信息,平台能够进行初步的问题诊断。 所以接下来要实现的指标是: 实现端侧场景化的监控运维;降级日志体系,实现LOG、TRACE、METRIC数据整合, 提供更加丰盛和精准的排查信息;实现高可用体系数据整合,提供面向问题排查的标准化接⼝和平台;插件化撑持平台赋能,业务自定义诊断插件,实现诊断体系平台间对接;平台根据诊断信息,给出诊断后果,能做到自动化、智能化;根据诊断后果给出解决方案或提出整改需要,造成从需要->研发->公布->监控->排查->诊断->修复->需要的闭环。日志体系降级目前剖析运行日志还是端侧排查问题的次要伎俩,后面也提到咱们的日志自身存在一些问题。所以咱们第一步是对日志体系进行了降级。(在此之前咱们欠缺了日志本身的根底能力,比方晋升写入性能、晋升日志压缩率、晋升上传成功率、建设日志的数据大盘等等) 为了进步日志的排查效率,咱们从日志内容着手,重新制定了端侧的规范日志协定。标准化的日志协定能够为咱们在平台侧进行日志可视化建设、自动化日志剖析提供帮忙。咱们从Log、Trace、Metric角度登程,按照手淘的理论状况将现有的日志分为以下几类: CodeLog: 兼容原有的日志,这些日志绝对芜杂;PageLog:记录端上页面跳转状况,排查问题时能够从页面维度对日志进行划分;EventLog:记录端侧各种事件,比方前后台切换、网络状态、配置变更、异样、点击事件等等;MetricLog: 记录端侧运行时的各种指标数据,比方内存、CPU、业务的各种指标数据;SpanLog: 全链路日志数据。把各个独立的点串联起来,定义对立的性能度量、异样检测的规范。基于OpenTrace的计划和服务端买通,造成端到端的全链路排查机制。[]() 有了这些数据之后。在平台侧配合日志可视化平台,能够对端侧的行为进行疾速回放。得益于日志的标准化,能够从平台侧对日志进行剖析,疾速展现存在异样的节点。 端侧诊断降级客户端是整个诊断体系的源头,所有的异样数据,运行信息都是由客户端上的各种工具进行收集上报的。目前端侧次要工具有: APM:收集端侧的各种运行、性能等信息,数据上报到服务端;TLOG:收集端侧运行时的日志信息,日志文件寄存在本地,须要时服务端下发指令进行捞取;UT:端侧埋点工具,很多业务异样信息都会通过UT进行上报告警;异样监控:这个以Crash SDK为代表,次要收集端侧的Crash信息,还有收集异样、用户舆情等相干的SDK;排查工具:内存检测、卡顿检测、白屏查看等工具。这些没有间接归类在APM中,因为这些工具是采样运行,有的甚至默认是敞开状态。能够看到端侧其实曾经有不少成熟的工具在应用,然而在排查问题时候常常发现数据缺失等问题,次要起因在于一方面在平台侧这些数据比拟扩散,没有一个对立的接口进行查问;另一方面客户端没有对这些工具的数据做整合,产生异样时这些工具间少有交互,造成了数据缺失。 为了解决这些问题,咱们在端侧引入了全新的诊断SDK和染色SDK。它们的次要性能有: 对接现有工具,收集端侧运行数据。把这些数据依照规范的日志协定写入到TLOG日志中;监听端侧的变更信息,生成变更的染色标识;监听端侧异样,异样产生时生成快照信息(包含运行信息、变更信息),上报服务端;场景化诊断数据上报,依据服务端配置的规定,在特定场景进行数据收集和上报;反对定向诊断,依据服务端下发的配置,调用对应的排查工具进行数据收集;反对实时日志上传,针对特定用户和设施进行在线调试。异样快照端侧的异样次要包含Crash、业务异样、舆情等。当异样产生时,上报的数据格式、内容、通道、平台都不一样。若想要减少一些数据,端侧和相应平台都须要进行革新。所以咱们对端侧异样监控相干的SDK进行了监听,对于业务提供了异样告诉接口。当诊断SDK接管到异样时,会利用以后收集到的运行信息生成一份快照数据。每个快照数据会有一个惟一的snapshotID。咱们只须要把这个ID传递给对应的SDK,这样对现有的SDK改变是最小的。 []() 快照数据随着端侧能力的增强也会越来越丰盛。收集到的快照信息会上传到诊断平台,平台之间能够利用snapshotID进行数据关联。对于诊断平台来说,能够依据快照信息、日志信息对异样进行剖析,给出初步的诊断后果。 变更监控手淘中大部分的问题是因为线上变更导致的。现有监控排查体系并没有专门对变更进行监控,次要还是依赖异样数量、异样趋势进行告警。这就有肯定的滞后性,导致很难在放量阶段疾速的发现问题。同时对于变更公布也没有一个对立的管控规范。所以咱们在端侧引入了染色SDK来收集变更数据,配合无线运维的变更诊断平台,对变更公布进行监控,做到可灰度、可观测、可回滚。 目前端侧的变更包含通用的配置变更(Orange平台)、AB试验变更和业务自定义的变更(试金石、平安、新奥创等等)。染色SDK在端侧和这些SDK进行对接,收集到端侧的变更数据后会生成对应的染色标识并上报。配合TLOG和诊断SDK,记录这些变更,并且在产生异样时给异样信息打标。平台侧也会和各个公布平台、高可用数据平台买通,依据客户端上报的数据进行公布决策。 染色标识变更其实是服务端向客户端下发数据,客户端应用的过程。 所以针对变更,咱们定义出: 变更类型:用来辨别变更的品种,比方配置变更(orange)、试验变更(ABTest)、业务A变更,业务B变更等等;配置类型:一个变更类型下可能有多个配置,比方orange变更,每个配置有一个namespace,用来辨别配置的类型;版本信息: 代表了一个配置的一次具体的公布。不肯定所有配置变更都有明确的版本信息,能够把某个公布或配置的惟一标识作为版本信息。比方每个orange配置公布都有一个version,每个ABTest公布都有一个publishID。有了这些信息咱们就能够给每一个变更生成一个惟一的染色标识。通过上报染色标识咱们能够统计出变更的失效数;通过在快照信息中携带染色标识,能够计算有变更标识的crash率、舆情数量;通过和高可用大盘数据进行比拟监控变更的品质。业务也能够在网络申请中带上染色标识,用于统计相干的接口是否存异样。 灰度定义 对于染色SDK咱们是心愿在灰度期间可观测,提前发现问题,不把变更导致的问题带到线上全量环境中,所以咱们把公布过程定位为三个阶段: 准备期:筹备变更数据,预发验证、提交审批、线上公布。这个阶段能够确定公布变更的类型、配置、版本;灰度期:对局部用户下发变更配置。咱们的染色监控也次要是在这个阶段运行,次要为上报染色标识和在异样快照中携带染色标识。平台侧在这个阶段对数据进行解决,生成灰度相干数据;全量期:当灰度达标之后,进入全量期。这个时候向所有满足条件的用户推送配置。数据上报管制因为手淘用户量太大,变更全量之后如果还持续上报失效数据,对服务端的压力会很大。同时异样信息中如果持续打标意义也不大了。所以咱们必须有一个机制来管控端侧的染色数据上报。 针对orange、ABTest这些通用的变更,咱们进行了独自适配,能够依据试验号、配置namespace进行黑白名单管制、采样管制或公布状态来管制。但对于自定义变更来说,可管制的条件千差万别,如果要做到精密的管制就必须要了解这个特定的变更。所以咱们定义了一些通用的条件:灰度标识、采样率、过期工夫来管制。 []() 这些信息是通过配置文件的模式下发到端上。端侧无需关注这些条件设置的逻辑,而是在平台侧进行设置,平台侧对接公布平台、高可用平台,并依据上报数据进行决策。目前次要还是根据配置中的灰度标识+超时工夫来决定是否上报。 公布门禁端侧上报了失效数、异样染色等数据之后,服务端就能够依据这些数据来监控变更。依据相干Crash数量、舆情数量、灰度工夫等来断定以后变更是否达到了全量公布的规范。 同时也会列出和这次变更相干的crash信息、舆情信息。辅助断定本次变更公布是否存在危险。 目前曾经有Orange配置变更、AB试验变更、详情、下单等业务接入。成果还是不错,曾经胜利躲避了4个线上故障。 场景化上报场景化数据上报,是诊断体系中的一个重要能力。以往都是当告警时咱们手动的从端上捞取相干数据进行排查,并且不同异样须要的数据也不雷同,常常要跨多个平台,进行屡次操作。这就导致数据获取滞后,整个排查时长不可控。所以在端侧具备了新的日志规范、异样快照收集、异样变更染色等根底能力后,咱们引入了场景化上报。 举个列子,依照现有的排查形式,当线上产生异样告警时,咱们个别先通过上报的异样信息来排查,但受限于现有信息不全,往往还须要通过拉取TLOG来做进一步排查,然而TLOG捞取又依赖用户在线,这就导致整个排查定位工夫十分长。 引入场景化概念之后,当平台检测到异样数量快要达到阈值的时候,能够主动生成一份场景规定配置,圈选一批用户下发到端上。当端上产生了雷同异样的时候,场景引擎会负责收集和上报所须要的数据,这样当告警达到阈值时平台上就曾经有足够的数据进行剖析定位。 场景规定场景引擎次要用来执行服务端下发的场景规定,而一个场景规定次要由三局部形成: 触发(Trigger) 能够是端上的一个行为或一个事件。绝对于以前只有在Crash、业务异样时才上报数据,咱们对异样触发机会进行了裁减。 解体异样用户截屏反馈网络异样 (mtop谬误、network谬误等)页面异样 (白屏、显示异样)零碎异样 (内存占用过高、 cpu占用过高、 耗电过快、发热、卡顿)业务异样 (业务错误码、逻辑谬误等)启动 (个别用于定向诊断)条件(Condition) 条件断定是整个场景化上传的外围。减少了场景条件之后,咱们能够从多个维度更精准的去划分异样类型。条件大抵上能够分为: 根底条件:从设施信息、用户信息、版本信息等维度去进行匹配和筛选;状态条件:次要包含网络状态、以后页面、内存水位等运行时的信息;特定条件:不同场景须要断定的条件是不同的。比方产生Carsh时,能够依据Exception类型、堆栈等信息进行匹配。而业务异样时可能依据错误码和网络谬误来进行匹配。行为(Action) 当端上某个规定被触发,并且满足设定的所有条件时,就会触发指定的行为,这样就能够依据不同的场景收集不同的数据。目前端侧次要行为有: 上传TLOG日志上传快照信息上传内存信息协同其余排查工具依据下发的参数进行数据收集上报场景下发 平台侧建设了一套新的场景治理平台,能够不便的配置各种场景和条件。并且也有规范的公布、审核、灰度流程。通过PUSH + PULL的形式,客户端能够及时的获取到场景规定。 ...

September 10, 2021 · 1 min · jiezi

关于客户端:中国大学-MOOC-Android-客户端开发提效之页面信息

本文次要形容了怎么样进步一个客户端开发排查和定位的效率,并且入手写了一个小工具的实际和思考,以及团队中其余合作者可能进步了定位问题效率,验证性能是否精确的效率。 作者/马杰 中国大学 MOOC 团队 编辑/刘振宇 一、前言中国大学 MOOC 是由网易与高教社携手推出的在线教育平台,承接教育部国家精品凋谢课程工作,向公众提供中国出名高校的 MOOC 课程。目前,无论是课程数量、品质还是社会影响力,中国大学 MOOC 都已成为寰球当先的中文慕课平台。 在日常的 Android 开发中,咱们常常会遇到以下的一些问题:测试、经营、产品同学跑过来说这个页面出了问题,连忙看下。这时候客户端开发同学就须要连忙定位到具体的某个页面。 据察看,大部分的状况下对于一个突发页面的问题定位,或者业务方想让开发者确认这个页面的业务逻辑的时候,客户端开发者,往往须要破费比拟长的工夫去给业务方回答。如果近期业务可能还能记得,然而客户端的页面比拟多,想要疾速定位到具体业务页面,那么就须要花更多的工夫去找相干的页面。 所以本文的想法是怎么疾速找到对应的页面,帮忙开发疾速的进入业务代码,疾速的回复业务方提出的问题。 二、计划施行在探讨计划的时候,咱们须要比照目前有哪些计划,比照之后再抉择一种更加无效的办法。 2.1 解决问题的罕用形式在 Android 开发中解决上述提供的问题,罕用的有以下 3 种形式: 关上 Android studio,靠着源码记忆,文案记忆去搜寻;应用 adb 命令来过滤以后的 activity;// windowsadb shell dumpsys window windows | Select-String -Pattern 'mCurrentFocus|mFocusedApp|mLastOpeningApp|mObscuringWindow' // Macadb shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp|mLastOpeningApp'全局搜寻要害文案。下面 3 种形式是可能解决问题,但从工夫效率上剖析,能够估算一下每一种形式大略须要多长时间: 第一种,依照集体教训,相熟我的项目代码的同学最快也要几十秒左右,慢的话 10 几分钟; 第二种,应用 adb 可能几秒就定位到页面,然而须要记住命令,或者提前设置命令快捷方式; 第三种,如果有很多雷同文案,须要多搜寻几遍,工夫也可能是10几秒到1分钟不等。 所有下面几种形式得出的工夫效率就是几秒到几分钟不等,而且根本都是须要代码或者 adb 的开发工具,依赖于开发环境。 既然须要花的工夫也不少,那么是不是应该做一个工具来晋升更快的定位速度,晋升定位效率呢? 2.2 更加高效的办法其实思路很简略,就是写一个开发的SDK,用来实时关注以后的页面信息。这个页面信息次要蕴含如下的信息: 以后 Activity 是哪个?以后的 Fragment 是哪个?以后页面的参数传递,如:intent 中的各种参数是什么?效果图如下: ...

June 30, 2021 · 2 min · jiezi

关于客户端:跨端技术都这么牛了客户端还有前途吗

阿里的大佬们曾总结,「一般来说,跨端技术有 4 类场景,别离是跨设施平台(跨Web端和手机端)、跨操作系统(如跨Android和iOS)、跨App以及跨渲染容器。」 而其中挪动端的跨平台技术始终以来都是很炽热的话题,在当初都不看好客户端技术天花板的背景下,客户端的将来仿佛在逐步朝着跨平台方向歪斜。 跨平台计划的劣势非常显著,对于开发者而言,能够做到一次开发,多端复用,一套代码就可能运行在不同设施上。这在很大水平上可能升高研发老本,同时可能在产品效力上做到疾速验证和疾速上线。 然而,挪动端的跨平台技术并不是仅仅思考一套代码可能运行在不同场景即可,还须要解决性能、动态性、研发效率以及一致性的问题。 性能: 如何通过前端和客户端的联合,实现更优的渲染性能以及交互性能; 动态性: 客户端怎么可能实现更低成本的发版、甚至不发版间接动静更新代码;研发效率:如何晋升不同客户端的动静调试之类的研发效率; 一致性: 如何实现一份代码的多端部署,并保障代码在多个客户端内展现状态的一致性以及兼容性问题。 现在,也曾经呈现了如WebView、React Native、Weex、Flutter、小程序等泛滥的挪动端跨平台框架。然而行业内统一呈现出群雄争霸的局势,并没有哪一种框架能够真正上说能给完满解决以上的问题,同时博众人之所长,一超多强。 1 WebViewWebView简略来说就是用来展现HTML的容器,用官网的话讲,叫做: A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.来给大家翻译一下: ...

June 2, 2021 · 1 min · jiezi

关于客户端:播放器性能优化之路

上面是播放的残缺流程: 播放器加载一个网络url,首先要进行网络申请,网络如何优化,波及到网络优化的方方面面。网络拉取回来数据之后,辨认一下以后视频的具体封装格局,这个能够正式流式视频,也能够是一般视频,优化的伎俩有点不同。辨认到具体的封装格局,依照封装格局的要求,开始解析封装格局,解析其中的音频流、视频流、字幕流等等。音频流要解码成音频原始数据,视频流要解码成视频原始数据。解码过程中留神音视频同步。音频播放,同时视频开始渲染。一、播放痛点依据咱们平时的开发实际,咱们总结出播放过程中常见的几类问题: 播放失败率高播放首帧慢播放卡顿播放器占用CPU、内存过高面对这些问题,咱们急切需要晓得两方面的数据: 怎么监控这些问题怎么解决这类问题这两个问题是有有递进关系的,“怎么监控这些问题”就是为了更好地“解决这类问题”。 二、监控伎俩咱们得悉下面的痛点,在产生这些问题时,咱们要收集相应的数据分析这类问题,不然开发者一头雾水,不晓得播放过程的数据信息,解决问题全靠运气。 1、网络加载监控播放视频首要的是网络加载,网络申请是一个简单的过程,全链路的点太多,将全链路的所有点收集起来,能够在播放器中加上网络的全链路监控: 这样咱们对网络的整体加载状况有了全面的把握,产生网络加载问题,也晓得是哪个点呈现了问题,剖析解决问题有了更加全的数据。 2、播放器全链路监控开篇就剖析了播放器的残缺流程,其实开发者也十分须要以后播放器的运行状态: 播放器的工作状态也能够拆解一下: 播放器产生状态异样,开发者能够明确获知播放器以后所处的状态。 每个状态都可能产生异样,产生异样都有具体的起因。利用播放器状态、播放器出错状况构建一个较为欠缺的播放监控体系。 3、播放器晦涩度监控播放卡顿,就是播放过程中产生loading,UI间接显示转圈,这对用户体验的侵害是微小的,用户在一直的吐槽中默默地卸载了咱们的app。卡顿的次要起因是网络情况不好,很小的一部分起因是源的问题。 卡顿的次数卡顿的时长卡登时的网速单次播放均匀卡顿次数和卡顿工夫是咱们掂量播放晦涩度的重要指标。 如果是源的问题,例如呈现播放视频的时候,进度条在走,然而画面不走,就是视频解码呈现问题,然而又没有出错,只是解码出的数据有问题。 解码出的数据有问题,有两种状况:原始数据就存在问题,这种状况下根本无奈优化;另一种状况下是解码线程异样。 MediaCodec产生异样解码线程异样谬误监控产生问题时零碎codec的具体状态,而后上报,便于剖析问题。 三、播放成功率优化播放失败的起因很多,应用播放器播放视频,最终都会在Player.onError回调中告诉开发者播放失败了,最多返回一个错误码,对应一个播放谬误。 总结而言,播放谬误次要分为上面几类: 网络加载谬误:网络申请产生问题,可能是网络申请的任何一个阶段。视频格式辨认谬误:不反对以后的格局,或者以后格局辨认出错。解码出错:不反对以后视频、音频解码导致的出错,或者零碎codec异样导致的问题。文件的IO异样:读取缓存文件产生问题。网络加载谬误个别要视状况而定,网络超时要做好超时重试机制。 视频格式反对应用ffmpeg能解决基本上所有的视频格式的辨认和解决工作。 MediaCodec解码受到手机硬件的制约,解码有时候会出错,出错能够切换到软解码。 四、播放性能优化1、复用链接:平时刷信息流视频的时候,其实很多视频的域名都是雷同的,这些链接都是能够复用的,网络建连的工夫须要30ms到200ms不等,如果能复用链接,这部分的工夫是能够节省下来的。 2、预加载一个播放器实例持有的数据十分大,player初始化的时候会初始化MediaCodec,MediaCodec对应底层的AVCodec,操作底层的/dev/codec-node,Android零碎规定了零碎最大持有的MediaCodec实例是16个,当然每个手机会有所不同,但总的来说不会有大的不同,就是MediaCodec的实例个数是无限的,不可能有限创立实例。 咱们预加载多个播放器实例的时候,就会创立多个codec实例,超过codec实现限度,零碎codec就无奈失常工作,极易造成OOM或者ANR。 咱们再平时解决问题的时候常常发现media.codec过程导致SystemServer卡死的。个别都是media.codec使用不当造成的。 那当初是否预加载不起播放器实例? 咱们预加载的目标是为了申请视频资源,其实只须要网路模块就能够的。 本地代理能够实现将播放器的网络模块独立进去: 播放器不间接和视频源服务器交互,两头通过本地代理层交互。本地代理层的网络加载模块是独立于播放器的,能够是播放器发动申请,也能够是其余的内部调用发动申请。最终setDataSource到播放器的url是一个http://127.0.0.1:port的申请,本地代理层会通过Socket向这个url中发送数据,播放器能够间接解析数据流。跟失常的播放流程齐全一样。这样咱们能够全局持有一个播放器实例:既能够做到预加载,而且能够解决播放器占用资源过多的问题。一举多得。 3、指定封装格局和解码格局:针对一些视频,咱们曾经明确晓得它们的封装格局和音视频的编解码格局,那咱们就能够提前告知播放器这些信息,播放器间接应用特定的封装格局去嗅探,间接起特定的解码器去解码。 例如信息流视频基本上都是MP4的封装格局,H264的视频编码,AAC的音频编码。 这样咱们能够节俭嗅探和MediaCodec检索的工夫。 4、MP4视频优化:MP4格局的视频解析进去如下: 其中moov中蕴含着MP4文件特有的属性数据,mdat是具体的音频和视频数据,MP4格局规定,只有解析出moov数据之后,能力解析出mdat中具体的音频和视频数据。 然而moov有时候在mdat之前,有工夫在mdat之后,如上,moov在mdat之前,那么咱们程序申请就没有问题。 然而如果moov在mdat之后,咱们程序申请就播放不进去,这时候须要起双IO缓冲加载:一个从头开始检索moov,另一个从开端检索moov,虽找到moov,就能够先解析出moov,而后解析mdat,播放视频了。 然而双IO毕竟比拟耗时,如果能在服务器提前将MP4视频的moov移到mdat之前,就能够晋升MP4的首帧。 5、流式视频优化:除了MP4视频,还有一些流式点播的视频,例如HLS格局,这些视频是一个一个的ts分片组成的。针对这些视频首帧的优化,我倡议间接将前几个ts的分片的数据压缩,例如针对一个3s的ts视频,原来的分辨率是1280 720,当初能够压缩到320 180的大小,数据量大大降低,这样首帧就能疾速加载下来。 6、边下边播咱们在播放视频的时候,最好能边播放边缓存到本地,这样我二次关上这个视频的时候,就能够不必申请了,间接复用本地的数据。 边下边播能够应用本地代理来实现。 7、video-id复用缓存咱们履行边下边播之后,二次关上能够复用缓存了,然而咱们是依据视频的url来复用的,当初信息流视频的url常常变动的,即便是同一个视频,半小时视频url就产生了变动。 那这样的复用效率不是很低吗? 还好当初信息流都是传过来一个video-id,这个video-id不会随着视频url变动而变动,只有是同一个视频,video-id不会变动,那咱们能够利用这个video-id来实现一次缓存,屡次复用。 五、其余播放体验优化倡议1、播放的时候呈现丢帧播放时丢帧次要是直播利用会呈现,当呈现播放丢帧的状况,服务器应该被动推流低码率的流,避免客户端呈现丢帧重大甚至卡住不走的状况。 2、播放画面呈现锯齿播放视频的时候呈现锯齿,这时候个别是两种状况: 视频清晰度较高,MediaCodec对搞清的视频解码反对地较弱,画面细腻感不强。倡议切换到软解码开始解码,软解码应用CPU解码,对细节的反对度很强,甚至8K的视频都能很好的解析。应用GLSurfaceView替换SurfaceView或者TextureView,GLSurfaceView通过OpenGL绘制纹理实现视频细节的细腻绘制,对视频画面反对成果很好。作者:Li Tianpeng

November 25, 2020 · 1 min · jiezi

Electron酷家乐客户端开发实践分享-入坑篇

作者:钟离,酷家乐PC客户端负责人原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-ru-keng-zhi-nan/酷家乐客户端:下载地址 https://www.kujiale.com/activity/136文章背景:在酷家乐客户端在V12改版成功后,我们积累了许多的宝贵的经验和最佳实践。前端社区里关于Electron知识相对较少,因此希望将这些内容以系列文章的形式分享出来。系列文章: 【Electron】酷家乐客户端开发实践分享 — 入坑篇【Electron】酷家乐客户端开发实践分享 — 软件自动更新【Electron】酷家乐客户端开发实践分享 — 浏览器启动客户端【Electron】酷家乐客户端开发实践分享 — 进程通信不定期更新...本文的初衷Electron所使用的技术栈(JavaScript、NodeJs、HTML、CSS)和web前端工程师完美契合。于是,越来越多的前端工程师,用Electron来开发桌面客户端的开发,我也是其中的一员。 虽然Electron技术栈对前端工程师比较友好,但是概念较多,和web前端开发还是有很大差别的,写个入坑指南希望能帮助读者快速上手Electron。 了解客户端首先抛出一个问题,web应用是桌面客户端吗?显然不是。那么,问题来了,什么样的软件才是桌面客户端呢?我们既然要从web前端转到客户端开发,那么就需要了解客户端,就像我们当初了解web应用一样。 回到刚刚那个问题,桌面客户端有两个重要的特点: 独立运行于操作系统上(桌面客户端只是PC,那么限定windows、MacOS、linux这几个主流PC操作系统)有自己的GUI(用户图形界面 graphical user interface)web应用有自己的GUI,必须在浏览器中执行,因此不是桌面客户端。 浏览器能直接运行在操作系统上,而且有自己的GUI,因此浏览器是桌面客户端。 Electron的能力在刚刚接触Electorn的时候,文档看的我是眼花缭乱。在某个加班的深夜,我不禁对天长叹:这个东西到底能干啥? 这东西能干啥?在经历了Electron的反复摩擦之后,我总结了Electron的几个关键能力: NodeJs全部能力,与操作系统交互 operation system 与操作系统相关的操作HTTP(s)、HTTP2process、child process 进程相关file system 文件系统...省略Electron提供的基础模块,主要与操作系统交互 app 主进程声明周期管理,控制MacOS任务栏dock、windows任务栏taskbarBrowserWindow 控制窗口,在MacOS和windows中窗口非常重要!screen 操作用户显示器globalShortcut 系统级别快捷键...省略Chromium提供的能力,主要提供GUI图形界面 解析HTML、CSS、JSajax请求cookie、localstorage...省略能力越大,责任越大如果用户安装了我们的桌面客户端,那么我们的软件在用户电脑上运行时,就有了非常大的权利,这是把双刃剑。 用户选择了我们的软件,我们也要对用户的电脑负责。能力越大,责任也就越大: 1.注意内存的占用,特别是chromium,简直是内存怪兽。可以通过os来获取用户电脑的配置,然后根据电脑的配置和可用资源,来制定合理的策略。 为软件增加代码签名,提升安全性谨慎操作注册表、用户敏感目录一旦被贴上【流氓软件】、【不好用】的标签,就很难再改变用户的印象了。 主进程、渲染进程 生命周期主进程:从整个应用启动到结束,该进程一直存在。主进程只有一个。 渲染进程:主进程可用创建/销毁渲染进程,因此渲染进程的生命周期是不固定的。渲染进程可以有多个。 执行环境 在Electron的API文档中,会在文档顶部标识该模块在哪个进程可用,例如:ipcRenderer 职责划分主进程渲染进程控制app的生命周期,为app注册关键事件解析HTML,渲染窗口内容阻止一些默认行为,例如webContents的跳转、download事件的默认行为等等(在渲染进程无法做到)处理窗口的交互逻辑创建BrowserWindow,也就是渲染进程。合理设置窗口的参数,控制窗口的生命周期(例如何时销毁窗口),决定BrowserWindow加载何处的HTML与主进程通信,实现高级交互窗口、前端资源我们回顾一下刚刚讲到的执行流程,其中有一个有趣的点,就是Electron的窗口会加载一个HTML来渲染窗口的内容。 HTML,以及HTML加载的css、js文件,统称为前端资源如果不加载HTML的,客户端还能用吗?不妨来试试 // main process const win1 = new BrowserWindow(); const win2 = new BrowserWindow();上述代码在主进程中执行,创建了两个窗口,窗口并没加载HTML文件。但是窗口却是真实存在的,带有系统标准的控制栏,可拖动,是货真价实的系统窗口! 我们可以发现,前端资源和窗口是分离的。由主进程创建的的窗口(BrowserWindow),既是一个系统原生窗口,同时也是一个加载&渲染前端资源的容器 窗口通常会通过file协议和http(s)协议来加载前端资源,接下来我们看看这两种方式的区别。 通过file协议加载HTML在Electron的官方入门例子中,就是通过file协议来加载HTML的 通过file协议加载HTML,无论有没有网络,都可以加载到HTML文件,这是file协议核心优势。缺点也比较明显: 如果页面资源要更新,那么只能通过发版来解决(如果你用webview,那么webview的内容就可以自动更新,不过webview也需要有网络才能加载)在file协议下,无法通过ajax来请求数据(协议不同),只能通过NodeJs的http(s)模块来发起网络请求通过http协议加载HTML通过http协议加载HTML,优点是可以随时通过web页面的部署,更新渲染进程的资源,并且在https协议下,你可以在页面中使用前端熟悉的ajax请求来获取数据。 当然,缺点也比较明显: ...

June 13, 2019 · 1 min · jiezi

用JS开发跨平台桌面应用从原理到实践

导读使用Electron开发客户端程序已经有一段时间了,整体感觉还是非常不错的,其中也遇到了一些坑点,本文是从【运行原理】到【实际应用】对Electron进行一次系统性的总结。【多图,长文预警~】 本文所有实例代码均在我的github electron-react上,结合代码阅读文章效果更佳。另外electron-react还可作为使用Electron + React + Mobx + Webpack 技术栈的脚手架工程。 一、桌面应用程序 桌面应用程序,又称为 GUI 程序(Graphical User Interface),但是和 GUI 程序也有一些区别。桌面应用程序 将 GUI 程序从GUI 具体为“桌面”,使冷冰冰的像块木头一样的电脑概念更具有 人性化,更生动和富有活力。我们电脑上使用的各种客户端程序都属于桌面应用程序,近年来WEB和移动端的兴起让桌面程序渐渐暗淡,但是在某些日常功能或者行业应用中桌面应用程序仍然是必不可少的。 传统的桌面应用开发方式,一般是下面两种: 1.1 原生开发直接将语言编译成可执行文件,直接调用系统API,完成UI绘制等。这类开发技术,有着较高的运行效率,但一般来说,开发速度较慢,技术要求较高,例如: 使用C++ / MFC开发Windows应用使用Objective-C开发MAC应用1.2 托管平台一开始就有本地开发和UI开发。一次编译后,得到中间文件,通过平台或虚机完成二次加载编译或解释运行。运行效率低于原生编译,但平台优化后,其效率也是比较可观的。就开发速度方面,比原生编译技术要快一些。例如: 使用C# / .NET Framework(只能开发Windows应用)Java / Swing不过,上面两种对前端开发人员太不友好了,基本是前端人员不会涉及的领域,但是在这个【大前端????】的时代,前端开发者正在想方设法涉足各个领域,使用WEB技术开发客户端的方式横空出世。 1.3 WEB开发使用WEB技术进行开发,利用浏览器引擎完成UI渲染,利用Node.js实现服务器端JS编程并可以调用系统API,可以把它想像成一个套了一个客户端外壳的WEB应用。 在界面上,WEB的强大生态为UI带来了无限可能,并且开发、维护成本相对较低,有WEB开发经验的前端开发者很容易上手进行开发。 本文就来着重介绍使用WEB技术开发客户端程序的技术之一【electron】 二、Electron Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的。 2.1 使用Electron开发的理由:1.使用具有强大生态的Web技术进行开发,开发成本低,可扩展性强,更炫酷的UI2.跨平台,一套代码可打包为Windows、Linux、Mac三套软件,且编译快速3.可直接在现有Web应用上进行扩展,提供浏览器不具备的能力4.你是一个前端????????~当然,我们也要认清它的缺点:性能比原生桌面应用要低,最终打包后的应用比原生应用大很多。 2.2 开发体验兼容性 虽然你还在用WEB技术进行开发,但是你不用再考虑兼容性问题了,你只需要关心你当前使用Electron的版本对应Chrome的版本,一般情况下它已经足够新来让你使用最新的API和语法了,你还可以手动升级Chrome版本。同样的,你也不用考虑不同浏览器带的样式和代码兼容问题。 Node环境 这可能是很多前端开发者曾经梦想过的功能,在WEB界面中使用Node.js提供的强大API,这意味着你在WEB页面直接可以操作文件,调用系统API,甚至操作数据库。当然,除了完整的 Node API,你还可以使用额外的几十万个npm模块。 跨域 你可以直接使用Node提供的request模块进行网络请求,这意味着你无需再被跨域所困扰。 强大的扩展性 借助node-ffi,为应用程序提供强大的扩展性(后面的章节会详细介绍)。 2.3 谁在用Electron 现在市面上已经有非常多的应用在使用Electron进行开发了,包括我们熟悉的VS Code客户端、GitHub客户端、Atom客户端等等。印象很深的,去年迅雷在发布迅雷X10.1时的文案: 从迅雷X 10.1版本开始,我们采用Electron软件框架完全重写了迅雷主界面。使用新框架的迅雷X可以完美支持2K、4K等高清显示屏,界面中的文字渲染也更加清晰锐利。从技术层面来说,新框架的界面绘制、事件处理等方面比老框架更加灵活高效,因此界面的流畅度也显著优于老框架的迅雷。至于具体提升有多大?您一试便知。你可以打开VS Code,点击【帮助】【切换开发人员工具】来调试VS Code客户端的界面。 三、Electron运行原理 ...

June 10, 2019 · 8 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

常见图片格式了解

前言作为一个客户端开发,对于图片格式一直没有一个清晰的了解,这里简单的罗列出各种图片格式的区别,文章中有部分是他人的引用,会在底部放上链接,望轻喷。概念了解有损压缩 & 无损压缩有损压缩(lossy compression):有损压缩算法是一种数据压缩方法,经过此方法压缩、解压的数据会与原始数据不同但是非常接近。它是与无损数据压缩相对的压缩方法。有损数据压缩又称破坏性资料压缩、有损压缩、有损压缩、不可逆压缩。其原理是借由将次要的信息数据舍弃,牺牲一些质量来减少数据量、提高压缩比。这种方法经常用于压缩多媒体数据(音频、视频、图片)。根据各种格式设计的不同,有损数据压缩都会有代间损失——每次压缩与解压文件都会带来渐进的质量下降。无损压缩(Lossless Compression):指数据经过压缩后,信息不受损失,还能完全恢复到压缩前的原样。无损压缩通常用于严格要求“经过压缩、解压缩的数据必须与原始数据一致”的场合。典型的例子包括文字文件、程序可执行文件、程序源代码。有些图片文件格式,例如PNG和GIF,使用的是无损压缩。索引色 & 直接色索引色:索引颜色是一种以有限的方式管理数字图像颜色的技术,以节省计算机内存和文件存储,同时加速显示刷新和文件传输。即用一个数字来代表(索引)一种颜色,在存储图片的时候,存储一个数字的组合,同时存储数字到图片颜色的映射。这种方式只能存储有限种颜色,通常是256种颜色,对应到计算机系统中,使用一个字节的数字来索引一种颜色。索引色常见有1位(即黑白),8位(即灰阶/256色),16位(即高彩),24位(即真彩),30/36/48位(即全彩),更多详细参考该百科。 直接色:使用四个数字来代表一种颜色,这四个数字分别代表这个颜色中红色、绿色、蓝色以及透明度(即rgba)。现在流行的显示设备可以在这四个维度分别支持256种变化,所以直接色可以表示2的32次方种颜色。当然并非所有的直接色都支持这么多种,为压缩空间使用,有可能只有表达红、绿、蓝的三个数字,每个数字也可能不支持256种变化之多。位图 & 矢量图:位图:位图[bitmap],也叫做点阵图,栅格图像,像素图,简单的说,就是最小单位由像素构成的图,缩放会失真。构成位图的最小单位是像素,位图就是由像素阵列的排列来实现其显示效果的,每个像素有自己的颜色信息,在对位图图像进行编辑操作的时候,可操作的对象是每个像素,我们可以改变图像的色相、饱和度、明度,从而改变图像的显示效果。举个例子来说,位图图像就好比在巨大的沙盘上画好的画,当你从远处看的时候,画面细腻多彩,但是当你靠的非常近的时候,你就能看到组成画面的每粒沙子以及每个沙粒单纯的不可变化颜色。矢量图:矢量图[vector],也叫做向量图,简单的说,就是缩放不失真的图像格式。矢量图是通过多个对象的组合生成的,对其中的每一个对象的纪录方式,都是以数学函数来实现的,也就是说,矢量图实际上并不是象位图那样记录画面上每一点的信息,而是纪录了元素形状及颜色的算法,当你打开一幅矢量图的时候,软件对图形相对应的函数进行运算,将运算结果[图形的形状和颜色]显示给你看。无论显示画面是大还是小,画面上的对象对应的算法是不变的,所以,即使对画面进行倍数相当大的缩放,其显示效果仍然相同[不失真]。举例来说,矢量图就好比画在质量非常好的橡胶膜上的图,不管对橡胶膜怎样的常宽等比成倍拉伸,画面依然清晰,不管你离得多么近去看,也不会看到图形的最小单位。图片类型BMPBMP取自位图BitMaP的缩写,也称为DIB(与设备无关的位图),是一种与显示器无关的位图数字图像文件格式。BMP同时支持索引色和直接色,但是其几乎没有压缩,所以通常图片非常的大,也导致了其几乎没有用武之地,现在除了在Windows操作系统中还比较常见之外,我们几乎看不到它。再加上浏览器的不支持,所以作为web开发,更加少于看到BMP。这里简单描述一下BMP解析成二进制时的结构:位置含义bmp文件头(bmp file header)提供文件的格式、大小等信息位图信息头(bitmap information)提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息调色板(color palette)(如果有的话)如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表位图数据(bitmap data)则图片数据GIF全称Graphics Interchange Format,采用LZW压缩算法进行编码。是无损的、采用索引色的、点阵图。GIF是无损的,采用GIF格式保存图片不会降低图片质量。但得益于数据的压缩,GIF格式的图片,其文件大小要远小于BMP格式的图片。文件小,是GIF格式的优点,同时,GIF格式还具有支持动画以及透明的优点。但,GIF格式仅支持8bit的索引色,即在整个图片中,只能存在256种不同的颜色。简单介绍下GIF使用的LZW压缩算法,详细可参考该文章:LZW编码 (Encoding) 的核心思想其实比较简单,就是把出现过的字符串映射到记号上,这样就可能用较短的编码来表示长的字符串,实现压缩。 比如: 我们可以将ABCDEFG 转成 1 来代表, 这样数据就会减少很多。再加上,LZW编码是自解释 (self-explaining) 的,即映射字典不会写到压缩数据里,他是在解码的过程中还原出编码时用的字典。JPEGJPEG是有损的、采用直接色的、点阵图。JPEG也是一种针对照片影像而广泛使用的有损压缩标准方法。JPEG图片格式的设计目标,是在不影响人类可分辨的图片质量的前提下,尽可能的压缩文件大小。这意味着JPEG去掉了一部分图片的原始信息,也即是进行了有损压缩。JPEG的图片的优点,是采用了直接色,得益于更丰富的色彩,JPEG非常适合用来存储照片,用来表达更生动的图像效果,比如颜色渐变。JPEG的算法比较复杂, 如果有兴趣可以参考该文章 其大概分为三步:把数据分为“重要部分”和“不重要部分”滤掉不重要的部分保存PNG便携式网络图形(英语:Portable Network Graphics,缩写:PNG)是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性。PNG的开发目标是改善并取代GIF作为适合网络传输的格式而不需专利许可,所以被广泛应用于互联网及其他方面上。PNG-8PNG-8是无损的、使用索引色的、点阵图。PNG是一种比较新的图片格式,PNG-8是非常好的GIF格式替代者,在可能的情况下,应该尽可能的使用PNG-8而不是GIF,因为在相同的图片效果下,PNG-8具有更小的文件体积。除此之外,PNG-8还支持透明度的调节,而GIF并不支持。 现在,除非需要动画的支持,否则我们没有理由使用GIF而不是PNG-8。当然了,PNG-8本身也是支持动画的,只是浏览器支持得不好,不像GIF那样受到广泛的支持。PNG-24PNG-24是PNG的直接色版本。PNG-24是无损的、使用直接色的、点阵图。无损的、使用直接色的点阵图,听起来非常像BMP,是的,从显示效果上来看,PNG-24跟BMP没有不同。PNG-24的优点在于,它压缩了图片的数据,使得同样效果的图片,PNG-24格式的文件大小要比BMP小得多。当然,PNG24的图片还是要比JPEG、GIF、PNG-8大得多。虽然PNG-24的一个很大的目标,是替换JPEG的使用。但一般而言,PNG-24的文件大小是JPEG的五倍之多,而显示效果则通常只能获得一点点提升。所以,只有在你不在乎图片的文件体积,而想要最好的显示效果时,才应该使用PNG-24格式。另外,PNG-24跟PNG-8一样,是支持图片透明度的。PNG-32PNG32也是PNG的直接色版本。其表现与PNG-24差不多。三者的区别在于:PNG-32每个像素的深度为32bits,其中RGBA四个通道各占8bits。所谓的RGBA四个通道,就是红,绿,蓝,透明 这四种色值各自的大小,都用8bits来表示(0~255)。PNG-24的像素深度为24bits,其中RGB三个通道各占8bits。PNG-8则是使用8位的索引色。SVGSVG是很多种矢量图中的一种,它的特点是使用XML来描述图片。借助于前几年XML技术的流行,SVG也流行了很多。使用XML的优点是,任何时候你都可以把它当做一个文本文件来对待,也就是说,你可以非常方便的修改SVG图片,你所需要的只需要一个文本编辑器。如果你是一个前端开发,那你应该对其了解比较多。图片比较与场景应用类型优点缺点应用场景BMP无损压缩,图质最好,支持索引色和直接色文件过大目前仅存于WINDOWS系统GIF无损压缩,支持动画及透明仅支持256种颜色,画质差需要动画的需求JPEG文件小有损压缩,画质损失不考虑过好画质且需响应速度较快, 如大背景图PNG-8无损压缩, 支持透明画质中等应用于大多数中小图且要求画质比较好的需求SVG支持放大缩小而不影响画质编写麻烦,性能差多应用于ICON之类一图胜前言待补充总结了解各种图片的格式,有助于我们与设计同事的沟通,与大家共勉。引用 & 参考图片格式那么多,哪种更适合你?

March 14, 2019 · 1 min · jiezi

支付宝工程师创造出了一个可以“拷贝”支付宝的神器

摘要: “拷贝”支付宝,新版mPaaS的魔法开启了!mPaaS是源于支付宝的移动开发平台,从最初的金融级移动开发平台,逐渐演进成集开发、测试、发布、分析、运营于一体的 App 全生命周期管理平台,服务了广发银行、12306、上海地铁等标杆级客户,帮助客户完成技术升级与业务增长。“拷贝”支付宝?呵,别逗了,这不可能。但支付宝工程师们真的把这种“不可能”变成了可能。1月4日,在上海举行的蚂蚁金服ATEC城市峰会上,新一代的移动开发平台mPaaS(mobile Platform-as-a-Service)3.0正式上线。新版本围绕移动场景完成了全面智能化升级,形成分析、营销、预测、多媒体等四大 AI 能力矩阵。此外,mPaaS 3.0版本提供了一套完备的H5/小程序应用开发、运维、分析功能,并提供底层小程序业务接口扩展能力,开发者可以利用mPaaS 小程序框架自主的开放业务接口。“新版本以智能技术助力客户构建自己的超级 App,并可以基于自有 App 做技术开放,构建超级 App生态,企业可以拥有等同于支付宝的能力,包括技术、生态、业务等”,蚂蚁金服金融科技产品技术总监杨冰介绍。mPaaS的演进之路正式介绍全新一代的mPaaS之前,我们先来回顾一下这个神奇平台的发展历程。2015年,金融行业风口已至。顺应趋势、助推行业整体进化,蚂蚁金服提出互联网助推器计划,发布蚂蚁金融云。支付宝从担保支付到国民App的过程中,沉淀了大量的技术实践。但如何将支付宝多年沉淀的技术在金融行业落地,这成了当时的一个挑战。2016年上半年,蚂蚁金服副CTO胡喜拍板,秉承“技术成熟一个,开放一个”的大原则,用轻量级的方式让蚂蚁的金融科技能力落地开花,因此首选mPaaS,并将其率先实施于蚂蚁的自有业务——网商银行,取得了非常有成效的结果。随后,mPaaS在信美保险和天弘基金也进行了落地。最开始的时候,mPaaS初期主要支持内部业务,所以并没有做多租户模式,而是采用的独占的模式,让用户去买机器,在公有云上,用户购买了服务后只能自己用。但支付宝工程师要以云的方式来完成这个动作,使其成为一个资源池。mPaaS的演进开始了。支付宝工程师最先做的是,先将mPaaS组件化、共享化,即用户可以自行挑选适合自己需求的组件,而无需整体采购全套方案。紧接着,mPaaS推出了一些热点的创新功能,比如热修复、离线包等。所以,在2016年11月的时候,mPaaS推出了一个更新的版本。如果说之前的mPaaS主要落地与支付宝内部的业务;那么此时的mPaaS已经具备了对外商业化的雏形,已经是一个正式的商业化版本。与此同时,mPaaS迎来了发展过程中一个非常重要的客户——12306。基于mPaaS的底盘技术,支付宝工程师对12306做了一个大的升级,并取得了非常明显的效果。新版12306 App无论是在流畅度,还是用户体验的方面,都取得了很好的反馈。为此,铁道部还专门给mPaaS团队发了感谢信,对支付宝团队的专业精神,还有技术深度都进行了高度的赞扬。12306项目的大获成功,不但解决了实际的痛点,也坚定了支付宝技术团队的做移动技术开放的决心。要知道,这个项目是10多个人的团队在不到2个月的时间内完成的,而且平稳顺利地经受住了当年的春运亿级用户的考验,是支付宝技术在相同体量 App 中的第一次成功复制。支付宝工程师们马不停蹄,立志要解决金融行业的痛点。此时,mPaaS的第一个金融客户广发银行出现了。彼时,广发银行研发中心总经理李怀根计划对旗下的App进行优化升级,其中最主要的是进行性能优化,即App的启动速度较慢,他希望立即将其解决。支付宝工程师用了一周左右的时间,设计了一个POC(Proof of Concept),就把广发银行App首页的代码“搬到”了mPaaS上,并在行里进行了现场对比 Demo, 对比发现精彩的平均启动速度从几秒缩短到不到1秒。最终广发银行在众多厂商中选择了与源于支付宝的 mPaaS 合作。新版发现精彩上线后,李怀根更在2018年云栖大会中总结到:“发现精彩 3.0 平均启动速度达到了0.52秒,iOS 闪退率不到万分之一,发现精彩整体体验大幅度提升!”这是mPaaS在高并发,大体量金融级 App 中的又一次复制。“拷贝”支付宝,新版mPaaS的魔法mPaaS是源于支付宝的移动开发平台,现在已经演进成集开发,测试,发布,分析,运营于一体的App全生命周期管理平台。1月4号发布的mPaaS 3.0 融入了人工智能小程序技术,进行了全面的升级。魔法一:全面升级的智能化能力mPaaS 3.0全面向智能化进行升级,推出了智能投放,舆情分析,多媒体,预测4款智能化组件。同时智能预测圈人的功能,与之前发布的消息推送服务(MPS),发布服务(MDS)进行了全面整合,例如可以通过智能预测来判断接下来一周即将流失的客户,然后针对这部分用户发布一个消息 (通过MPS服务),或者通过智能投放服务发放一个营销活动(通过智能投放服务MCDP),促使这些用户能够继续留存下来。所以这次升级不仅仅是推出了智能化组件,更是整个平台的智能化升级。同时 mPaaS 3.0 解决了智能化能力落地难的问题, mPaaS 提供数据采集,智能引擎,智能化场景一体化解决方案,开箱即用,无需做任何系统对接,数据对接。同时,也提供了数据和系统的扩展能力,可以结合业务数据服务更多的场景。魔法二:通过小程序构建自主的生态系统新版的mPaaS还提供mPaaS小程序功能,mPaaS小程序源于支付宝小程序,是支付宝小程序技术的全面开放,包含了小程序开发框架、IDE、发布服务、分析服务等完整能力闭环,让客户可以以小程序的方式开放业务接口,围绕自己的App构建小程序生态。同时,基于mPaaS小程序开发的业务可以在自有App、阿里系、mPaaS生态间投放、联通、共享,壮大客户自主的业务生态。魔法三:全新组件“真机云测”面向碎片化严重的安卓市场,新版的mPaaS还推出全新组件“真机云测”,帮助App在上线前完成全面、统一的测试方案,从而彻底验证App的兼容性、功能完善与性能稳定。 “真机云测”提供了包括机柜,测试框架,任务调度平台,测试效果评估一体化解决方案,可以有效的提高测试效率,降低测试成本,提高问题发现率。目前,mPaaS真机云测已在支付宝体系内完成 50w+自动化任务,用例执行400w余次,捕获闪退 5w+次。基于以上技术创新,新版的mPaaS让“拷贝”支付宝更加便捷。毫不夸张地说,通过蚂蚁金服的移动开发平台mPaaS,企业可以拥有等同于支付宝的能力,包括技术、生态、业务等。目前,全新一代的移动开发平台mPaaS已经在蚂蚁金服金融科技官网(https://tech.antfin.com/produ…)上对外开放。本文作者:平生栗子阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 11, 2019 · 1 min · jiezi

客户端单周发版下的多分支自动化管理与实践

背景目前,互联网产品呈现出高频优化迭代的趋势,需求方希望尽早地看到结果,并给予及时反馈,所以技术团队需要用“小步快跑”的姿势来做产品,尽早地交付新版本。基于以上背景,美团客户端研发平台适时地推行了单周发版的迭代策略。单周版本迭代的优点可以概括为三个方面:更快地验证产品创意是否符合预期,更灵活地上线节奏,更早地修复线上Bug。首先说一下美团平台的发版策略,主要变更点是由之前的每四周发一版改为每周都有发版。具体对比如下:(旧)三周迭代指的是2周开发+1周半测试,依赖固定的排期和测试时间,如果错过排期,则需要等待至少20天方可跟着下个版本迭代发布,线上验证产品效果的时间偏长。具体示例描述如下:(新)单周版本迭代指一周一发版,单周迭代版本排期、测试不再依赖固定时间节点,需求开发并测试完成,就可以搭乘最近一周的发版“小火车”,跟版发布直接上线。对于一般需求而言,这将会大大缩短迭代时间。业务方研发人员的痛点在之前按月发版的迭代节奏下,基本上所有的需求都属于串行开发,每个版本的开发流程比较固定。从“评审-开发-提测-灰度-上线”各个环节都处于一个固定的时间点来顺序执行,开发人力资源的协调方式也相对简单。全面推进单周发版之后,并不能把所有需求压缩到5天之内开发完成,而是会存在大量的并行开发的场景,之前的固定时间节点全部被打破,由固定周期变成了动态化调配,这给业务方的需求管理和研发人员人力管理都带来了指数式复杂度的提升。一旦进入并行开发,需求之间会产生冲突和依赖关系,版本代码也会随之产生冲突和依赖,这也大大提高了开发过程中的分支管理成本,如何规范化管理分支,降低分支冲突,把控代码质量,是本文接下来要讨论的重点。下面描述了几种典型的单周发版带来的问题:业务需求开发周期不固定,会存在大量的多版本、多需求并行开发。平台只提供了单周发版的基础策略,每5天发一版,业务方完成需求即可搭车发版。对于各业务方来说,需求开发往往并不是都能在5天内完成,一般需求在5~10天左右,在之前串行发版模式下这个问题其实也存在,但并不突出,在单周发版的前提下,都要面临跨版本开发,意味着多个版本多个需求会同步并行开发,这给业务方的分支管理带来了极大的挑战。业务方架构复杂,仓库依赖多,单周发版分支创建合并维护成本大。交通业务线涉及火车票、国内机票、国际机票多条业务线,代码仓库除了业务线的独立仓库,还有交通首页,交通公共仓库,RN仓库等多个仓库,Android端6个Git仓库,iOS端5个仓库,RN5个仓库,共计16个Git仓库。多仓库频繁发版分支代码存在安全风险,容易漏合代码,冲掉线上代码。业务线自身的公共基础库需求变动频繁。也需要具备单周发版的能力。例如交通公共基础仓库,承载了很多交通业务线的UI功能组件,这些公共组件的业务变化频繁,公共基础仓库变化的同时,可能会对使用组件的业务产生影响,需要同步的升级发版。美团平台的策略是公共服务组件每四个小版本统一升级一次,但对业务方自身组件这种策略限制较大,还是需要公共组件也要具备随时发版的能力。单周发版分支管理解决方案针对上面提出的问题,交通客户端团队通过技术培训、流程优化、关键点检测、自动化处理等方式保证分支代码的安全。技术培训主要是加强技术人员分支管理的基本知识,Git的正确使用方法,这里不做过多描述。本文主要讨论关键点检测,以及如何进行自动化的分支管理。在实施单周发版之前,业务方代码仓库只有两个分支,Develop分支,即开发分支;Stage分支,即发版分支;开发流程基本在串行开发模式,每个版本10天开发,8天测试,然后进入下一版本的开发。这种方式只能适用于节奏固定的长周期开发方式,对于多版本并行开发来说,有点力不从心,显然已经不能承载当前更灵活的发版节奏。针对这些问题,我们推出了如下分支管理结构。总的来说,就是废除之前作为开发分支的Develop分支,建立对应的Release发版分支,每个版本打包从Release分支直接打包;同时Stage分支不再承担打包职责,而是作为一个主干分支实时同步所有已发布上线的功能,Stage分支更像一个“母体”,孵化出Release分支和其它Feature分支;当Release分支测试通过、并且发版上线之后,再合入到Stage分支,此时所有正在开发中的其它分支都需要同步Stage分支的最新代码,保证下一个即将发布的版本的功能代码的完整性。上面的流程描述可能有些复杂,下面是简化的流程图,每个版本都有自己的生命周期:从Stage创建一个Release分支;进入开发阶段;如果Stage分支有变化,同步Stage分支;打包测试;测试通过,发布线上;发布线上之后,合回Stage分支。为了适应单周发版,新的开发流程也引入了很多新的挑战。例如下图所示的一个Branch分支中涉及的六个关键点:创建分支、合入主干、主干变化通知、Merge主干变化、检测主干同步、未同步拦截,除了这些还要考虑多仓库同步操作的问题,还有热修复版本的管理方式的问题。能否把这些关键节点合理的规范和把控起来,是我们当前应对多分支并行开发的主要难点:如何更高效的解决这些问题呢?结合我们当前使用的工具:Git + Atlassian Stash 代码仓库管理工具;Jenkins Build打包工具;大象(美团内部通讯工具)内网通信工具。目前这三个开发工具已经非常成熟、稳定,并且接口丰富易于扩展,我们需要配合当前单周发版的分支管理模式,利用这些工具来进行扩展开发,正所谓“要站在巨人的肩膀上”。创建分支Release分支如何创建,何时创建,分支命名规范定义如何约束?创建Release分支,本质上是从Stage新建一个分支,当前提前一个周期创建新的发版分支,例如在10.1.1版本Release后,创建10.1.3版本的分支,此时10.1.2版本处于开发测试阶段。业务方所有的分支命名和平台的分支命名保持一致,采用Release/x.x.x的格式,但同时需要升级成为即将发布的Release版本号,例如10.1.3。现在交通业务线多达十几个仓库,每个仓库每周都要操作一次需要耗费大量人力。之前每个分支的创建都是通过Stash或者手工创建,能不能自动化批处理的创建呢?答案是肯定的。对此,我们采用了Jenkins的方式,需要建立一个Jenkins Job, 基本原理就是通过命令行的方式进行Branch的创建,然后通过Job管理,批处理建立所有仓库的Release分支,这样就收敛了Branch的创建,即采用统一的命名规范,并且同时升级版本号。这就解决了创建分支的难点,实现了自动化创建分支,并且实现了规范化命名。如何知道Stage分支有变化,变化后需要做什么,不做会怎样?一个好的开发习惯,就是每天写码之前都同步主分支,但是还是需要一个机制来确保同步。这里做了三个措施来确保各个分支和Stage是保持同步的:一个通知,一个警告,一次拦截。这三个步骤解决主干变化通知、检测主干同步、未同步拦截的问题。一个通知:具体路径如下,建立了一个内部推送公众账号和一个Jenkins监听Job,当所有交通业务仓库Stage分支有代码改动,通知所有对应的开发人员,该仓库有代码变化,请及时合入。 一次警告:本地开发过程中,每次提交代码到远端仓库时,会触发一个Stage分支代码同步检测的脚本,如果发现未同步,会通过内部通讯系统通知提交者存在未同步主分支问题。但这里目前并不做强制拦截,保证分支代码开发的整体流畅性。最终拦截:在开发分支打包的过程中强制拦截,最终功能代码上线还是需要打包操作。在打包操作时统一收口,由于之前打包也是在Jenkins上来完成的,这里我们也是通过在打包Jenkins上接入了分支合并检测的插件,这样每次打包时会再次检测和主分支的同步情况。如果发现未同步则打包失败,确保每次发版都包含当前线上已有代码的功能,防止新版本丢失功能。如何合并分支,如何保证漏合?和上面提到的第一个如何创建分支的问题类似,通过Jenkins Job来进行批量操作,可以一键创建所有分支的Pull Request;在每个版本发版之前,统一进行一次打包,合入美团的主分支,防止多个仓库有漏合的情况。公共基础库版本策略?公共基础和业务分支保持同样的策略,通过批处理脚本同时建立分支,合并分支,监听分支变化,需要注意的是,每次版本升级,公共基础库也需要同步的打包,并且强制业务库升级。不然,如果基础仓库存在接口变动,有的业务升级了,有的业务没升级,最终会导致无法合入主分支,进而无法打出App包。热修复的版本管理策略?热修复确实是一种非常规的处理方式。从原则上来讲,热修复需要在对应的Release分支上进行修改,然后把修改合入Stage分支,同时需要同步到其它正在开发的分支。实际的处理需要根据具体情况来分析,是否需要对线上多个版本热修复。如果多版本都要修复就不能再合入Stage分支,否则会导致Stage分支冲突,如果把Stage分支合入需要热修复的其它分支,会把线上当前最新代码带入历史旧版本,会导致版本兼容性问题。最终执行起来可能还是对热修复版本进行单独处理,不一定要进行Stage主分支的同步,热修复的版本管理成本会比较高,更多的需要人工介入。未来展望目前整体的分支发版流程已经基本完成,现在已经稳定运行了10个小版本,同时没有出现因为分支管理问题而引发的线上问题。不过,当前整体流程的自动化程度还有待提高,每周需要人工去触发,很多代码合并过程中的冲突问题还需要人工去解决。未来我们希望能够自动化地根据平台的版本号自动创建分支,并且对于一些简单的冲突问题拥有自动化的处理能力。随着单周发版的不断成熟,未来对于持续交付能力也将不断提升,发版节奏可以不限于单周,一周两版或是更快的发版节奏也成为一种新的可能。作者介绍王坤,美团客户端开发工程师,2016年加入美团,目前主要负责大交通业务的客户端架构、版本管理及相关工作。

January 11, 2019 · 1 min · jiezi

Android组件化方案及组件消息总线modular-event实战

背景组件化作为Android客户端技术的一个重要分支,近年来一直是业界积极探索和实践的方向。美团内部各个Android开发团队也在尝试和实践不同的组件化方案,并且在组件化通信框架上也有很多高质量的产出。最近,我们团队对美团零售收银和美团轻收银两款Android App进行了组件化改造。本文主要介绍我们的组件化方案,希望对从事Android组件化开发的同学能有所启发。为什么要组件化近年来,为什么这么多团队要进行组件化实践呢?组件化究竟能给我们的工程、代码带来什么好处?我们认为组件化能够带来两个最大的好处:提高组件复用性可能有些人会觉得,提高复用性很简单,直接把需要复用的代码做成Android Module,打包AAR并上传代码仓库,那么这部分功能就能被方便地引入和使用。但是我们觉得仅仅这样是不够的,上传仓库的AAR库是否方便被复用,需要组件化的规则来约束,这样才能提高复用的便捷性。降低组件间的耦合我们需要通过组件化的规则把代码拆分成不同的模块,模块要做到高内聚、低耦合。模块间也不能直接调用,这需要组件化通信框架的支持。降低了组件间的耦合性可以带来两点直接的好处:第一,代码更便于维护;第二,降低了模块的Bug率。组件化之前的状态我们的目标是要对团队的两款App(美团零售收银、美团轻收银)进行组件化重构,那么这里先简单地介绍一下这两款应用的架构。总的来说,这两款应用的构架比较相似,主工程Module依赖Business Module,Business Module是各种业务功能的集合,Business Module依赖Service Module,Service Module依赖Platform Module,Service Module和Platform Module都对上层提供服务,有所不同的是Platform Module提供的服务更为基础,主要包括一些工具Utils和界面Widget,而Service Module提供各种功能服务,如KNB、位置服务、网络接口调用等。这样的话,Business Module就变得非常臃肿和繁杂,各种业务模块相互调用,耦合性很强,改业务代码时容易“牵一发而动全身”,即使改一小块业务代码,可能要连带修改很多相关的地方,不仅在代码层面不利于进行维护,而且对一个业务的修改很容易造成其他业务产生Bug。组件化方案调研为了得到最适合我们业态和构架的组件化方案,我们调研了业界开源的一些组件化方案和公司内部其他团队的组件化方案,在此做个总结。开源组件化方案调研我们调研了业界一些主流的开源组件化方案。CC号称业界首个支持渐进式组件化改造的Android组件化开源框架。无论页面跳转还是组件间调用,都采用CC统一的组件调用方式完成。DDComponentForAndroid得到的方案采用路由 + 接口下沉的方式,所有接口下沉到base中,组件中实现接口并在IApplicationLike中添加代码注册到Router中。ModularizationArchitecture组件间调用需指定同步实现还是异步实现,调用组件时统一拿到RouterResponse作为返回值,同步调用的时候用RouterResponse.getData()来获取结果,异步调用获取时需要自己维护线程。ARouter阿里推出的路由引擎,是一个路由框架,并不是完整的组件化方案,可作为组件化架构的通信引擎。聚美Router聚美的路由引擎,在此基础上也有聚美的组件化实践方案,基本思想是采用路由 + 接口下沉的方式实现组件化。美团其他团队组件化方案调研美团收银ComponentCenter美团收银的组件化方案支持接口调用和消息总线两种方式,接口调用的方式需要构建CCPData,然后调用ComponentCenter.call,最后在统一的Callback中进行处理。消息总线方式也需要构建CCPData,最后调用ComponentCenter.sendEvent发送。美团收银的业务组件都打包成AAR上传至仓库,组件间存在相互依赖,这样导致mainapp引用这些组件时需要小心地exclude一些重复依赖。在我们的组件化方案中,我们采用了一种巧妙的方法来解决这个问题。美团App ServiceLoader美团App的组件化方案采用ServiceLoader的形式,这是一种典型的接口调用组件通信方式。用注解定义服务,获取服务时取得一个接口的List,判断这个List是否为空,如果不为空,则获取其中一个接口调用。WMRouter美团外卖团队开发的一款Android路由框架,基于组件化的设计思路。主要提供路由、ServiceLoader两大功能。之前美团技术博客也发表过一篇WMRouter的介绍:《WMRouter:美团外卖Android开源路由框架》。WMRouter提供了实现组件化的两大基础设施框架:路由和组件间接口调用。支持和文档也很充分,可以考虑作为我们团队实现组件化的基础设施。组件化方案组件化基础框架在前期的调研工作中,我们发现外卖团队的WMRouter是一个不错的选择。首先,WMRouter提供了路由+ServiceLoader两大组件间通信功能,其次,WMRouter架构清晰,扩展性比较好,并且文档和支持也比较完备。所以我们决定了使用WMRouter作为组件化基础设施框架之一。然而,直接使用WMRouter有两个问题:我们的项目已经在使用一个路由框架,如果使用WMRouter,需要把之前使用的路由框架改成WMRouter路由框架。WMRouter没有消息总线框架,我们调研的其他项目也没有适合我们项目的消息总线框架,因此我们需要开发一个能够满足我们需求的消息总线框架,这部分会在后面详细描述。组件化分层结构在参考了不同的组件化方案之后,我们采用了如下分层结构:App壳工程:负责管理各个业务组件和打包APK,没有具体的业务功能。业务组件层:根据不同的业务构成独立的业务组件,其中每个业务组件包含一个Export Module和Implement Module。功能组件层:对上层提供基础功能服务,如登录服务、打印服务、日志服务等。组件基础设施:包括WMRouter,提供页面路由服务和ServiceLoader接口调用服务,以及后面会介绍的组件消息总线框架:modular-event。整体架构如下图所示:业务组件拆分我们调研其他组件化方案的时候,发现很多组件方案都是把一个业务模块拆分成一个独立的业务组件,也就是拆分成一个独立的Module。而在我们的方案中,每个业务组件都拆分成了一个Export Module和Implement Module,为什么要这样做呢?避免循环依赖如果采用一个业务组件一个Module的方式,如果Module A需要调用Module B提供的接口,那么Module A就需要依赖Module。同时,如果Module B需要调用Module A的接口,那么Module B就需要依赖Module A。此时就会形成一个循环依赖,这是不允许的。也许有些读者会说,这个好解决:可以把Module A和Module B要依赖的接口放到另一个Module中去,然后让Module A和Module B都去依赖这个Module就可以了。这确实是一个解决办法,并且有些项目组在使用这种把接口下沉的方法。但是我们希望一个组件的接口,是由这个组件自己提供,而不是放在一个更加下沉的接口里面,所以我们采用了把每个业务组件都拆分成了一个Export Module和Implement Module。这样的话,如果Module A需要调用Module B提供的接口,同时Module B需要调用Module A的接口,只需要Module A依赖Module B Export,Module B依赖Module A Export就可以了。业务组件完全平等在使用单Module方案的组件化方案中,这些业务组件其实不是完全平等,有些被依赖的组件在层级上要更下沉一些。但是采用Export Module+Implement Module的方案,所有业务组件在层级上完全平等。功能划分更加清晰每个业务组件都划分成了Export Module+Implement Module的模式,这个时候每个Module的功能划分也更加清晰。Export Module主要定义组件需要对外暴露的部分,主要包含:对外暴露的接口,这些接口用WMRouter的ServiceLoader进行调用。对外暴露的事件,这些事件利用消息总线框架modular-event进行订阅和分发。组件的Router Path,组件化之前的工程虽然也使用了Router框架,但是所有Router Path都是定义在了一个下沉Module的公有Class中。这样导致的问题是,无论哪个模块添加/删除页面,或是修改路由,都需要去修改这个公有的Class。设想如果组件化拆分之后,某个组件新增了页面,还要去一个外部的Java文件中新增路由,这显然难以接受,也不符合组件化内聚的目标。因此,我们把每个组件的Router Path放在组件的Export Module中,既可以暴露给其他组件,也可以做到每个组件管理自己的Router Path,不会出现所有组件去修改一个Java文件的窘境。Implement Module是组件实现的部分,主要包含:页面相关的Activity、Fragment,并且用WMRouter的注解定义路由。Export Module中对外暴露的接口的实现。其他的业务逻辑。组件化消息总线框架modular-event前文提到的实现组件化基础设施框架中,我们用外卖团队的WMRouter实现页面路由和组件间接口调用,但是却没有消息总线的基础框架,因此,我们自己开发了一个组件化消息总线框架modular-event。为什么需要消息总线框架之前,我们开发过一个基于LiveData的消息总线框架:LiveDataBus,也在美团技术博客上发表过一篇文章来介绍这个框架:《Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus》。关于消息总线的使用,总是伴随着很多争论。有些人觉得消息总线很好用,有些人觉得消息总线容易被滥用。既然已经有了ServiceLoader这种组件间接口调用的框架,为什么还需要消息总线这种方式呢?主要有两个理由:更进一步的解耦基于接口调用的ServiceLoader框架的确实现了解耦,但是消息总线能够实现更彻底的解耦。接口调用的方式调用方需要依赖这个接口并且知道哪个组件实现了这个接口。消息总线方式发送者只需要发送一个消息,根本不用关心是否有人订阅这个消息,这样发送者根本不需要了解其他组件的情况,和其他组件的耦合也就越少。多对多的通信基于接口的方式只能进行一对一的调用,基于消息总线的方式能够提供多对多的通信。消息总线的优点和缺点总的来说,消息总线最大的优点就是解耦,因此很适合组件化这种需要对组件间进行彻底解耦的场景。然而,消息总线被很多人诟病的重要原因,也确实是因为消息总线容易被滥用。消息总线容易被滥用一般体现在几个场景:消息难以溯源有时候我们在阅读代码的过程中,找到一个订阅消息的地方,想要看看是谁发送了这个消息,这个时候往往只能通过查找消息的方式去“溯源”。导致我们在阅读代码,梳理逻辑的过程不太连贯,有种被割裂的感觉。消息发送比较随意,没有强制的约束消息总线在发送消息的时候一般没有强制的约束。无论是EventBus、RxBus或是LiveDataBus,在发送消息的时候既没有对消息进行检查,也没有对发送调用进行约束。这种不规范性在特定的时刻,甚至会带来灾难性的后果。比如订阅方订阅了一个名为login_success的消息,编写发送消息的是一个比较随意的程序员,没有把这个消息定义成全局变量,而是定义了一个临时变量String发送这个消息。不幸的是,他把消息名称login_success拼写成了login_seccess。这样的话,订阅方永远接收不到登录成功的消息,而且这个错误也很难被发现。组件化消息总线的设计目标消息由组件自己定义以前我们在使用消息总线时,喜欢把所有的消息都定义到一个公共的Java文件里面。但是组件化如果也采用这种方案的话,一旦某个组件的消息发生变动,都会去修改这个Java文件。所以我们希望由组件自己来定义和维护消息定义文件。区分不同组件定义的同名消息如果消息由组件定义和维护,那么有可能不同组件定义了重名的消息,消息总线框架需要能够区分这种消息。解决前文提到的消息总线的缺点解决消息总线消息难以溯源和消息发送没有约束的问题。基于LiveData的消息总线之前的博文《Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus》详细阐述了如何基于LiveData构建消息总线。组件化消息总线框架modular-event同样会基于LiveData构建。使用LiveData构建消息总线有很多优点:使用LiveData构建消息总线具有生命周期感知能力,使用者不需要调用反注册,相比EventBus和RxBus使用更为方便,并且没有内存泄漏风险。使用普通消息总线,如果回调的时候Activity处于Stop状态,这个时候进行弹Dialog一类的操作就会引起崩溃。使用LiveData构建消息总线完全没有这个风险。组件消息总线modular-event的实现解决不同组件定义了重名消息的问题其实这个问题还是比较好解决的,实现的方式就是采用两级HashMap的方式解决。第一级HashMap的构建以ModuleName作为Key,第二级HashMap作为Value;第二级HashMap以消息名称EventName作为Key,LiveData作为Value。查找的时候先用组件名称ModuleName在第一级HashMap中查找,如果找到则用消息名EventName在第二级HashName中查找。整个结构如下图所示:对消息总线的约束我们希望消息总线框架有以下约束:只能订阅和发送在组件中预定义的消息。换句话说,使用者不能发送和订阅临时消息。消息的类型需要在定义的时候指定。定义消息的时候需要指定属于哪个组件。如何实现这些约束在消息定义文件上使用注解,定义消息的类型和消息所属Module。定义注解处理器,在编译期间收集消息的相关信息。在编译器根据消息的信息生成调用时需要的interface,用接口约束消息发送和订阅。运行时构建基于两级HashMap的LiveData存储结构。运行时采用interface+动态代理的方式实现真正的消息订阅和发送。整个流程如下图所示:消息总线modular-event的结构modular-event-base:定义Anotation及其他基本类型modular-event-core:modular-event核心实现modular-event-compiler:注解处理器modular-event-plugin:Gradle PluginAnotation@ModuleEvents:消息定义@Retention(RetentionPolicy.SOURCE)@Target(ElementType.TYPE)public @interface ModuleEvents { String module() default “”;}@EventType:消息类型@Retention(RetentionPolicy.SOURCE)@Target(ElementType.FIELD)public @interface EventType { Class value();}消息定义通过@ModuleEvents注解一个定义消息的Java类,如果@ModuleEvents指定了属性module,那么这个module的值就是这个消息所属的Module,如果没有指定属性module,则会把定义消息的Java类所在的包的包名作为消息所属的Module。在这个消息定义java类中定义的消息都是public static final String类型。可以通过@EventType指定消息的类型,@EventType支持java原生类型或自定义类型,如果没有用@EventType指定消息类型,那么消息的类型默认为Object,下面是一个消息定义的示例://可以指定module,若不指定,则使用包名作为module名@ModuleEvents()public class DemoEvents { //不指定消息类型,那么消息的类型默认为Object public static final String EVENT1 = “event1”; //指定消息类型为自定义Bean @EventType(TestEventBean.class) public static final String EVENT2 = “event2”; //指定消息类型为java原生类型 @EventType(String.class) public static final String EVENT3 = “event3”;}interface自动生成我们会在modular-event-compiler中处理这些注解,一个定义消息的Java类会生成一个接口,这个接口的命名是EventsDefineOf+消息定义类名,例如消息定义类的类名为DemoEvents,自动生成的接口就是EventsDefineOfDemoEvents。消息定义类中定义的每一个消息,都会转化成接口中的一个方法。使用者只能通过这些自动生成的接口使用消息总线。我们用这种巧妙的方式实现了对消息总线的约束。前文提到的那个消息定义示例DemoEvents.java会生成一个如下的接口类:package com.sankuai.erp.modularevent.generated.com.meituan.jeremy.module_b_export;public interface EventsDefineOfDemoEvents extends com.sankuai.erp.modularevent.base.IEventsDefine { com.sankuai.erp.modularevent.Observable<java.lang.Object> EVENT1(); com.sankuai.erp.modularevent.Observable<com.meituan.jeremy.module_b_export.TestEventBean> EVENT2( ); com.sankuai.erp.modularevent.Observable<java.lang.String> EVENT3();}关于接口类的自动生成,我们采用了square/javapoet来实现,网上介绍JavaPoet的文章很多,这里就不再累述。使用动态代理实现运行时调用有了自动生成的接口,就相当于有了一个壳,然而壳下面的所有逻辑,我们通过动态代理来实现,简单介绍一下代理模式和动态代理:代理模式:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。动态代理:代理类是在运行时生成的。也就是说Java编译完之后并没有实际的class文件,而是在运行时动态生成的类字节码,并加载到JVM中。在动态代理的InvocationHandler中实现查找逻辑:根据interface的typename得到ModuleName。调用的方法的methodname即为消息名。根据ModuleName和消息名找到相应的LiveData。完成后续订阅消息或者发送消息的流程。消息的订阅和发送可以用链式调用的方式编码:订阅消息ModularEventBus .get() .of(EventsDefineOfModuleBEvents.class) .EVENT1() .observe(this, new Observer<TestEventBean>() { @Override public void onChanged(@Nullable TestEventBean testEventBean) { Toast.makeText(MainActivity.this, “MainActivity收到自定义消息: " + testEventBean.getMsg(), Toast.LENGTH_SHORT).show(); } });发送消息ModularEventBus .get() .of(EventsDefineOfModuleBEvents.class) .EVENT1() .setValue(new TestEventBean(“aa”));订阅和发送的模式订阅消息的模式observe:生命周期感知,onDestroy的时候自动取消订阅。observeSticky:生命周期感知,onDestroy的时候自动取消订阅,Sticky模式。observeForever:需要手动取消订阅。observeStickyForever:需要手动取消订阅,Sticky模式。发送消息的模式setValue:主线程调用。postValue:后台线程调用。组件化总结本文介绍了美团行业收银研发组Android团队的组件化实践,以及业界首创强约束组件消息总线modular-event的原理和使用。我们团队很早之前就在探索组件化改造,前期有些方案在落地的时候遇到很多困难。我们也研究了很多开源的组件化方案,以及公司内部其他团队(美团App、美团外卖、美团收银等)的组件化方案,学习和借鉴了很多优秀的设计思想,当然也踩过不少的坑。我们逐渐意识到:任何一种组件化方案都有其适用场景,我们的组件化架构选择,应该更加面向业务,而不仅仅是面向技术本身。后期工作展望我们的组件化改造工作远远没有结束,未来可能会在以下几个方向继续进行深入的研究:组件管理:组件化改造之后,每个组件是个独立的工程,组件也会迭代开发,如何对这些组件进行版本化管理。组件重用:现在看起来对这些组件的重用是很方便的,只需要引入组件的库即可,但是如果一个新的项目到来,需求有些变化,我们应该怎样最大限度的重用这些组件。CI集成:如何更好的与CI集成。集成到脚手架:集成到脚手架,让新的项目从一开始就以组件化的模式进行开发。参考资料Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBusWMRouter:美团外卖Android开源路由框架美团外卖Android平台化架构演进实践作者简介海亮,美团高级工程师,2017年加入美团,目前主要负责美团轻收银、美团收银零售版等App的相关业务及模块开发工作。招聘美团餐饮生态诚招Android高级/资深工程师和技术专家,Base北京、成都,欢迎有兴趣的同学投递简历到chenyuxiang@meituan.com。 ...

December 24, 2018 · 1 min · jiezi