关于移动应用开发:YonBuilder移动开发平台-AVM框架-封装身份证号码虚拟输入键盘组件

AVM(Application-View-Model)前端组件化开发模式基于规范Web Components组件化思维,提供蕴含虚构DOM和Runtime的编程框架avm.js以及多端对立编译工具,齐全兼容Web Components规范,同时兼容Vue和React语法糖编写代码,编译工具将Vue和React相干语法糖编译转换为avm.js代码。 基于规范 Web Components 组件化思维,兼容 Vue / React 语法个性,通过一次编码,别离编译为 App、小程序代码,实现多端开发。 组件性能介绍身份证号码虚构输出键盘,可用以身份证号码输出时应用,可对输出的身份证号码进行验证,反对15位和18位。 示例展现 组件开发 组件文件id-card-keyboard.stml <template> <view class="id-card-keyboard_container"> <view class="id-card-keyboard_box"> <view class="id-card-keyboard_box-header"> <text class="id-card-keyboard_box-header-label">{idCard}</text> <text class="id-card-keyboard_box-header-button" @click="finish">实现</text> </view> <safe-area> <view class="id-card-keyboard_box-item-container"> <view class="id-card-keyboard_box-item" v-for="item in numbers"> <view class="id-card-keyboard_box-item-label" v-if="item.type=='number'" data-key={item.key} @click="getNumber"> <text style="font-size:28px;">{item.key}</text> </view> <view class="id-card-keyboard_box-item-label" v-else-if="item.type=='ico'" @click="delNumber"> <image class="id-card-keyboard_box-item-ico" src={item.key} mode="widthFix"></image> </view> </view> </view> </safe-area> </view> </view></template><script> import checkIdcard from './id-card-check.js' export default { name: 'id-card-keyboard', data() { return{ numbers:[{type:'number',key:'1'},{type:'number',key:'2'},{type:'number',key:'3'},{type:'number',key:'4'},{type:'number',key:'5'},{type:'number',key:'6'},{type:'number',key:'7'} ,{type:'number',key:'8'},{type:'number',key:'9'},{type:'number',key:'X'},{type:'number',key:'0'},{type:'ico',key:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAC4jAAAuIwF4pT92AAAC/UlEQVRo3u2aO2gUURSGv1k1PlFXUHwUQZEYlUACPiM2KbQSKxEkBEtbHyDxAWqjoK2gqIjYWJpC0gQLQUWjoBJUtBOMiWJMwJiYiGtxdtlzx1lndr07w4X7wcBm59xzcv655752wOPxeDwej8dJggxjtwGHgHagsQ7+fwHvgJvA1QzzrAv7gUKK12NgQdZJ22J3yuKVruf1SCbtEu4Cbqm/nyEl9hKYBGZYivMbyCMP66j6/gxwNuWcrXEMs0fcSynuKRXzY9Yi1EoHpnivUoydRyaUAvApayFqYQtSUiXxPgPLUoyfA0aKsYeyFqNaOjHFewisSNCuHRm7ZiaM0wQcB1ZH3JsNfMXBHniAv5cSSQgo95gBYH6M/XJggso9bI7y54yAzZjijZB8xg+AMZKNlw3Aa2U7HGHjnIAbgO8qqTFgY5U+9mI+gNsRNguBF5jirYuwc0rAFsozXqkEV9boaxvmg3iAjGcArcCguvcIWFLBj1MCvlVJvQHm/qe/9cC48nkf2E55zCsAH2J8OCPgVsyyW2PJbwuVt2fjwNqY9s4IeBpzuWKTDmSM0+L1A6sStK2rgDmLvvLq86Dl/7MfGVs1ozi8NYuik3Lv+GbR7yxErKgSfkL8AYQzJbwolNxFCz4D4KnyOQQcDsXpjfHhjIAAR7AnYiNSuiVf75FZGWSXM63u3fiHH6cEBBFNi3i3Bh9NmLuRAWBeyKYNmMKcuKLK2TkBAS5hininyvYDqu0UsuuIohX4qWz7ImycFBDkwFSL2F1F21KbaWBzjG34rDGMswICXA4ldyVhuz2I4M0J7XcBJ4GdEfecFhDgPKaIPSnHD4AvOHqgWiI8Jl5LMXYD8oOV0wKCrNe0iCdSitulYjpZwppwOfcCO5BeEiDLEBtXDlgMHMRc6ly3nVAWr3acQw4eNMPFRG3tzQuIgPpthB/I4cNoBjlb5wLpvpUwAWyqRyK23gSolj7kxGYpUgUB0gNtXpPFGD3APuS3E4/H4/F4PB6Pxwp/AGH7f53PGS6/AAAAAElFTkSuQmCC'}], numberIndex:0, resultNumber:[], idCard:'' } }, methods: { getNumber(e){ // console.log(JSON.stringify(e)); if(this.data.numberIndex<18){ this.data.resultNumber[this.data.numberIndex] = e.currentTarget.dataset.key;//兼容IOS和安卓 this.data.numberIndex += 1; this.data.idCard = this.data.resultNumber.join(''); } }, delNumber(e){ this.data.numberIndex -= 1; if(this.data.numberIndex>=0){ this.data.resultNumber.splice(this.data.numberIndex,1); this.data.idCard = this.data.resultNumber.join(''); } }, finish(){ if(!checkIdcard(this.data.idCard)){ api.confirm({ title: '提醒', msg: '您输出的身份证号码不合乎规定,是否持续应用?', buttons: ['确定', '勾销'] }, (ret, err)=> { var index = ret.buttonIndex; if(index==1){ this.fire('setNumber',this.data.idCard); } }); } else{ this.fire('setNumber',this.data.idCard); } } } }</script><style> .id-card-keyboard_container { position: absolute; height: 100%; width: 100%; background-color: rgba(0,0,0,0); } .id-card-keyboard_box{ align-items: center; position: absolute; bottom: 0; width: 100%; background-color: #f0f0f0; border-top-left-radius: 30px; border-top-right-radius: 30px; } .id-card-keyboard_box-item-container{ flex-flow: row wrap; justify-content: space-around; align-items: center; padding: 10px; box-sizing: border-box; } .id-card-keyboard_box-item{ flex-basis: 33%; box-sizing: border-box; padding: 5px; } .id-card-keyboard_box-item-label{ display: flex; background-color: #ffffff; padding: 5px; border-radius: 5px; width: 100%; height: 48px; align-items: center; justify-content: center; box-sizing: border-box; } .id-card-keyboard_box-item-ico{ width: 60px; } .id-card-keyboard_box-header{ width: 100%; flex-flow: row nowrap; justify-content: space-between; align-items: center; padding: 10px 15px 0 15px; } .id-card-keyboard_box-header-label{ font-size: 18px; } .id-card-keyboard_box-header-button{ font-size: 18px; color: #327432; }</style>组件应用阐明 ...

December 27, 2022 · 2 min · jiezi

关于移动应用开发:YonBuilder移动开发平台-AVM框架-封装省市区级联选择弹框

AVM(Application-View-Model)前端组件化开发模式基于规范Web Components组件化思维,提供蕴含虚构DOM和Runtime的编程框架avm.js以及多端对立编译工具,齐全兼容Web Components规范,同时兼容Vue和React语法糖编写代码,编译工具将Vue和React相干语法糖编译转换为avm.js代码。 基于规范 Web Components 组件化思维,兼容 Vue / React 语法个性,通过一次编码,别离编译为 App、小程序代码,实现多端开发。 组件性能介绍省市区级联抉择框,也可用于其余多层级数据的抉择,典型场景为省市区抉择。 目前场景设定的是3级,可依据本人的理论需要改成2级或者4级或者更多级。 数据源就是典型的树形构造的JSON数组数据。理论代码中我封装了一个对于省市区三级数据的js插件,具体应用办法省市区三级行政区划数据JS插件 示例展现 组件开发-组件文件area-cascader.stml <template> <view class="area-cascader_container"> <view class="area-cascader_box"> <view class="area-cascader_box-header"> <text class="area-cascader_box-header-label">请抉择所在地区</text> <text class="area-cascader_box-header-button" @click="closeCascader">×</text> </view> <view class="area-cascader_box-nav"> <view class="area-cascader_box-nav-item" v-for="(item,index) in result" data-index={index} @click="setSelect"> <text class='area-cascader_box-nav-item--selected' v-if="this.data.selectIndex==index && item.value">{item.text}</text> <text class='area-cascader_box-nav-item--unselected' v-else-if="this.data.selectIndex==index && !item.value">请抉择</text> <text class='area-cascader_box-nav-item--result' v-else-if="this.data.selectIndex!=index && item.value">{{item.text}}</text> </view> </view> <scroll-view class="area-cascader_pane" scroll-y=""> <view class="area-cascader_pane-option" v-for="(item,index) in selectArea" data-value={item.value} data-text={item.text} @click="setSelectItem"> <text>{item.text}</text> </view> </scroll-view> </view> </view></template><script> export default { name: 'area-cascader', props:{ options:Array }, install(){ this.data.result[0]={value:null,text:''}; }, installed(){ this.data.selectArea = this.props.options; }, data() { return{ result:[], selectIndex:0, selectArea:[] } }, methods: { setSelectItem(e){ if(this.data.selectIndex<3){ this.data.result[this.data.selectIndex].value=e.currentTarget.dataset.value; this.data.result[this.data.selectIndex].text=e.currentTarget.dataset.text; //追加下一级 this.data.selectIndex+=1; if(this.data.selectIndex<3){ this.data.result[this.data.selectIndex]={value:null,text:''}; var parentOption=this.data.selectArea.filter(item=>item.value==e.currentTarget.dataset.value); this.data.selectArea = parentOption[0].children; } if(this.data.selectIndex==3){ this.fire('finish',this.data.result); } } }, closeCascader(){ this.fire('close',''); }, setSelect(e){ this.data.selectIndex=e.currentTarget.dataset.index; if(this.data.selectIndex==0){ this.data.selectArea = this.props.options; } else if(this.data.selectIndex==1){ var parentOption=this.props.options.filter(item=>item.value==this.data.result[0].value); this.data.selectArea = parentOption[0].children; } } } }</script><style> .area-cascader_container { position: absolute; height: 100%; width: 100%; background-color: rgba(0,0,0,0.1); } .area-cascader_box{ align-items: center; position: absolute; bottom: 0; width: 100%; height: 70%; background-color: #ffffff; border-top-left-radius: 30px; border-top-right-radius: 30px; } .area-cascader_box-header{ width: 100%; flex-flow: row nowrap; justify-content: space-between; align-items: center; padding: 10px 15px 0 15px; } .area-cascader_box-header-label{ font-size: 18px; } .area-cascader_box-header-button{ font-size: 40px; color: #ccc; } .area-cascader_box-nav{ width: 100%; flex-flow: row nowrap; justify-content: flex-start; align-items: center; padding: 15px; } .area-cascader_box-nav-item{ box-sizing: border-box; align-items: center; /* background-color: #452334; */ margin-right: 20px; justify-content: center; } .area-cascader_box-nav-item--selected{ font-size: 16px; padding-bottom: 10px; border-bottom: 3px solid #49c916; } .area-cascader_box-nav-item--unselected{ font-size: 16px; padding-bottom: 10px; border-bottom: 3px solid #49c916; } .area-cascader_box-nav-item--result{ font-size: 16px; padding-bottom: 13px; border-bottom: 0; } .area-cascader_pane{ padding-left: 15px; padding-right: 10px; width: 100%; height: 80%; } .area-cascader_pane-option{ flex-flow: row nowrap; align-items: flex-start; justify-content: space-between; padding: 5px 0; }</style>组件应用阐明本组件是基于AVM.js开发的多端组件,通常同时适配Android、iOS、小程序、H5 , 具体反对状况还要看每个组件的阐明文档。 ...

December 19, 2022 · 2 min · jiezi

关于移动应用开发:Unity实现无缝大世界地形

大世界最重要的毫无疑问是地形了,地形也是一项比拟古老,且始终在迭代更新的图形学技术。地形零碎主体技术要点,个别围绕着LOD来开展。最近一些年,随着DrawInstance和GPU Pipeline的风行,地形零碎又在这两个方向做了进一步倒退,这俩技术十分符合地形零碎,几乎就是为地形而生。 Unity的整套地形零碎(包含植被),在有DrawInstance性能前,简直不能在挪动上应用,大家个别采纳转成Mesh的形式在游戏中应用。转成Mesh始终不是短暂之策,施展不了地形极致LOD的劣势。要想做大世界,本人写一套高效的地形零碎是必不可少的。 大略思路仍然是正当的材质和模型LOD,联合DrawInstance,在性能和成果之间进行均衡。我这里提供一个在挪动平台验证过的可行解决方案,能够参考,也可齐全依照这个计划来,至多在几万到几十万的量级不会出问题。 通过模型,能够分两局部,GPU地形和近景四叉树,以下图每个格子是512的大地图为例,灰色局部为近景四叉树,蓝色局部为GPU地形。 GPU地形这里先次要讲一下配置和注意事项。 GPU地形整体大小是2048x2048,更新粒度是512,也能够是256,但若是256,资源文件可能就太多,Unity在资源文件爆炸后,Import的速度会变得很慢。理论测下来,512的粒度没有显著问题。LOD分5级就足够了,因为咱们整体大小才2048,LOD0的Mesh对应密度比例是1:1,LOD4曾经是16x16了。 跟模型无关的数据倡议用保留成二进制文件,因为如果保留成Unity文件,如Asset或者贴图,须要把这些资源放到Asset目录下,而二进制的数据文件是能够放到目录外的,打包的时候进入Bundle就ok,在Editor模式下,能够间接通过文件拜访,所有为了节俭Import耗时。二进制还有益处是,能够整合各种数据,如每个块各个LOD等级的一些配置信息(坐标,高度差等)、挖洞信息等。一个2Wx2W的大世界数据能管制在500MB左右。 在理论应用过程中,如果用Hiz来剔除会有一帧提早,不太适宜做地形的剔除,所以最好还是采纳PVS,并且地形的排布比拟参差,比拟好做PVS,人造省去了PVS里字典映射的局部。 这里说一句,DXR用来烘焙PVS是个不错的框架,很适宜做一些离线自动化工具。 接着来着重说一下,PVS的数据组织和加载应用。 家喻户晓GPU地形只显示2048x2048的地形,那么全量的PVS数据是多少呢?LOD0是一个4x4的Mesh,显示的理论空间是4x4,那么LOD0的内存数据就是(2048/4)x(2048/4)/1024 = 256KB,所有LOD加起来不超过512KB。数据结构能够间接用一维数组来示意,因为是个全量平均排布,那么Offset = (Pow(4,level) - 1)/3,这里level=0示意最低的即2048x2048,这里须要有个换算,还有就是可能低等级没有到0,只有再减去无用Index就能够失去正确后果。其实从一个地块的数据量,就能大略推算出整个大世界地形的PVS数据有多少,理论还能进行压缩,这些对于包体的大小是齐全能承受的。 为了省去索引懊恼须要将硬盘数据全量寄存,这是一棵残缺的树,然而这里的内存还能压缩,因为LOD0是最多的,而离中心点较远的地位其实是不须要的,能够在LOD0只加载64米以内的数据,LOD1则是124米,以此类推。这样能把数据压缩到十分小。就索引来说,须要做进一步换算,拿个纸笔应该能很快推算出来。 GPU地形能够再拆分两种材质:绿色为RVT材质,黄色为中景离线烘焙贴图材质 远景RVT材质在这里因为应用GPU地形的原由,粒度会降落到4x4,这样咱们能够把尺寸缩到512,RVT在把尺寸缩到512后,即便VT的size管制到2048,也能达到一个比拟清晰的画面,不仅能节约很大的内存,而且地表的精密度也能失去很大的晋升。 中景离线烘焙贴图材质咱们为每一块512地形,生成混合实现的Albedo和Normal,这里有三点须要留神。 1.烘焙的时候留神把投射RVT的模型也渲染到下面,比方路面、贴花这些,间接烘焙到中景贴图上。 2.Normal间接取地形的顶点法线即可,无需把Layer上Tangent转到世界空间。相隔太远、太碎的Normal反而会造成噪点,起副作用,咱们只保留顶点法线的后果就ok。 3.烘焙时,地形的Layer能够适当放大,远处的纹理能够通过放大Tile,让纹理的细节显现出来,具体放多大,取决于RVT远处纹理的缩放比。 在材质筹备好后,因为是512位单位的,咱们须要显示2048x2048(两头扣掉一块512为RVT材质),咱们在运行时,能够采纳TextureArray或者动静合并VT的形式。以合并VT的形式为例,咱们能够疏忽两头RVT那个块,间接生成2048x2048的残缺贴图,间接平铺下来就OK,要解决的是,咱们该当尽可能复用之前的数据。如: 咱们只需把右下一圈,填到左上即可。如下图,将蓝色区域填在灰色区域,而后存一个偏移即可。 这种解决办法在整个大世界的资源加载十分常见,肯定要有这个意识。 因为整个GPU地形应用两种材质,那么在GPU地形生成Mesh的IndirectDraw就要离开生成,分两个DrawCall提交。 近景四叉树模型2048以外的中央,其实也能够用GPU地形,只是资源的模式要分两级,2048以外的数据粒度更大一点,保障IO敌对,甚至能够做四叉树的IO,不把各个等级的数据放在一起,从技术上讲是能齐全可行的,关键问题在于,进去的模型品质很差,因为须要把LOD等级提到很高,根本至多要128x128来示意一个数据,疏密调节的灵便度在这里远远不如模型。 在确定生成近景模型后,最现实的状况是,全地图生成一个动态模型间接渲染,一个2Wx2W的大地图,做完极致优化,1~2W面就能达到预期成果。这里倡议用Houdini主动生成,能够把山脊那局部的面加多,海拔低的盆地和平原面数能够砍得狠一点。然而咱们面临了几个问题。第一,咱们要能灵便抠掉两头2048x2048的空隙,粒度是512。第二,近景模型跟GPU地形之间存在接缝问题。 第一个问题,难的不是抠出两头局部(VS输入SV_Position为无穷大或者堕落顶点到边上),难的是2048的矩形边,要求抠出来是一个四四方方的,那么就须要在每个512上都要卡一条直线,卡线会使模型顶点数间接翻倍。 第二个问题,能够把模型往降落降,依据视角个性,能解决大部分问题,然而恰好地形接缝问题太过突兀(漏出天空盒,有人思考把天空盒的底面用地形色彩图,仍然没法解决所有问题),必须得保障100%没问题。那么经典的解决接缝问题就是堕落边和加裙子,因为GPU地形和近景地形不在一个零碎,不好堕落边,只剩加裙子了,加裙子又须要很多顶点。 为了解决这两个问题,最简略的一个方法是,切成每个512x512的地块,且生成裙边,而后管制地块的显隐,再通过Static Batch渲染。这里引申出了两个新问题,一个是面数过多,切块加裙边,面数根本翻3倍以上,另一个是尽管是Static Batch,然而理论DrawCall数偏高。理论裙边仅仅在2048的那个矩形须要,其余中央造成很大的节约,能缓解第一个问题,然而还是因为切边导致面数翻番。 咱们引出一种新的解决办法,四叉树生成各个等级LOD的模型,而后显示矩形,如图所示: 这样的益处在于:极大缩小了因为切割增加的面,DrawCall相比全量LOD0少得多。 一些想法还是想通过堕落横跨两头矩形三角形,把矩形内的点推到矩形上,而后对于矩形内剩下的三角形,在VS的输入,设置SV_Position的xy为无穷大来挖空两头的局部,仿佛是一种比拟完满的解决方案。因为四叉树的近景显示,性能没太大问题,临时没尝试这种方法,前面如果有机会,能够试试这种方法。 近景的材质,间接应用一张Diffuse贴图就能够,然而这张贴图,不要应用工具主动生成,因为就这一张贴图,值得花一些工夫在PS里精雕,特地是能够在游戏场景中对着来画,一边画,一边对照游戏中的成果,在山脊的中央,勾画出山体的线条,把法线、AO这些都间接画到Diffuse上,会起到意想不到的成果。 封面图来源于网络 这是侑虎科技第1259篇文章,感激作者狗哥老司机供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:793972859) 作者主页:https://www.zhihu.com/people/... 再次感激狗哥老司机的分享,如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:793972859)

December 16, 2022 · 1 min · jiezi

关于移动应用开发:悬浮窗开发设计实践

目录介绍01.整体概述 1.1 我的项目背景1.2 遇到问题1.3 根底概念1.4 设计指标1.5 收益剖析02.Window概念 2.1 Window增加View2.2 Window的概念2.3 LayoutParams2.4 WMS流程梳理03.悬浮窗技术要点 3.1 业务思考点剖析3.2 关键技术要点3.3 利用悬浮窗3.4 增加浮窗源码流程3.5 了解WMS原理3.6 拖拽回弹吸附04.开发重要步骤 4.1 悬浮窗实现流程4.2 申请悬浮窗权限4.3 初始化悬浮窗4.4 设置悬浮窗参数4.5 增加View到悬浮窗4.6 悬浮窗拖拽实现4.8 悬浮窗权限适配4.9 LayoutParam坑05.计划根底设计 5.1 整体架构图5.2 UML设计图5.3 要害流程图5.4 接口设计图5.5 模块间依赖关系06.其余设计说明 6.1 性能设计6.2 稳定性设计6.3 异样设计6.4 事件上报设计07.遇到的问题和坑 7.1 解决输入法层级关系7.2 边界逻辑敞开悬浮窗7.3 点击屡次关上页面7.4 Home键遇到的问题01.整体概述1.1 我的项目背景业务场景剖析 以视频通话为例,在视频通话时,咱们关上其余利用或点击Home键退出时或点击缩放图标,悬浮窗会显示在其余利用之上,给人的假象是通话页面变小了,点击悬浮窗回到通过页面,悬浮窗隐没。退出通话页面悬浮窗隐没。市面上常见的悬浮窗,如微信视频通话性能,有如下特点: 整屏页面能切换到一个小的悬浮窗;悬浮窗能运行在其余app上方;悬浮窗能跳回整屏页面,并且悬浮窗隐没需要悬浮窗成果 点击放大按钮,将以后远端视屏加载进悬浮窗,且悬浮窗可拖拽,不影响其余界面焦点;点击悬浮窗可返回原来的Activity1.2 遇到问题什么是悬浮窗 全局悬浮窗在许多利用中都能见到,点击Home键,小窗口依然会在屏幕上显示。留神:悬浮窗留神申请权限!那么开发全局悬浮窗属于那一类呢? 属于零碎窗口,相当于跟Toast是一个级别的。针对悬浮窗的展现和移除,则能够模拟Toast中addView和removeView操作……视频通话Activity如何最小化 Activity自身自带了一个moveTaskToBack(boolean nonRoot),咱们要实现最小化只须要调用moveTaskToBack(true)传入一个true值就能够了,然而这里有一个前提,就是须要设置Activity的启动模式为singleInstance模式,两步搞定。注:activity最小化后从新从后盾回到前台会回调onRestart()办法。点击悬浮窗开启activity会回调onNewIntent(留神能够setIntent(intent)一下)1.3 根底概念Window 有三种类型,别离是利用 Window、子 Window 和零碎 Window。 利用Window:z-index在1~99之间,它往往对应着一个Activity。子Window:z-index在1000~1999之间,它往往不能独立存在,须要附丽在父Window上,例如Dialog等。零碎Window:z-index在2000~2999之间,它往往须要申明权限能力创立,例如Toast、状态栏、零碎音量条、谬误提示框都是零碎Window。这些层级范畴对应着 WindowManager.LayoutParams 的 type 参数 如果想要 Window 位于所有 Window 的最顶层,那么采纳较大的层级即可,很显然零碎 Window 的层级是最大的。Android显示零碎分为3层 ...

November 18, 2022 · 2 min · jiezi

关于移动应用开发:已开源就等你来优酷动态模板研发体系为分发提效30

概述优酷是一个多屏、多端,以内容散发及内容生产为主体的娱乐生态综合体。 在内容散发场景,存在大量的客户端开发需要,包含视觉降级、各场景的业务需要迭代、大小屏设施需要同步等,为了升高研发在跨端场景中组件反复开发的技术老本,优酷技术团队于2019年底开始摸索跨端动态化研发提效解决方案,通过2年多工夫的致力,目前跨端动态化能力曾经在优酷各业务场景落地,帮忙研发团队在散发业务上实现提效30%左右。动静模板技术体系以跨端动态化SDK为核心,通过在设计阶段、研发阶段、联调阶段升高对接、开发、调试等外围工作的技术老本,从而真正实现研发提效。目前整个技术计划包含画眉跨平台动效解决方案、智能化设计平台GaiaSketch、跨端模板动态化计划GaiaX、动静模板可视化少代码搭建平台GaiaStudio、辅助调试工具疾速预览FastPreview。动静模板技术计划将客户端研发链路实现了串联,通过齐备的工具化支撑体系,让开发者能够高效实现组件由原始设计稿到可运行代码的最短通路,本文将对研发体系中波及到的外围模块就行介绍,心愿对技术社区及宽广开发者有肯定帮忙。 智能化设计平台 - GaiaSketchSketch作为一款优良的设计软件,目前曾经是绝大多数互联网企业设计部门的次要设计工具。与Photoshop相比,Sketch对矢量元素具备更强的解决能力,并且其自身提供了丰盛的二次开发接口,具备很强的开放性。针对标注导出及组件还原两局部日常工作量占比拟高的工作,优酷技术团队基于Sketch插件架构体系,研发了自动化标注构建导出、组件代码主动生成以及规范素材库建库等能力。对于设计师来说能够通过插件建设起规范库(包含:组件库、款式库、图标库、页面库),也能够通过插件导出更为简略易用的【标注文件】;对于开发来说,能够通过插件能将Sketch设计稿中的图层导出为代码(GaiaX、React、Rax、Vue、小程序等)。 规范素材库每个组件库由一个或者多个 Symbol 形成,每个 Symbol 依照肯定的格局命名后,插件就能读取并可视化展现在插件中,并可将可视化后的组件间接拖拽到 Sketch 设计稿中且与原库放弃关联。每个 Symbol 的名称遵循着规范的格局命名,名称以 / 连贯,比方 Foundation/Bar/TabBar ,导入到插件后,可视化展现的成果如上图所示。 标注导出将Sketch源文件中的画板导出为标注文件供开发应用,导出的【标注文件】具备更好的兼容性、更易用的界面。 标注导出的产物次要蕴含两个局部,一部分是标注模板文件,另一部分包含设计稿相干数据信息,比方画板截图,图层数据、切图等。 代码导出将Sketch设计稿中选中的图层导出为选定的语言类型的代码,比方GaiaX模板、React、Rax、Vue、小程序等,导出的代码布局形式基于盒子模型布局(Flexbox)和相对布局(Absolute);对于节点的层级,【导出代码】会在导出的过程中进行一直的优化,使层级尽可能的少且正当 开源我的项目GaiaSketch开源我的项目地址:https://github.com/alibaba/Ga...。 动静模板引擎 - GaiaXGaiaX是动静模板引擎我的项目在优酷外部的代号,它是解决跨端组件开发提效计划的关键技术。在项目前期的需要分析阶段,团队从优酷的理论场景和各团队开发中的切实诉求登程,将问题空间定位在组件这个层级,不仅很好的躲避了如Weex、ReactNative等技术计划的复杂度和工程量,其次也在基本上让技术计划脱离JS Bridge的老路,保障了端侧的落地性能保障。 架构设计依照分层设计理念共分为4层。根底依赖层保持最小依赖准则,要重点阐明的是,为了保障模板布局计算的高性能,咱们引入了由RUST编写的StretchKit高性能布局计算引擎[https://github.com/vislyhq/st...],其具备跨端、较小的包体积(170K)、计算性能卓越等特点;外围渲染层形成模板引擎的渲染内核,解决模板文件解析、虚构节点树构建、布局计算、表达式构建解析等外围逻辑;模板核心及模板服务层则面向业务,与优酷业务架构进行联合实现对现有能力的复用,防止反复造轮子,并向下层业务提供标准化模板渲染及接入服务。 总线设计对于动静模板引擎来说,输出结构化的模板文件,通过文件IO、数据解析、虚构节点树构建、布局计算、表达式运算、渲染树构建到实在视图树组成了残缺的总线链路。 性能体现因为动静模板DSL具备严格的标准化和规范化,因而在设计研发交付一体化链路上,GaiaSketch的组件代码导出可间接生成GaiaX动静模板DSL代码,这样,开发工程师能够缩小模板框架的从0到1的搭建工作,通过简略的编辑优化,便可实现一个模板文件的构建。 开源我的项目优酷动静模板引擎开源我的项目地址:https://github.com/alibaba/GaiaX 动静模板低代码IDE - GaiaStudio动静模板引擎能够解决跨端组件渲染的一致性,通过一套对立的DSL对组件布局、款式、数据进行形容,从而实现多端开发的提效。但对于客户端开发来说,类前端的DSL自身存在肯定的学习老本,另外,如何编写更加正当、简洁的模板代码,对模板在终端最终的渲染及性能体现都会产生间接的影响。为了解决上述问题,优酷技术团队在动静模板引擎上线后,着手构建了GaiaStudio动静模板低代码IDE。开发者通过该IDE,能够对模板进行可视化搭建,引擎反对的所有布局及款式属性,都在IDE中进行了内置,开发者只需进行抉择,便可用户界面中即可失去渲染反馈。 技术计划思考到开发便捷性和后续的跨平台,咱们选用了Electron作为底层跨平台计划,用Sematic UI作为CSS组件库,保障在没有设计的状况下,也可能写出比拟好看的用户界面。与网页前端比照,Electron有更好的性能、并且可更便捷的实现有手机端的互联互通。 性能简介 ● 模板创立模板是GaiaX技术体系的外围介质,也是跨端动静渲染的关键点。 模板的构建品质决定了端侧渲染的性能和还原成果。GaiaStudio提供了十分全面的模板治理能力,反对从0-1间接构建一个新的模板,也可通过GaiaSketch导出模板代码的形式,导入到GaiaStduio简化编辑老本。 ● 模板编辑模板编辑是模板构建过程中工作量最大的,为了升高开发者的技术老本及对FlexBox的学习曲线,GaiaStudio提供了可视化、参数化的产品性能,即便操作者对FlexBox、CSS理解甚少,也能够疾速上手实现一个模板的编辑工作。对于一个模板,一般来说咱们要进行一组残缺的编辑解决,能力实现整个构建工作,包含:根底属性、布局、款式、动画、数据绑定。 ● 模板调试GaiaStudio的模板调试性能次要有两个性能形成,即模板代码动态剖析及模板真机预览(FastPreview)。1) 模板代码动态剖析能够帮忙开发者查看模板代码的语法及合理性,针对如模板命名是否非法、布局设置则、模板嵌套层级等问题实时反馈给开发者,帮忙开发者更正当的实现模板搭建。 2) 模板预览FastPreview,GaiaStudio提供了亚秒级的模板真机预览能力,让开发者能够实时在真机端查看本人搭建的模板的正确性和还原成果,让Native开发也能够具备相似H5、小程序的开发体验。 下载地址GaiaStudio目前对社区提供通用版本安装包,源码临时未开源,具体的下载地址在GaiaX开源我的项目中可获取:https://github.com/alibaba/GaiaX。 跨平台动效解决方案 - 画眉在客户端开发的日常工作中,存在着大量动效场景需要,如 ToastView 显示与隐没、Dialog弹出、按钮的显隐等。当动效设计需要交付给研发后,往往要达到最终的成果,须要通过多轮的走查和调优,归纳起来造成这种后果的起因次要有:1)同一动画,不同的开发人员实现的成果有差异 2)同一动画,iOS 端和 Android 端实现的成果有差异 3)对于曲线动效,设计师无奈提供精确参数,实现成果随机性强 4)局部开发人员对系统自带曲线函数不相熟,会导致降级利用线性或加速曲线,影响动态效果。为了彻底解决端侧动画实现的研发效率及成果保障,技术团队与设计核心独特推出了跨平台动效解决方案-画眉(Motion-curve)。在GaiaX动静模板引擎中,曲线动效的实现也依赖画眉提供根底服务。 架构设计 技术计划● iOS端技术计划SDK 接口层采纳 Category 计划,通过 AOP 思维来简化调用复杂度。CALayer + MotionCurveX 为 CALayer 的所有可动画属性,依照动效曲线能力提供反对,且在动画实现后,无需再次设置目标值。画眉SDK将罕用的 7 种动效曲线,进行底层算法实现,使用者只需指定曲线枚举,即可实现标准化的动效。● Android端技术计划为了升高Java调用C的性能损耗,Android端采纳差值器Interpolator来实现曲线动效。 ...

April 24, 2022 · 1 min · jiezi

关于移动应用开发:Lazada-D11-体验升级技术实践

作者:余浩斐(浩斐) 领有 6.8 亿人口的东南亚市场正在经验爆发式增长,作为东南亚当先的电商平台和阿里巴巴全球化策略的重要增长引擎,Lazada 已成长为当地当先和增长疾速的旗舰电子商务平台,推动了东南亚数字经济基础设施的提高,目前Lazada业务在东南亚印尼、菲律宾、泰国、马来西亚、新加坡和越南等六国经营,员工来自全世界50个不同的国家和地区,实现了各国业务的同步推动,在买家和商家数量上持续放弃了强劲的持续增长态势。 数据显示 D11 当天有超过800,000品牌和商家参加,Lazada越南首小时同比去年销售额翻番,Lazada新加坡首小时销售额较素日增长10倍!这些增量数字的背地,离不开Lazada各个团队、商家、品牌和用户的共同努力,其中无线技术团队保持数据驱动、技术赋能,高效稳固的撑持D11期间的多方购物场景,同时在启动耗时、会场渲染、包体积和主链路体验方面都有显著的提高。 去年咱们曾经进行了大量布局优化等根本技术策略,在2021年咱们将优化场景在一步下沉到更加细化的畛域,联合“页面合并和工作重编”对利用启动进行了大幅优化,发展了针对低端机所进行的“全链路路由动静预申请”,为了进一步加强数据复用性也发展了“购物车增量更新”和“下单页首屏预判”......链接针对Lazada 启动、首页、PDP、Cart、Checkout等根底链路外围场景所进行的多方面体验优化,造成了链路级别的用户体验再降级,质变的过程也势必将引发量变的后果,最终晋升整体的业务转化效率. 启动工作编排&懒加载利用启动作为用户进入首页的第一必经环节,启动性能带给用户的体感很大水平决定了用户接下来是否抉择持续留存,因而对App的启动优化咱们始终是继续精进,同时随着业务的一直简单以及可优化空间的逐步压缩,优化遇到的挑战也是十分大,也经验了一直的摸索。 利用启动阶段大大小小有几十个工作,这些工作须要主线程进行调度并行,同时主线程本身也须要执行一些必须执行的工作,工作之间还有逻辑上的依赖和锁依赖,因而咱们将工作分成若干个组,组内并行能够达到最大并发成果,组间串行解决依赖问题,组和组之间由主线程进行同步调度,在工作并发和依赖之间达到均衡的调度框架。 启动工作优化的主旨就是要让组内工作并行更快,缩小主线程的占用,让主线程可能更快地调度下一组, 因而每一组的启动时长由组内的最长耗时工作决定,通过对工作进行编排,让有逻辑依赖和锁依赖的工作尽量不分在雷同的组,基于这个准则咱们通过systrace等工具对短板工作和锁进行了进一步的梳理并进行了针对性优化: 对耗时过长工作进一步进行拆分,缩短工作在单组中的耗时;尽量在放弃依赖关系的前提下对下列应用loadLibrary锁的工作进行扩散编排,最大水平缩小锁竞争;对耗时工作进行优化,对不必要在系统启动阶段应用的工作在可交互后初始化。 通过以上的优化策略,本期优化启动工夫绝对去年双11有较大幅度的改善,随着优化的进一步停顿,后续的启动优化,须要有更多的千里之行;始于足下的信心和急躁,一直地引入新的工具、新的思维,联合线上数据的欠缺更加准确地进行治理和优化。 启动页合并在启动工作编排的根底上,咱们在启动过程中能够察看到,一次残缺的启动过程,APP会经验Application的创立,闪屏页Activity的创立,最初才是用户真正看到的首页Activity,即LazadaApplication.onCreate() -> EnterActivity.onCreate() -> MainTabActivity.onCreate() 如果将这两个页面合并为一个Activity,用户启动即首页。在Activity外部通过View的转换实现上述闪屏页到首页Activity的切换,那么用户就会缩小一次Activity的创立及这个过程中相应的Binder调用,从而更快的展示首页内容 最初,新的启动流程就由原来的三步演变为LazadaApplication.onCreate() -> EnterActivity.onCreate() 两步,在EnterActivity外部,当启动工作初始化实现时,通过contentView的转换将闪屏页替换为首页内容,从而晋升启动速度,借助试验采样节约了250ms左右的启动工夫。 全链路路由动静预申请如何针对低端机弱网络进行定向性剖析,并造成链路式而非单点式的优化策略是咱们2021年新的发力点,全链路预加载我的项目是在全局资源智能调度上的一个尝试,通过定制更加灵便的调度策略来实现最大水平的利用零碎闲暇资源,咱们启动该项目标外围指标也是:通过灵便的前置任务调度,实现全链路的秒开体验。 导航预加载 利用导航过程中的工夫gap,提前加载下个页面的资源。这部分节俭的工夫 不仅蕴含页面切换耗时100ms-200ms,可能也蕴含页面初始化的工夫100ms-200ms(个别页面曾经实现了“初始化和网络加载同步化”)。闲时预加载 智能调度:基于用户行为预测,利用以后页面闲暇工夫,提前加载下个预测页面的资源;强制调度:无行为剖析,利用以后页面闲暇工夫,提前加载下个配置页面的资源。通过定义一套标准化解析调度框架不仅反对近程配置的解析映射同时也反对本地工作的近程调度,最终在业务无侵入的个性下实现全链路的路由申请。 通过前置申请减速网络申请过程 PDP场景优化110ms, Cart(from PDP)场景优化230ms, 下单页优化110ms,用户外围购物链路预计节俭450ms。 购物车异步刷新增量更新不同于页面关上场景:在二刷场景中端侧的视图构建和渲染根本不会有工夫耗费,Android和IOS都是有成熟的View复用机制,目前枷锁二刷用户体验的外围问题在于网络耗时过程,从监控数据也能看出接口的Api-totalTime耗时均匀就须要1200ms左右。引发二刷网络耗时的起因有两个: 全量更新导致服务端必须在update中进行全页面元素的从新计算(Trade基于团体奥创中间件协定);全量数据返回引发数据的传输和解析过程都会更加耗时。因而初步的解决形式是:针对二刷场景服务端仅返回须要增量更新的数据元素,客户端借助上次数据进行本地增量合并更新,这样子即升高了服务端的申请耗时,同时也能升高数据量大小从而缩小传输和解析耗时(当然多了一次比照过程耗时,根本能够忽律不计) 整体的计划概述为:借助客户端对上次页面数据的复用能力,通过针对购物车异步操作进行增量更新,升高服务端申请的计算范畴和数据包体积,并最终达到升高网络耗时的目标。第一期咱们次要针对"商品店铺的抉择反选、商品数量的增减"操作进行定向优化,将计算范畴放大到"已选中商品的店铺元素、操作的商品店铺元素、非凡固定元素" 成果比照视频,请查看:Lazada D11 体验降级技术实际 数据取自中国深圳, 线上环境 借助AB试验比照:ServerRt优化57.52%,SKU查问量优化80.69,QPM优化217.75% 客户端大盘数据TotalTime从1100ms->750ms晋升31.82%,通过细分维度能够察看到Item引发的刷新动作有明显降低其余的根本稳固,合乎预期;购物车弱化刷新频次Android购物车目前每次从新进入都会引发刷新操作,联合购物车线上性能数据:用户每次进入根本要期待1.029.81ms,64.5%的用户能够在1s内实现加载,这就导致一个问题“局部加购的用户可能重回购物车时也会引发刷新”,譬如这个用户故事:用户在Cart Just For You模块看到了一个感兴趣的商品并点击进入PDP, 在PDP浏览后(无任何加购操作)返回Cart,Cart重刷会导致之前正在浏览的数据被从新笼罩。因而咱们启动了该我的项目次要目标是:在兼顾购物车失常刷新的前提下,升高购物车的刷新频次。 Android侧仅凋谢“重回购物车时刷新”,这是思考到“大促场景下Android用户群体宏大,加购动作如果间接绑定Cart刷新行为可能引发流量压力”;同时,Android侧提供了“即时刷新购物车”的能力以便于非凡场景下的应用。 成果比照视频,请查看:Lazada D11 体验降级技术实际 数据取自中国深圳, 线上环境 ...

February 8, 2022 · 1 min · jiezi

关于移动应用开发:极客星球-Flink在数据智能公司的探索实践与优化

▌Flink摸索1.1:Why FlinkApache Flink 是一个分布式解决引擎,用于离线和实时的计算。Flink凭借其极致的流式解决性能和优良的框架设计吸引了泛滥开发者退出,各大厂也都纷纷引入Flink作为其次要的流式开发引擎。 Flink的次要劣势: Exactly-once 语义多种高效的窗口计算轻量级的checkpoint机制反对 EventTime 及工夫乱序事件高效的反压机制弱小的状态管理机制,可反对超大的状态存储反对流的join和维表的join泛滥的connector反对简单事件处理CEP反对SQL、自定义UDF、UDFA等Flink在满足简单性能的同时,QPS还能够达到百万级别,同时容错性和准确性也能够失去保障,这是MobTech袤博科技抉择Flink的次要起因。MobTech袤博科技是寰球当先的数据智能科技平台,累计笼罩设施155亿+,DAU 2.6亿+,继续迭代的趣味标签达6000+。在数据智能产业,以数据利用为主导,交融顶尖的大数据、云计算、人工智能等多元先进技术,打造开发者服务、商业化、AI、Mob研究院四大版块,为寰球数百个国家和地区的企业、开发者和政府机构提供商业智能解决方案、App经营赋能计划、企业级AI智能计划、数据征询钻研等服务。 1.2 Flink在MobTech袤博科技的集群状况公司采纳 On-yarn 的运行模式,长时间运行的流工作采纳Per-Job模式,局部利用采纳Session形式运行。On-yarn模式简略不便无需关怀保护Flink集群状况,只须要配置Yarn和部署Flink Client即可开发和提交Flink工作。Flink版本从最后的1.7一路降级到1.13,降级至最新版本的起因有三点: 1、因为社区版本迭代较快,如果跨多个版本迭代批改的老本会十分大,所以在小版本迭代的时候抉择降级是最划算的,因为须要改变的代码不会太多; 2、Flink社区十分沉闷,每个版本都会有较大的性能和性能的晋升,特地是Flink SQL,这也是咱们思考的重要起因之一; 3、随着版本的迭代开发方式也失去降级,新近次要是基于stream api进行开发,开发效率偏低而且性能不是最优的。公司外部逐步搭建起Flink流式平台,使开发SQL化大大提高了开发效率和程序性能。 MobTech袤博科技领有独立的流式计算集群,蕴含了Flink、Spark、Storm等各种的流计算引擎。而Flink应用程序占80%以上,大部分过来开发的Spark streaming程序在应用Flink重构后,资源利用率翻了一倍,同时解决了计算提早、数据背压以及内部资源依赖等问题。 ▌Flink在数据智能企业的利用和挑战Flink在MobTech袤博科技的利用场景有很多,从实时报表、数据监控、实时画像、机器学习到实时数仓,Flink在各个环节和业务线都施展了至关重要的作用,为公司带来微小的价值。接下来我将选取几个比拟经典的案例来分享咱们在Flink实际中获得的教训成绩。 2.1 经典的多维度DAU计算问题MobTech袤博科技作为寰球当先的数据智能科技平台,因为笼罩的设施数量微小,所以每天都有大量的日志进入日志零碎; 日均实时处理数据量达150亿+,日均QPS 20w/s+,数据处理峰值可达 90w/s,DAU 达2.6亿+,MAU 达 12亿+,趣味标签体系6000+; 同时Flink Checkpoint 达10G左右,所以如何精确地计算和存储如此微小的数据成为一大挑战。 单个Topic 最高 20w/s +,总和曾经超过百万QPS 计算过程中数据可扩张至千亿以上 挑战1:大QPS下的 UID去重问题 因为思考到日活的数据量较大,单天的日活已达2.6亿+,周活和月活的能够翻数倍,如果要准确计算UV值将会耗费微小的内存和磁盘空间,同时会导致checkpoint的后果微小可能呈现提早甚至失败。Flink有原生的COUNT DISTINCT来反对去重计算,采纳的 Split Distinct Agg 形式做聚合,能做到准确去重但效率不高长时间运行上来会越来越慢,同时无奈解决状态太大的问题。起因在于COUNT DISTINCT应用了MapState作为状态存储,如果单个Key的UID过大会导致内存溢出同时State过大导致Checkpoint工夫过长甚至失败。 所以咱们采纳业内罕用的HyperLogLog算法做到误差小于0.1%的估算形式,单个维度的Key对应的HLL只有一个对象且大小只和精度无关,重写相似COUNT DISTICT的聚合函数即可实现,HLL_COUNT_DISTICT(UID)。 通过优化后Flink Checkpoint的大小由原来的30G降到2.5G左右升高了10倍以上的存储压力,同时计算上没有呈现背压的状况。 挑战2:数据热点问题 在开发的过程中咱们发现某个报表后果只有两个Key导致所有数据只进入两个slot计算导致热点问题,这类问题借由Flink原生对COUNT DISTINCT的优化思路Split Distinct Agg形式能够很好地解决。SQL语句如下: 第一次聚合由group key和额定的bucket key进行shuffle,bucket key是应用 HASH_CODE(distinct_key) % BUCKET_NUM计算的。当第二次group by day的时候须要留神的是,因为咱们应用HLL_COUNT_DISTICT来代替原生COUNT DISTINCT,返回类型是HLL,所以须要自定义SUM_HLL对HLL对象做累加解决。 ...

December 8, 2021 · 1 min · jiezi

关于移动应用开发:mPaaS-月度小报|魔方卡片Cube公测十个卡片模板任意使用

本月亮点速览 产品动静 魔方卡片(Cube)公测小程序公布 IDE 质检工具等质量体系(需专有化部署)音讯推送 MPS 新增定时推送、音讯撤回等推送能力挪动网关新增熔断机制营销动静 魔方卡片(Cube)公测期间,每位用户均可取得十个卡片模板收费应用飞天会员购 mPaaS 根底资源包享八折优惠产品动静1.魔方卡片(Cube) 公测公布,源于「支付宝」首页的卡片技术栈,让 App 各业务模块开发及发版更繁难、更动静。 2.小程序 小程序质量体系公布,蕴含巨神兵、云闪测、IDE 自检工具和线上巡检四个工具。(需专有化部署) 3.音讯推送 MPS 推送能力进一步丰盛,新增定时推送、音讯撤回、自定义推送人群性能;(需专有化部署) 4.挪动网关 MGS 新增熔断机制,提供熔断能力,在后端系统出现异常时对后端进行爱护。(需专有化部署) 5. 版本基线阐明 Android 10.1.68 系列11月已公布至 42 版本;iOS 10.1.68 系列11月已公布至 40 版本。开发者可返回 mPaaS 产品文档查看版本更新详情。 营销动静1.魔方卡片(Cube)公测福利 每个用户均可取得十个 Cube 模板,公测期间收费任用。在接入应用过程中如需技术支持,可钉钉搜寻“32843812”退出接入答疑群。 2.mPaaS 根底资源包飞天会员八折 公布更新接口调用次数、公布包下载流量、推送胜利音讯数、网关接口调用次数、挪动剖析埋点流量、数据同步音讯数、投放展位接口调用数等预付费资源包八折购。(仅限飞天会员)

December 3, 2021 · 1 min · jiezi

关于移动应用开发:我为什么放弃移动开发

本文作者|Niklas Klein 译者|朱琪珊 策动|蔡芳芳,原文链接:https://medium.com/swlh/9-rea...,本文由原作者发表在 medium.com,经原作者受权由 InfoQ 中文站翻译并分享。 当我还在上大学的时候,Android 和 iOS 还是新兴的平台,每个人都对这两项技术很感兴趣。如果你加入一些过后的编程研讨会,最初总会写一个小型的 Android 利用。这就是我向 Android 生态系统迈出的第一步,也可能是我随后成为了一名挪动开发者的起因。 在这篇文章中,我想要分享我对于 Android SDK 和 Flutter 的蹩脚体验。我提到的某些要点也实用于 iOS SDK。我曾经在几年前放弃了挪动开发的工作,心愿起初许多事件曾经在朝好的方向改良。但在过后,我发现挪动生态系统是如此的令人感到困惑和挫败,以至于我抉择了一条不同的职业门路。 设施的碎片化对开发者而言,Android 开发的最大痛点,就是设施配置的微小差别。我始终都未能了解为什么 SDK 中大部分性能(尤其是用户交互界面的局部)取决于设施,而不是我的利用。这从根本上导致我必须应用反对库,针对每个指标 API 级别调试我的利用。除此之外,我还常常遇到在仿真器或者测试设施上好好的代码,却在某个三星或者华为的设施上解体的状况。 质感设计(Material Design)当我在 HackerNews 或者 Reddit 上读到对于谷歌的 Material Design 的评论时,某些时刻会感到我是惟一真正喜爱它的人。我感觉它在视觉上很具吸引力,我通常很享受这样的用户体验。官网的文档网站倒退得很快,也十分胜利,我感觉这是优良文档的榜样。当它被发表用于 Android 时,我感到十分兴奋! 话虽如此,在 Android 平台上从 Holo 过渡到 Material Design 并非一帆风顺。因为它如同是被急匆匆公布进去似的。在接下来的几年之中,官网的 Material Design 反对库始终短少一些十分根本的组件。尽管你有时能够在谷歌自有的利用上看到这些组件,然而它们并没有真正被纳入到反对库中。开发者不得不构建本人的组件,或者应用 GitHub 上品质无奈保障的实现作为替代品。这次应用 Material Design 反对库的经验,再加上大量的不统一的视觉设计和实现谬误,让我第一次停下来真正地思考和质疑这个生态系统。 无人理睬的最佳实际构建一个牢靠的 Android 利用,是一项充斥挑战的工作。这次要是因为 SDK 对开发者并不敌对。实践上,一个 Android 应用程序能够永远挂在后盾不应用任何系统资源,而后在用户须要时立刻回到先前的状态,这切实是令人诧异。不过前提是开发者正确地实现了这个利用的状态和生命周期管理机制。 开发者:你好,互联网!我的利用在扭转屏幕方向时解体了,该如何解决?互联网:这很简略!禁止扭转屏幕方向即可。 ——啊哦,可怜的是,这种回复很广泛。 你是否已经应用过 Java 中的线程(Threads)?在可变变量随处可见的的命令式代码库中,这件事变得十分艰难。但你猜怎么着?在 Android SDK 中应用线程更加艰难。如果你想要在一个 Activity 中治理 Thread,那你就只能自求多福了。侥幸的是,咱们最终保留了 Fragments,这最终让这件事变得容易了一些。但代价是在一开始就须要应用 Fragments。 ...

November 8, 2021 · 1 min · jiezi

关于移动应用开发:App-不想被点名mPaaS-隐私合规检测为开发者护航数字生态建设

01 寰球最严数据隐衷保护法行将落地,你的 App 筹备好了吗?据新华社报道,全国人大常委会于 8 月 20 日通过了《个人信息保护法》,该法自 11 月 1 日起实施。 它全面和具体地规定了企业爱护集体信息安全的各项任务,同时指出违反法规最高可面临 5000 万或一年度营业额 5% 的巨额罚款。 这是我国首部针对个人隐私爱护的法律,立法的严格水平堪比欧盟《通用数据保护条例》(GDPR)。它的出炉不仅是对顶层制度的查缺补漏,还进一步开释出了国家信心整治市场侵害个人隐私行为的信号,将对互联网科技行业造成压力。 近年来,政策法规及监管规范一直细化深入,监管查处力度也一直加大。App 开发企业如若违规将会面临的各类损失也在日益减少: 品牌形象受损: 全网公开通报,大量负面信息,导致品牌公信力降落;用户量下滑: App 被下架,无奈获取新增用户,已有用户也可能会基于隐衷平安思考,放弃应用 App;经济损失: 最高 5000 万或一年度营业额 5% 的巨额罚款。你的 App 筹备好直面这场“严考”了吗? 02 隐衷合规自查?这些“重点难点”圈一下隐衷合规自查,企业须要面临的难点大略有三个方向: 监管条文的解读及有效性;检测我的项目多、检测步骤简单;检测不全面,漏检漏改危险大。隐衷检测的几个重点问题也须要开发者及时关注: 但除工信部重点检测的十项问题外,隐衷合规检测项还须要笼罩监管法规要求的其余 25 项检测内容,合乎四部委所有监管条例。 不难发现,隐衷合规检测我的项目泛滥且耗时较长,波及动态、动静、人工解读等,隐衷合规条文也必须通过业余的人员解读且联合业务性能利用场景,判断其是否非法合规。 其中如有一个环节呈现过错,开发者便会为本人勤勤恳恳开发的利用埋下相应的危险隐患:监管不定期巡检查处,不合规的会被全网通报勒令整改,情节严重的间接下架解决。 03 大厂隐衷合规计划,全方位对外输入mPaaS 隐衷合规检测服务: 根据国家相干法律法规及行业标准,对挪动APP隐衷平安、集体数据收集和应用进行合规剖析。从个人信息收集、权限应用场景、隐衷政策等多个维度帮忙企业及APP开发者辨认平安危险,提供对应的专家整改倡议,助力客户躲避监管处罚及通过审核上架。 立刻填写表单理解更多计划详情 01 隐衷协定检测,业余法务解读以国家颁布并执行的法律法规为检测规范,基于已取得多项专利的隐衷政策文本智能剖析技术对隐衷协定文本的自动化检测,逐条比照隐衷协定申明是否合乎法律法规要求。 02 敏感权限、个人信息采集行为辨认定位基于动态检测及动静监测技术辨认能力,逐个列举 App 及第三方 SDK 中敏感权限调用和个人信息采集行为状况,并溯源代码调用具体位置。 03 定制化场景检测基于动静运行时检测,对理论采集产生的业务场景进行定位。反对提供测试用例,联合业务场景检测业务采集个人信息的危险行为和频率。 04 业余解读依据国家相干法律法规、四部委工作组的检测根据,以及行业实用规定,联合 App 隐衷协定,针对 App 理论采集、应用个人信息行为检测后果逐条剖析解读,提供与法规对照的检测报告,及整改倡议。 E N D

September 10, 2021 · 1 min · jiezi

关于移动应用开发:技术干货-应用性能提升-70探究-mPaaS-全链路压测的实现原理和实施路径

业务背景随着挪动开发行业的步入存量时代,App 整体架构的负载能力、以及各个环节的优化逐渐成为各个开发者们关注的重点。 压力测试就是实现以上性能的次要计划。个别能够基于压力测试: 测试后端业务的负荷瓶颈;评估整体架构性能;业务稳固峰值;排查出各节点的单薄关系;优化系统资源;防止短板效应;为经营提供精确的用户承载量作为作证,防止流动/新利用的上线带来的突发流量造成的用户体验不佳。 明天,咱们将为大家介绍全链路压测计划的是实现原理和施行门路。 全链路压测与原理通常咱们能够简略的把负载性能=单机性能*机器总量这一公式套用到预估的计划中,然而在理论的场景下,经常会波及到大量的业务节点,如DNS,网关,数据库等环节,都有可能是导致整体业务性能的瓶颈,因此理论服务能力可能与预期存在较大误差。 个别用户会通过 loadrunner 等计划实现生产环境下的服务器性能压力测试,然而在 mPaaS 利用中,简单的部署无奈通过 MGS 网关,昂扬的费用等难点应运而生,为了解决这些痛点。 mPaaS 团队这边根据多位客户的述求,提供出 MGS 全链路压测计划。 区别于以往的测试计划,全链路压测计划中最大的不同是视角上的不同,站在客户端角度上作为切入点,将整个服务端链路作为一个黑盒,以实在的 request 和 response 作为评估的根据,模仿实在的业务申请,实在的数据流量,实在的用户习惯,来达到得出尽可能实在的评估后果。 链路梳理在一个规范的数据链路中,个别为以下模型 而在全链路压测中,咱们把整体的服务端实现视为一个黑盒,因此咱们所需关注的焦点聚焦在前半段,重点能够概括为: 1.客户端申请构建; 2.客户端申请发送并通过 MGS 网关; 3.客户端解析 MGS 网关返回的 response 并做出正确处理; 4.实现高并发的客户端申请集群。 以上再次梳理,能够演绎出以下难点 难点1 客户端申请构建 mPaaS 挪动网关 RPC 通信是在 HTTP 协定根底之上的实现的一种标准化接口方式,在复用 HTTP 申请规范的前提下,定义了一套数据交换格局,采纳Header,Body 作为理论辨别,能够近似了解为,通过Header 中的Operation-Type做为实在api指向,将body局部根据规定封装后进行转发。 在该步骤中,咱们以 JMeter 作为实现计划,Jmeter 灵便的脚本个性能够良好的实现客户端的实在申请模仿。 难点2 数据加解密 mPaaS 挪动网关 RPC 申请特有的数据加密形式构建申请中比较复杂的局部。客户侧已有的测试计划不能笼罩这部分能力,因而往往抉择敞开网关服务端验签和加密性能施行压测。 这种形式的隐患在于无奈预计加解密给网关服务器带来的计算压力。 依据教训,不同的加解密算法配置,对网关的吞吐量有 20% ~ 40% 影响。在此阶段,由金融线 SRE 团队基于用户生产环境定制开发的 JMeter 插件 MGSJMeterExt,该插件逆向实现了申请体的加密和解密过程,使得压测脚本的编排能够包含加密局部。 ...

August 30, 2021 · 2 min · jiezi

关于移动应用开发:排查指南-两个案例学会从埋点排查-iOS-离线包

离线包原理以一次启动离线包的流程为例,离线包的加载流程分为两种场景,第一种是离线包下载好的场景,流程如图1所示,第二种是离线包没下载好的场景,如图2所示: 图1:离线包加载支流 图2:离线包下载流程 咱们能够从埋点来跟踪离线包具体的加载流程*: 查看本地是否有离线包,本地有则执行第四步解压,解压之后再进行校验,校验通过加载本地离线包,如果本地曾经装置过,那就不须要解压间接走解压后的流程网络申请离线包信息,这一步和上一步是异步进行的,对应的埋点有 H5_APP_REQUEST比照申请回来的离线包信息,再决定是否下载离线包,对应的埋点有 H5_APP_DOWNLOAD解压离线包,对应的埋点有 H5_APP_UNZIP如果开启了离线包验签,校验离线包的合法性,对应的埋点有 H5_APP_VERIFY、H5_AL_SESSION_VERIFYTAR_FAIL加载本地离线包,对应的埋点有 H5_AL_SESSION_MAP_SUCCESS、H5_AL_SESSION_FALLBACK留神:fallback走线上须要等到离线包申请这个异步申请回调回来之后返回的 fallback + mainUrl 确定 Webview 关上的URL。 *参考资料:离线包日志埋点 案例1:首次关上离线包白屏STEP1:依照离线包加载流程剖析,首次关上离线包肯定是须要走线上的fallback,因为本地没有,走线上之前肯定须要先晓得离线包的线上地址也就是URL,所以须要查看日志剖析是否是申请离线包信息那一步出错了。 STEP2:剖析日志关上线上离线包的时候URL为空,在离线包申请还没有回调回来之前就关上离线包,所以呈现了白屏。 STEP3:查看代码 将创立的离线包控制器作为根视图,机会过早,所以导致了该问题。 STEP4:联合客户需要给出倡议,能够应用本地预制离线包解决首次过早关上离线包呈现白屏的问题。 案例2:关上预置离线包,报错(-1009)复现demo STEP1:断网状况下关上预制离线包失败显示网络无奈连贯阐明关上预制包失败了,所以走了线上,因为没有网络所以显示网络无奈连贯,问题出在本地预制的离线包上。 STEP2:依照离线包的加载流程剖析,在本地有预制包的状况下呈现走线上的状况别离有两种状况,离线包验证签名失败和加载本地离线包失败。 STEP3:日志剖析 察看到有验签失败的字样。 STEP4:查看代码客户端是否敞开了验签,默认是开启的,如果没有敞开,那么客户端须要设置对应的公钥,或者敞开验签。 STEP5:敞开验签再试一遍,持续剖析日志: H5_AL_SESSION_FALLBACK加载本地离线包失败,最终走的线上,察看解压离线包胜利没有问题,问题出在加载离线包那一步,日志中查找到H5_APP_EXCEP离线包异样埋点,是读取数据时候失败了。 STEP6:问题有可能呈现在该离线包下面,所提供一个失常的离线包给客户做成本地预制离线包,断网关上验证,没有问题,问题就出在该离线包上。 STEP7:解开预制的离线包,察看离线包是否门路字符总长度是否超过了限度导致读取数据失败。 JS文件名称过长,导致总的字符长度超过了限度,须要客户批改离线包*。 *参考资料:生成离线包 思考和总结通过下面两个案例的介绍,咱们能够清晰的看到案例一最终呈现问题的起因是申请离线包信息这个申请没有回调回来,客户就关上离线包时没有获取到URL,问题呈现在了申请离线包那一步了,而案例二最终定位到加载本地包失败那一步。 理解了离线包的具体加载流程,再联合 nebula 容器自动化埋点日志,就能够具体定位问题到离线包加载的哪一步了。 本文作者:阿里云 mPaaS TMA 团队(杨强 荣阳) END 下周二(8.24)阿里云飞天会员日开启,音讯推送等资源包1折秒杀抢购,点击理解更多折扣详情。

August 20, 2021 · 1 min · jiezi

关于移动应用开发:华为多媒体管线服务AV-Pipeline-Kit打造灵活定制的音视频场景框架

HMS Core 6.0新增的多媒体管线服务(AV Pipeline Kit),是华为在媒体畛域又一技术凋谢。该服务通过打造灵便定制的音视频场景框架,赋予APP丰盛的音视频的解决能力,同时简化音视频采集、编辑和播放等业务开发工作量,助力影音、社交和电商等畛域利用的疾速开发。https://www.bilibili.com/vide... 多媒体管线三大能力:自定义流水线编排、视频超分和声音事件检测。自定义流水线编排能力通过主动解析插件和插件优选等技术,开发者能够在媒体利用中集成插件,实现采编播的媒体利用翻新并晋升用户体验。第三方插件在满足多媒体管线束缚根底上,可不便被多媒体管线集成,帮忙开发者高效聚焦媒体翻新业务。 多媒体管线服务为开发者提供视频超分能力,让用户观看低分辨率视频时能享受高分辨率的体验。该能力反对在视频播放过程中逐帧超分,提供实时降噪、色调加强等能力,对高分辨率视频能够加强其画质,对低分辨率视频可能进行超分以晋升观看体验,反对270P-720P等多种分辨率,最高可实现3倍超分。视频超分还具备自适应分辨率和码率调整的个性,进一步晋升应用体验。 声音事件检测也是多媒体管线服务的一大能力,反对对音频播放中的声音进行声音事件检测。多媒体管线服务提供的声音事件检测能力辨认速度更快,准确率更高,目前反对敲门、车鸣声等13个类别的声音事件检测。开发者能够使用此能力辨认如宝宝哭声,小猫叫声等,晋升家庭辅助揭示体验。 多媒体管线服务具备易用性、高性能以及低功耗三个特点。在易用性上,多媒体管线服务预约义Pipeline反对根底采编播性能,还反对Pipeline快捷拓展,为开发者提供丰盛的开发辅助工具。在高性能方面,多媒体管线服务使用对立的数据格式封装,反对主动援用技术,真正实现模块间的数据“零拷贝”;而通过硬件能力检测技术,优先调用具备硬件加速能力节点,相比传统Pipeline性能晋升超10%。从低功耗角度来讲,多媒体管线服务提供的多模态媒体框架通过缩小数据拷贝以及数据格式转换,优先加载硬件能力来降低功耗开销,功耗收益超20%,帮忙利用达到更好的性能与功耗均衡。 多媒体管线服务可能无效解决目前音视频利用开发难,功耗压力大,影响用户应用体验的痛点,让开发者聚焦翻新。随着多媒体管线服务能力的凋谢,会有更多优质利用为用户带来更好的体验。 HMS Core黑科技,理解一下 拜访华为多媒体管线服务官网,理解更多相干内容获取华为多媒体管线服务开发领导文档华为多媒体管线开源仓库地址:GitHub、Gitee华为HMS Core官方论坛解决集成问题请到Stack Overflow 点击右上角头像右方的关注,第一工夫理解HMS Core最新技术~

July 16, 2021 · 1 min · jiezi

关于移动应用开发:5分钟给商品建立3D模型我是如何做到的

发问:3D模型展现商品不香吗?香!当然香。 当3D建模成为电商网购利用的左膀右臂,大同小异的产品首图就有了面目一新的出现风貌!商品3D模型360°全方位展现,细节更丰盛,辅以线上虚构“看、试、穿、戴”,提供靠近实物的差异化网购体验,助力高效晋升用户转化。 3D模型虽漂亮,然而,时下成果佳的3D建模技术因其较高的老本而使得让宽广需求者望而生畏。 技术门槛高:业余人员手工制作,师带徒传承,学习老本高;工夫老本高:实现一个简略物体的低模模型,工作量以小时起步,高模模型耗时更长。消耗高:单个商品的业余建模老本高,平均价格达到上千元,简单模型更贵;华为挪动服务最新凋谢的3D建模服务,助力轻松建模。用户只需应用一般的RGB相机,通过拍摄物体的不同角度图像,便可实现物体的3D几何模型和纹理的自动化生成,为利用提供3D模型构建、预览等能力。如在电商鞋子展现的场景,您能够通过此能力主动生成鞋子3D模型,用于3D展现,用户可360°随心放大或放大商品,为用户提供差异化的购买体验。   成果示例 技术计划 3D物体建模能力由端云协同实现,端侧负责采集RGB图像,通过盘绕物体一周拍摄多张图像,从而获取物体的不同角度图像,拍摄结束后上传至云端实现3D物体建模。云端建模的流程及关键技术包含指标检测宰割、特色检测与匹配、稠密点云计算、浓密点云计算以及纹理重建等模块。最终输入业界通用的3D模型格局(.obj文件),面片数约40K~200K。 开发筹备1.配置集成的SDK包在利用的build.gradle文件中,dependencies内增加3D建模服务的SDK依赖 // 3D Modeling Kit SDKimplementation 'com.huawei.hms:modeling3d-object-reconstruct:1.0.0.300'2.配置AndroidManifest.xml关上main文件夹中的AndroidManifest.xml文件,能够依据场景和应用须要,配置读取和写入手机存储以及相机权限,在<application>前增加 <!-- 往sdcard中写入数据的权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 应用相机的权限 --><uses-permission android:name="android.permission.CAMERA" />开发步骤1、配置存储权限申请在MainActivity的onCreate()办法中,首先对手机存储的读取权限进行判断,如果未获取权限,则通过requestPermissions进行申请。 if (EasyPermissions.hasPermissions(MainActivity.this, PERMISSIONS)) { Log.i(TAG, "Permissions OK");} else { EasyPermissions.requestPermissions(MainActivity.this, "To use this app, you need to enable the permission.", RC_CAMERA_AND_EXTERNAL_STORAGE, PERMISSIONS);}查看权限申请的后果,如果有权限初始化界面,没有权限提醒用户开启。 @Overridepublic void onPermissionsGranted(int requestCode, @NonNull List<String> perms) { Log.i(TAG, "permissions = " + perms); if (requestCode == RC_CAMERA_AND_EXTERNAL_STORAGE && PERMISSIONS.length == perms.size()) { initView(); initListener(); }}@Overridepublic void onPermissionsDenied(int requestCode, @NonNull List<String> perms) { if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { new AppSettingsDialog.Builder(this) .setRequestCode(RC_CAMERA_AND_EXTERNAL_STORAGE) .setRationale("To use this app, you need to enable the permission.") .setTitle("Insufficient permissions") .build() .show(); }}2.新建3D物体建模配置器// Initializing the RGB ModeModeling3dReconstructSetting setting = new Modeling3dReconstructSetting.Factory() .setReconstructMode(Modeling3dReconstructConstants.ReconstructMode.PICTURE) .create();3.新建3D物体建模引擎并初始化工作调用Modeling3dReconstructEngine的getInstance()接口,传入以后利用的上下文创立3D建模引擎实例 ...

July 15, 2021 · 2 min · jiezi

关于移动应用开发:华为HMS-Core-60全球上线

2021年7月15日 HMS Core 6.0于今日正式在华为开发者联盟官网上线,为利用开发者带来了多项全新的凋谢能力,并对已有的个性及服务进行了降级。目前,用户设施内置的HMS Core 6.0 APK已全面完成更新降级,开发者可登录华为开发者联盟官网下载应用各项服务。HMS Core 6.0将华为在媒体利用、图形渲染、网络减速等多个劣势畛域的先进技术进一步凋谢。在媒体畛域,新增了多媒体管线服务(AV Pipeline Kit),通过音视频开发框架及视频超分、声音事件检测等插件,帮忙开发者解决音视频利用开发难、功耗压力大的痛点。在图形畛域凋谢了3D建模能力(3D Modeling Kit),为开发者提供基于AI技术的物体建模、材质制作等能力,用户仅需一般RGB相机,通过手机便可高效生成3D模型,让电商类、动画制作类App霎时取得趣味交互,目前该能力反对Android全平台。 此外,在现有的图形计算服务(CG Kit)中,业界首次在挪动端实现了基于体渲染技术的动静光影雾效“体积雾”,助力手游晋升室内场景画面的真实感和表现力。该个性也已面向Android所有反对Vulkan的设施凋谢。 在华为当先的通信畛域,Network Kit降级了AI网络环境预测,可基于业务的网络拜访法则、以及差异化网络需要,进行网络参数调优,让利用的网络时延升高超过20%。 HMS Core 6.0还凋谢了包含会员、视频剪辑、音频剪辑等多项服务,开发者能够登录华为开发者联盟官网查看更多详细信息。 作为华为软硬件和云端凋谢能力的合集,HMS Core始终以开发者为核心,一直将华为劣势的核心技术及能力凋谢给更多开发者,为跨平台、跨设施的利用提供了一致性的优质体验。HMS Core反对包含Web利用、快利用,以及Android、HarmonyOS等的利用和服务开发,同时也反对在平板、智慧屏、智能手表及车机等设施调用。此外,HMS Core还反对丰盛的第三方开发框架,如React Native、Cordova等。 拜访华为开发者联盟官网,理解更多相干内容获取开发领导文档华为挪动服务开源仓库地址:GitHub、Gitee关注咱们,第一工夫理解 HMS Core 最新技术资讯~

July 15, 2021 · 1 min · jiezi

关于移动应用开发:300行代码实现语音搜索购物的技术分享

“阿强,手写板怎么又不见了?” 最近,程序员阿强的那位敢于尝试新事物的外婆,又迷上了网购。在不太费劲儿地把购物软件摸得门儿清之后,没想到,本认为顺畅的网购之路,卡在了搜寻物品上。在手写输入环节,要么误操作,无心中更换到不相熟的输入法;要么误按了界面上形象的指令字符……于是阿强也常常收到外婆发来的求助。 其实,不止是购物利用,时下智能手机里装载的大部APP,都是歪斜于年老群体的交互设计,老年人想要体验学会应用,很难真香。 在一次次急躁领导外婆实现操作后,阿强,这个成熟coder给本人提了个需要:晋升外婆的网购体验。不是一味让她适应输入法,而是让输入法投合外婆的应用偏好习惯。 手动输出易出错,那就写个语音转文字的输出办法,只有启动录音按钮,实时语音辨认输出,简略又快捷,外婆用了说直说好! 成果演示 实时语音辨认和音频转文字有丰盛的应用场景1、游戏利用中的使用:当你在联机游戏场组队开黑时,通过实时语音辨认跟队友无阻沟通,不占用双手的同时,也防止了开麦露出声音的难堪。。2、办公利用中的使用:职场里,耗时长的会议,手打码字记录即低效,还容易漏掉细节,凭借音频文件转文字性能,转写会议探讨内容,会后对转写的文字进行梳理润色,事倍功半。3、学习利用中的使用:时下越来越多的音频教学材料,一边观看一边暂停做笔记,很容易打断学习节奏,毁坏学习过程的完整性,有了音频文件转写,零碎的学习完教材后,再对文字进行温习梳理,学习体验更佳。 实现原理华为机器学习服务提供实时语音辨认和音频文件转写能力。实时语音辨认反对将实时输出的短语音(时长不超过60秒)转换为文本,辨认准确率可达95%以上。目前反对中文普通话、英语、中英混说、法语、德语、西班牙语、意大利语、阿拉伯语的辨认。 反对实时出字。提供拾音界面、无拾音界面两种形式。反对端点检测,可精确定位开始和完结点。反对静音检测,语音中未谈话局部不发送语音包。反对数字格局的智能转换,例如语音输入“二零二一年”时,可能智能辨认为“2021年”。音频文件转写可将5小时内的音频文件转换成文字,反对输入标点符号,造成断句正当、易于了解的文本信息。同时反对生成带有工夫戳的文本信息,便于后续进行更多功能开发。以后版本反对中英文的转写。 开发步骤1、开发前筹备配置华为Maven仓地址并将agconnect-services.json文件放到app目录下:关上Android Studio我的项目级“build.gradle”文件。增加HUAWEI agcp插件以及Maven代码库。在“allprojects > repositories”中配置HMS Core SDK的Maven仓地址。在“buildscript > repositories”中配置HMS Core SDK的Maven仓地址。如果App中增加了“agconnect-services.json”文件则须要在“buildscript > dependencies”中减少agcp配置。buildscript { repositories { google() jcenter() maven { url 'https://developer.huawei.com/repo/' } } dependencies { classpath 'com.android.tools.build:gradle:3.5.4' classpath 'com.huawei.agconnect:agcp:1.4.1.300' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}allprojects { repositories { google() jcenter() maven { url 'https://developer.huawei.com/repo/' } }}参见云端鉴权信息应用须知,设置利用的鉴权信息。 ...

June 24, 2021 · 5 min · jiezi

关于移动应用开发:从-Alpha-到-Beta这次是-New-mPaaS

新视觉:金盏花黄的盛放随着过来一年与泛滥开发者交换、能力上达成单干,mPaaS 进一步明确了「容器+动静公布」的产品主张。 作为支付宝孵化的挪动开发平台,mPaaS 不仅助力泛滥金融、政务机构顺利搭建性能持重、体验晦涩的端上 App,更是致力于精简产品状态,晋升接入体验,为中国泛滥挪动端开发者提供动态化的跨平台能力。 随同 mPaaS 新的产品主张,咱们对 mPaaS 品牌进行了降级,设计团队选用可能代表年轻化、踊跃有生机的金盏花黄作为 mPaaS 品牌色,用来传播 mPaaS 心愿给开发者提供更简略、更高效挪动开发场景的愿景和价值观。 点击这里,立刻拜访 mPaaS 全新官网。 新容器:自定义 View 全新个性随着全新视觉零碎的迭代,mPaaS 容器也带来了新版本。 在此次新版本中,咱们实现了小程序容器的自定义 View 能力。 开发者能够在小程序组件中,自定义 class 继承 MPBaseEmbedView 并实现 getView 办法,获取 Android View 返回给小程序。同时,咱们也反对针对自定义 View 渲染参数进行自定义、自定义音讯及事件并发送至自定义 view 中。 这意味着,开发者能够基于新能力,联合细分业务场景,实现更为多元化、丰盛的业务交互;同时,让更为简单的业务模块也能借助自定义 View 的形式,联合容器能力实现动态化更新。 *Android 端小程序容器基线降级文档已上线,详情请拜访链接:https://help.aliyun.com/docum...*iOS 端全新小程序容器还在筹备中,钉钉搜寻「32930171」退出 mPaaS 技术交换群,get 最新产品动静。 E · N · D CodeDay#6 成都站报名ing,立刻报名>>

June 11, 2021 · 1 min · jiezi

关于移动应用开发:友盟UAPM-移动应用性能体验报告-APM越发受到关注第三方监控工具覆盖已超四成

近日,国内当先的全域数据智能服务商——友盟+,公布了《友盟+U-APM 挪动利用性能体验报告》。据悉,友盟+于去年将原挪动剖析U-App谬误剖析模块正式降级为U-APM利用性能监控平台,通过近一年的察看,通过DEM 视角剖析挪动利用端的性能体现公布这份报告,旨在帮忙开发者清晰理解行业动态,精准定位本身产品地位。 利用性能治理越发受到关注,SDK 覆盖率整体超过四成 互联网存量时代,个体用户的体验晋升变得越来越重要,利用性能治理(APM)受到开发者更多关注。截至2021年2月,利用商店上架利用集成三方SDK的比例42.2%,且持续保持增长趋势。不同于其余开发者工具,利用性能治理 SDK 具备较强排他性,92%的利用仅会抉择一款利用性能治理产品。 从不同规模来看,规模越大的利用越器重对稳定性能的监控, SDK 覆盖率越高。值得关注的是,中长尾利用也更加重视用户体验的晋升,在抽样长尾利用中仍有37%集成利用性能治理SDK。 影视观看行业对稳定性和性能体验最为器重,SDK 覆盖率高达五成 从不同行业来看,除手机丑化/母婴/拍照摄影/工具类利用外,其余行业 SDK 覆盖率均超过40%,尤其影视行业APP对晦涩水平要求较高,SDK 覆盖率高达五成。 利用性能治理 SDK 覆盖率随利用规模增大而回升 头部利用成熟度高:随着利用规模的逐渐扩充,开发者领有用更多的资源和工夫精细化打磨用户体验,因而也更加器重稳定性能的监控和治理, SDK覆盖率越高。 中长尾利用逐步器重:互联网存量时代,中长尾利用也更加重视用户体验的晋升,在抽样长尾利用中有37%集成利用性能治理SDK。 对于不同利用而言,抉择一款适宜本身APM产品,须要同时思考老本均衡、本身产品情况、用户应用情况等诸多因素。因为排他性的存在,选定一款APM就意味着惟一的存在,这还须要开发者在一开始便思考SDK集成难度、监控有效性、技术稳定性、SDK合规性、技术支持度以及开发方的前期配合度等。 现在行业中如友盟+U-APM能够帮忙开发者通过2行代码实现SDK的集成,从而取得实时监控、智能告警、用户细查和云真机测试等诸多能力。 该产品源于阿里巴巴外部外围团队弱小的谬误捕捉技术及友盟+超强的数据处理能力,保障U-APM为客户提供稳固牢靠的性能监控与测试服务,同时其可能反对秒级响应的我的项目服务群实时帮忙开发者解答问题,并在SDK隐衷合规问题等方面可能提供帮助排查,反对技术接入,前期也会提供技术专家的征询解答,从而杜绝开发者的后顾之忧。 友盟+的U-APM利用性能监控平台基于弱小的捕捉采集能力,疾速定位谬误本源,提供用户维度的解体统计,疾速还原解体现场,并设立多种正告通道,助力开发者7*24监控利用状况。 据悉,报告还就挪动利用解体现状、手机品牌解体状况进行了具体的剖析,完整版报告可到友盟+官网下载。

June 1, 2021 · 1 min · jiezi

关于移动应用开发:快成物流科技-x-mPaaS-小程序容器加持下的技术架构提质增效

导言从 2017 年开始,GMTC“挪动技术大会”就更名为“大前端技术大会”。倒退至今,混合开发、原生开发、前端开发等概念正在深度交融,组成“大前端”团队。大前端团队如何选型技术?如何疾速上手?如何高效协同?让咱们看看快成科技如何解决这一问题。缘起两地三团队快成科技是网络货运畛域的领军科技企业,畛域排名市场前三,平台有 3w+ 大宗商品货主,将货单公布到平台,由 60w+ 的卡车司机接单承运,每年产生 120亿 的运费交易额。 以司机端为例,须要承载从发单抢单到从进出场治理,从在途门路监控到金融白条加油加气等一系列互相强关联、流程链条长的业务。这些工作由两地三个研发团队,独特分工协作实现。 在 7*24 小时不间断的客户服务和每月 2-3 次发版的高度迭代中,技术框架瓶颈逐步凸显,具体包含: 在零碎框架方面,初始框架是原生 App+HTML5,传统 web 存在启动白屏和性能响应不晦涩,大大降低了用户体验;在发版周期方面,研发部门多,产品链条长,局部企业须要更多的共性定制化服务导致发版期待周期不统一,频繁的发包更新不仅升高了经营效率,也给客户带来了频繁更新的困扰;在体验一致性方面,原生开发依赖零碎框架,因为原生个性不同,而导致各厂商多渠道平台中差异化凸显,多平台性能、体验差异较大;在多部门合作方面,罕用开发语言、前端 JavaScript 框架等不尽相同,不能及时依据需要张弛和上线 DDL 来灵便调配技术人员合作开发。在快成科技业务继续高速倒退的背景下,优良的技术架构是“提质增效”的保障,零碎重构势在必行。快成的小伙伴们开始寻找优良的架构,解决场景问题。 选型四维度快成小伙伴针对发现的问题,探讨出四个选型维度: 框架成熟度:简略来说,就是这个新技术是否靠谱,百亿的业务压力,没有太多的试错空间;迁徙老本:如果想得到新技术带来的收益,须要咱们付出什么代价,例如新技术的学习老本、原来架构的革新老本等;社区气氛:次要是看跟进这个技术的人够不够多、文档资料是否丰盛、遇到问题是否失去帮忙等;考量根底上兼顾性能、跨平台和动态性。定好技术选型考量指标之后,团队对常见的跨平台计划诸如 React Native、Weex、Flutter 和小程序进行了一系列的调研以及 Demo 制作,横向比拟如下: 技术选型调研后果React Native 和 Weex• 启动工夫慢、帧率不如原生; • 迁徙老本较大,开发者需封装一层较重的中间层,对研发人员要求较高。Flutter性能和效率至上然而动态化能力十分无限。小程序自身并非一种跨平台开发计划,无奈利用自身 app 关上,更看重渠道劣势。正在进入技术选型窘境的时候,快成物流科技偶尔接触到了源自支付宝技术框架的mPaaS,通过应用 mPaaS 小程序容器,整合 mPaaS 框架、离线包和复用 h5 插件,依靠于性能强劲的 web 渲染引擎,完满合乎了咱们对技术选型的冀望与要求。 入手试试看选定技术选型之后,在重构初期,针对我的项目工程建设以及划分上,咱们共事之间进行了一场强烈的头脑风暴,最终选定了在多部门合作前提下进行轻量组件化并行开发多个小程序并进行动静下发的计划。 快成团队从协同、技术等多角度,进行框架的逐渐导入。 如需理解残缺内容详情,欢送观看 CodeHub#5 全程回放 1. 多团队协同 2. 实在场景测试真机预览与调试问题,首先要设置好白名单,设置形式可参考文档,而后在原生端依据文档进行相应的配置和代码书写,最初须要留神的是 IDE 生成的二维码须要应用咱们 App 的扫码能力扫描(可接入 mPaaS 的扫码组件),用支付宝扫一扫是打不开的。 ScanService service = LauncherApplicationAgent.getInstance().getMicroApplicationContext() .findServiceByInterface(ScanService.class.getName());ScanRequest scanRequest = new ScanRequest();scanRequest.setScanType(ScanRequest.ScanType.QRCODE);service.scan(this, scanRequest, new ScanCallback() { @Override public void onScanResult(boolean success, Intent result) { if (result == null || !success) { showScanError(); return; } Uri uri = result.getData(); if (uri == null) { showScanError(); return; } // 启动预览或调试小程序,第二个参数为小程序启动参数 MPTinyHelper.getInstance().launchIdeQRCode(uri, new Bundle()); }});3. 外围问题解决在同一小程序不同页面跳转传参的时候咱们遇到了大参数传递被截断的问题。 ...

May 21, 2021 · 1 min · jiezi

关于移动应用开发:技术干货-轻松两步完成向-mPaaS-小程序传递启动参数

前言在局部场景下,须要向小程序的默认接管页(pages/index/index)传递参数。 本文将以传递 name 和 pwd 参数为例,别离介绍此场景在 Android 小程序和 iOS 小程序中的实现过程。 前提条件已参照 疾速开始 文档接入了小程序组件。 Android 小程序1.在客户端增加启动时跳转页面的参数信息。如下所示: Bundle param = new Bundle();String query = "name="+Uri.encode("123")+"&pwd="+Uri.encode("456");param.putString("query",query); //设置参数MPNebula.startApp(appId:"2020121620201216",param);URL 启动传参时,传递参数的字段为 query;获取参数时,通过解析 query 字段获取。startApp 参数阐明: appId:小程序的 ID,能够从 mPaaS 控制台查看。param:Bundle 对象,能够向 Bundle 对象传递申请参数,key="query",value="键值对";多个参数两头用(&)隔开。留神1:小程序框架会对每对自定义入参的键值对的 value 进行 uri decode。因而,请对入参键值对的 value 进行 uri encode。留神2:小程序框架不会对自定义入参的键值对的 key 做任何解决。因而,请不要对 key 设置特殊字符,避免小程序侧无奈辨认自定义参数。2.小程序获取参数。从 onLaunch/onShow(options) 办法的参数 options 中获取。 存储 app.js 会获取客户端向小程序传递的参数并保留到全局变量 globalData 中,应用时从 globalData 间接取值或更新值。如申请头里的 token、user_id 等参数,从 Native 传递过去后,保留到 globalData 中,应用时间接取值。 iOS 小程序1.在客户端增加启动时跳转页面的参数信息。如下所示: ...

May 7, 2021 · 1 min · jiezi

关于移动应用开发:技术干货-如何在-Library-中使用依赖-mPaaS

应用场景在应用 mPaaS 框架过程中,有时须要复用模块。复用时须要依照应用 Module 依赖的形式增加模块。本文以将复用 mPaaS 扫码组件的 Module 为例进行阐明。 前提条件已依照原生 AAR 接入形式将工程接入 mPaaS。 操作步骤在 Android 工程中创立 Android Library 类型的模块 “scan”。 在新创建的 scan 模块的 build.gradle 文件中增加 api platform("com.mpaas.android:$mpaas_artifact:$mpaas_baseline") 。示例如下:dependencies { …… //moudle里应用mPaaS组件性能时,必须增加 api platform("com.mpaas.android:$mpaas_artifact:$mpaas_baseline") …… }通过 Android Studio mPaaS 插件为 scan 模块装置扫码组件。具体菜单门路为:mPaaS > 原生 AAR 接入 > 配置/更新组件 > 开始配置。装置后,扫码组件组件会主动加载。 配置 App 主工程。plugins { id 'com.android.application' ...... //必须在app下的build.gradle增加baseline.config(基线) id 'com.alipay.apollo.baseline.config'}调用组件模块。在应用扫码组件的中央,导入 scan 模块。dependencies { api platform("com.mpaas.android:$mpaas_artifact:$mpaas_baseline") .... api project(':scan')//扫码组件}撰文:刘启洋 ...

April 26, 2021 · 1 min · jiezi

关于移动应用开发:mPaaS-月度小报-CodeHub4-在线教育应用的开发实践香港站正式开服上线

本月亮点速览 Easyconfig 辞别白屏期待:客户端从天而降的“白屏”期待应该如何排查?行业资讯: 货运物流数字化降级行业交流会:如何针对货运物流行业,打造性能晦涩、用户沉闷、终端平安的挪动利用?安卓 WebView 致利用间断闪退 mPaaS 动静: CodeHub#4 荷小鱼:在线教育利用的开发实际全新地区——中国(香港)正式开服:助力 App 实现动态化更新与公布扫码组件上架 APICloud、CSDN,立刻 get 支付宝同款Easyconfig白屏期待问题如何排查开发者在接入 mPaaS 容器后,配合挪动公布服务,能够让客户端不便地从本地加载业务包,极大地晋升了利用的加载效率。但在接入过程中一些轻微的谬误,可能会导致离线机制生效,以致利用在网络好的状况下会有一个短暂的“白屏”等待时间,在网络较差的状况下,甚至齐全无奈实现页面的加载。 因为 mPaaS 容器存在 fallback 机制,所以即便“离线”失败,容器也是能够“失常”加载业务包的内容的,开发阶段开发者往往容易疏忽掉“离线”对性能层面的影响。 所以倡议开发者在联调和上线的过程中,对于离线机制的工作状况予以检查和确认,能够通过抓包的伎俩从内部确认没有额定的、非必要的 fallback 申请产生,最终的目标是施展离线包的性能劣势。 理解更多:问题排查 | 客户端从天而降的“白屏”期待 行业资讯货运物流数字化降级行业交流会来自上海大学古代物流钻研核心主任的储雪俭传授示意:“明天探讨智慧物流,相对不是欲速不达的,销售渠道下沉造成物流日益碎片化。面对以后业务碎片化,咱们须要思考的是在业务碎片化的情景下,怎么通过平台做整合,建设你的专业化运维池?” 在整个货运物流行业的经营模型里,挪动端目前对用户来说是不可或缺的力量。如何针对货运物流行业,打造一个性能晦涩、用户沉闷、终端平安的挪动利用呢? 理解更多:货运物流挪动端解决方案:为货运物流行业打造高性能、高粘性的“双端”触点 WebView 致安卓利用间断闪退据外媒 9to5 Google 报道,大量安卓用户突遇利用间断闪退的状况,有用户发现通过卸载安卓零碎 WebView 后能够进行闪退。目前,谷歌曾经公布了利用解体问题的永恒修复:波及更新 Android System WebView 和 Chrome 浏览器 89.0.4389.105 版本升级。 实际上,Android 碎片化问题自其诞生之初业已存在,而且目前看上去并没有好的解决方案。不同零碎、不同厂商中的浏览器内核同样存在差别,导致层出不穷的兼容性问题令泛滥安卓开发同学头疼不已。 为了彻底解决并且掌控这些问题,mPaaS 集成独立的 UC 浏览器内核,同时继承了支付宝深度利用的容器及离线包:围绕 WebView 所产生的任何平安问题,开发者能够通过 mPaaS 能够在第一工夫修复并公布 理解更多:技术干货 | 深度解构 Android 利用面临紧急发版时的救星计划:mPaaS 热修复——DexPatch mPaaS 动静CodeHub#4 回顾:在线教育利用的开发实际作为启蒙教育利用,「荷小鱼」的 App 页面除了须要嵌入根底框架代码和页面逻辑代码外,还须要嵌入多个字体库和多个音视频文件。资源的多而大,导致页面非常容易收到网络的影响:网络不稳固时容易文件失落、白屏加载资源工夫长、造成网络线程阻塞等。 同时,也让 App 更新资源变得艰难了很多:无奈实时更新下发最新资源、缓存生效等。为技术团队在更新版本和调修 Bug 上造成了很大的妨碍。 ...

April 9, 2021 · 1 min · jiezi

关于物流系统:货运物流移动端解决方案为货运物流行业打造高性能高粘性的双端触点

从 2020 年倒退网络货运以来,在互联网和大数据的合作下,传统的物流企业逐步转向信息化模式,在政策的一直推动下,网络货运平台也在逐步走向智慧化,改善了物流业的零散、层级较多的现状,助力物流企业实现规模化、集约化的倒退。 智慧物流,是将来货运物流物流的发展趋势,作为我国国民经济重要组成部分,货运物流行业也将走向数字化时代。 在刚刚闭幕的货运物流数字化降级交流会里,来自上海大学古代物流钻研核心主任的储雪俭传授示意: “明天探讨智慧物流,相对不是欲速不达的,销售渠道下沉造成物流日益碎片化。面对以后业务碎片化,咱们须要思考的是在业务碎片化的情景下,怎么通过平台做整合,建设你的专业化运维池?” 而在整个货运物流行业的经营模型里,挪动端目前对用户来说是不可或缺的力量。如何针对货运物流行业,打造一个性能晦涩、用户沉闷、终端平安的挪动利用呢? 来自蚂蚁团体 mPaaS 的解决方案架构师蒋平,给出了源自支付宝在挪动开发畛域的十年教训积淀。 货运物流行业挪动端现状倒退“双端”是网络货运平台增长的源能源 蒋平认为,在从互联网的思维模式来看,在整个货运物流平台里次要偏差两个端:货主端与车主端,他们造成了一个双轮驱动模型,会独特驱动物流平台的倒退。 双轮驱动的过程中,所以有更多的车主,就象征能够笼罩更多的地区、维度,提供更多车的型号和服务,有更多的货主,就会对货运物流平台产生更多的诉求。 在整个经营模型里,挪动端目前对用户来说是不可或缺的力量。 站在货主和车主的角度来看,他们对APP的诉求十分强烈——对车主来说,须要不停抢单做生意,在物流 App 上取得营收。对货主来说,须要想方法取得更多的存单,而且因为监管的要求,须要做很多相似人脸识别、指纹识别的人机合作要求。 这些要求对 App 的运算能力和网络要求很高,如果没有好的架构体系,很难承载 App 对于当初用户的冀望,并达到他们的业务诉求。 用户对性能的要求:在支付宝、淘宝等各类超级 App 的心智影响下,当初所有用户对手机上的 App 性能都有极高的应用要求,性能不佳的 App,在用户手里很难活到第二次应用。 业务对更新的要求:从阿里的经营体系来看,咱们的 App 市场曾经倒退到了 4.0 甚至更高阶的维度,除了须要从根本维度满足用户的直观体验和信赖要求,让用户顺畅地应用我的 App 之外,互联网时代下的业务场景疾速更迭,也在要求技术研发团队须要随时更新咱们的业务诉求。 平台对经营的要求:对平台型的企业或 App,经营是一个重要需要。App 公布进来当前,好的经营伎俩,会晋升 App 的活跃度,并达到很高的应用效率。 mPaaS 的价值体现为货运物流行业打造高性能、高粘性的“双端”触点 基于各大超级 App 对用户应用体感的造就,每个用户的应用习惯以及对 App 的期望值当初曾经具备肯定的高度了。性能问题也成为了每一个利用须要面临解决的首要难题。在 mPaaS 的既往案例中,无数次地帮忙客户实现一秒启动 App。 其二,为了满足应业务场景疾速更迭而产生的业务实时更新的需要,mPaaS 将支付宝客户端的次要技术方向齐全对外输入:通过动静公布能力,齐全能够实现一个 App 的动静公布/灰度/回滚。 除了 App 的开发与搭建,放弃其生命力的重要因素,便是其经营能力,这也是 mPaaS 具备的外围价值之一:经营流动触达。比方春节期间,App 须要做相干内容投放,投放给谁?在什么工夫投放?投放什么内容?这一系列的问题在 mPaaS 平台里有残缺的技术架构体系进行反对,能够大大不便平台型 App 车主端或货主端业务往来的过程,减少粘稠度。 此外,mPaaS 还能撑持 App 利用小程序生态体系,通过上面三个 App 开发业态,丰盛内容经营,满足平台对生态延长的诉求: ...

April 2, 2021 · 1 min · jiezi

关于移动应用开发:排查指南-当-mPaaS-小程序真机扫码时提示-应用更新错误50002

问题形容APP 扫码 mPaas 小程序弹出 toast 信息:"利用更新谬误(50002)"。 起因剖析通过扫码进行真机调试的失常流程如下: 在小程序 IDE 生成二维码,以供手机客户端扫描,同时会将小程序包上传至 mPaaS 控制台的小程序公布中。手机客户端扫描此二维码后,会被动通过 RPC 申请去拉取控制台中的 AMR 文件。当调用 MDS 小程序更新接口后,若没有获取对应的小程序信息,就会提醒“利用更新谬误(50002)”。这类问题可能的起因包含: 服务端尚未公布,包含:控制台未公布上传的小程序。小程序刚公布,但服务端尚未收到刚公布的小程序。客户端版本不在范畴内。申请信息和服务端公布的规定不匹配。排查思路1. 过滤日志在 Android Studio 控制台的日志信息中过滤关键字 DynamicRelease。查看 UnionResourceInfo 中是否有 Item 信息。 失常状况下,会含有 item 信息,示例如下: 若未蕴含 item 信息,则为异样,示例如下: 2. 查看接入真机预览和调试性能依照Android 小程序接入真机预览与调试中的步骤查看查看接入真机预览和调试是否正确。 3. 查看客户端版本范畴版本号对应 Android 我的项目 versionName 值。只有当最低版本号 < 以后 App 版本号 < 最高版本号时,能力失常的拉取小程序。若不在这个范畴,App 启动小程序时就会拉取失败,报 "利用更新谬误"。 所以举荐在最低版本输出 0.0.0.0,最高版本不填写(示意无限大)。 注意事项 因为在小程序 IDE 上传、预览、真机调试会主动将小程序上传至控制台,无需用户在控制台批改配置信息,所以在创立小程序时,不举荐从小程序公布中增加小程序包,防止出现主门路不统一。如要批改小程序,能够在小程序 IDE 中批改。 4. 查看主入口门路查看 mPaaS 控制台中填写的小程序主入口门路是否与小程序 IDE 中的主入口门路统一。 mPaaS控制台默认主门路格局为:/index.html#xxx/xxx/xxx/xxx,其中 # 前方的 xxx/xxx/xxx/xxx 是小程序的 app.json 中的 pages 中的第一个值。 ...

February 7, 2021 · 1 min · jiezi

关于移动应用开发:CodeDay5-全程回顾一场关于动态化开发实践的技术探讨

时隔一年,很快乐能在 2021 年的第一个月,重启 CodeDay。 在过来的一年中,通过跟支付宝挪动端团队和宽广开发者的交换和沟通,咱们理解到大家在波及到对于挪动利用跨端开发的过程中遇到的一些问题,同时也在思考,咱们能够对外输入那些卓有成效的技术实际计划?开发团队在面临业务高并发需要时,如何对技术模型进行迭代降级? 带着这样的疑难,咱们开展了 4 个方向的议题分享。 ▶ CodeDay#5 全程回顾 01 支付宝在动静公布方向上的摸索和演进 *公众号「mPaaS」回复“动静公布”下载残缺PPT_ 通过近十年的版本迭代,支付宝已由初始的工具性 App,转型降级成为了凋谢的、生态化的超级 App。App 性能也由最后繁多的转账、领取升级成了生存平台,不仅蕴含理财、金融等性能板块,也包容了生存、出行等各种各样的服务板块。 那怎么样让这些业务模块安稳地运行在 App 里呢?动态化开发架构就是咱们在降级摸索上的一个很重要的支点,不仅可能保障业务模块能够即时公布和更新,也能保障整个 App 高质量安稳运行。 02 兼顾包大小/易用性的容器优化之路 *公众号「mPaaS」回复“小程序容器”下载残缺PPT 小程序,是一种依赖 Web 技术,集成了原生能力的,新的挪动应用程序格局。 但因为以后小程序容器仍然存在接入麻烦、依赖抵触、接口不反对以及包体积大等问题,咱们对小程序容器围绕接入优化、性能优化、体验降级等三个方向进行了革新。 公众号「mPaaS」回复“Alpha”申请试用 03 全新的跨端开发实际 *公众号「mPaaS」回复“跨端开发”下载 mPaaS 小程序 IDE 集成在支付宝小程序 IDE 中,继承了其中的能力,并且和 mPaaS 的账号体系深度绑定,能够实现一键真机预览,真机调试,上传公布等能力。并且通过 mPaaS 的插件,能够实现多端开发。 *点击跳转《应用 HBuilder 引入构建 mPaaS 小程序》视频教程 04 小程序体系下,摸索更多可能性 *公众号「mPaaS」回复“U4内核”下载残缺PPT 在各种新技术层出不穷的明天,Web 通过了 30 多年的倒退,除了撑持传统互联网畛域的网页搭建,在挪动互联网畛域也有着十分宽泛的利用,比方小程序、信息流、会场这样的业务场景,咱们都能够看到 Web 技术的身影。 为什么 Web 能有如此弱小的生命力呢? 咱们认为有一点十分重要,就是高度标准化。因为高度标准化,它有着很强的向下兼容性和跨平台兼容性,从而能够十分宽泛地利用,而且经久不衰。然而高度标准化也会带来一个问题,就是新个性的落地十分迟缓,一个个性从提出到造成规范,到最终在浏览器中落地,须要通过多年的工夫,对于开发者来说,理解浏览器内核的停顿更有实际意义。

February 1, 2021 · 1 min · jiezi

关于移动应用开发:技术干货-mPaaS-客户端问题排查漫长的-3s-等待之谜

面对日益简单的技术世界,App 在开发、上线和运维阶段所遭逢的问题也越来越多。这些不拘一格的问题可能来自整个链路的任意环节,而不仅仅是代码层面。 对于开发者来说,排查伎俩曾经不再局限于构建代码过程中的调试,往往须要裁减排查办法,从多种路径对问题进行剖析和定位。这篇文章会和大家分享 mPaaS 开发者的一例小程序网络性能问题排查之旅。 问题背景“笑联科技”反馈基于 mPaaS 开发的 App 中,其集成的小程序拜访客户自建的 Web API 存在连贯慢的性能问题。问题复现视频如下: ▶播放问题复现视频 从问题复现的状况看,关上小程序后,页面数据的加载有一个“漫长”的期待过程。 和开发者沟通后理解到,页面初始化所必须的局部数据是通过自有的 Web API 获取到的,数据返回慢会导致页面加载的期待。另外开发者也提到,这个问题存在地域性和偶发性,既局部地区的局部用户在一段时间内会被这个问题重大困扰。 问题剖析与排查如前文所述,数据是通过 Web API 获取的,天然咱们心愿通过内部伎俩去确认这个 Web API 自身是否存在性能问题。 然而,通过浏览器或 Postman 等工具去拜访该 Web API ,均无奈复现问题,后端的响应都是毫秒级。然而因为开发者提到该问题存在地域性和偶发性,因而无奈间接排除局部起因。 因为咱们并不是 App 的间接开发者,对于这类问题,一种惯例的伎俩是抓取 HTTP 报文来察看和了解 App 背地的行为特色。比拟侥幸的是,咱们的测试用 iOS 手机能够复现问题,通过 Charles 抓取 App 报文,咱们有如下发现: Web API 的地址为:https://api.xiaolianhb.com/; 当 Charles 开启 SSL Decryption 时(中间人解密 HTTPS Body 模式),问题无奈复现。 当 Charles 敞开 SSL Decryption 时,问题能够复现,数据加载显著存在一个 3s 的期待状况。 上述景象 2 和 3 强烈暗示问题可能和 HTTPS/SSL 协定层面相干(开启 SSL Decryption 时,HTTPS 连贯由 Mac 笔记本和服务器进行;敞开 SSL Decryption 时,HTTPS 连贯由 iPhone 和服务器进行)。 ...

January 14, 2021 · 1 min · jiezi

关于移动应用开发:江苏民丰-x-mPaaS-县域小银行技术团队就12人却找到了数字化转型的秘籍

想参加将来竞争,中小银行积极参与数字化转型曾经成为必选项。金融数字化转型的大潮中,配角不只是国有大行,中小银行也踊跃沉闷在舞台上。 总部位于江苏省宿迁市的民丰农村商业银行就是其中的一家。这家前身为当地农村信用社的农商行,总资产400多亿元,依附一只规模仅有12 人的开发团队,通过应用云平台上的数字技术,单月投资仅仅1万元左右,就疾速实现了业务数字化、线上化发展,为银行业绩的稳固快速增长做出了重要奉献。 以往,相似民丰农商银行这样的很多中央中小银行经营作业模式扩散、手工化程度较高,营销获客以实地访问、网点地推等形式为主。但新冠疫情暴发以来,“非接触式”金融服务需要和数字化经营要求指数级增长,中央中小银行在晋升数字化能力方面显得尤为迫切。 中小银行大都意识到要进行改革,要进行数字化转型,但在资本投入无限、人才不足、科技实力有余等刚性约束条件下,这条转型之路到底该怎么走?绝大多数机构并没有清晰的答案。 作为一家扎根苏北的地方性法人金融机构,民丰农村商业银行也曾面临上述种种艰难,但通过一场始于2016年的改革,逐步摸索出了一条有特色的普惠金融倒退及数字化转型门路。这条路对于亟需改革的中小银行,尤其扎根县域经营的小银行而言,或者可能带来一点启发。 普惠金融的“民丰模式”提到江苏,很多人脑海里都会浮现出很多美妙粗劣的画面——小桥流水的农村,波光帆影的太湖,笔直流淌的古运河,粉墙黛瓦的古镇……但这些画面实际上绝大多数跟商贸发达的苏南地区相干。 因为江苏省区域经济倒退不均衡,人们常说“苏南富,苏北穷”。宿迁市,就位于苏北,地处鲁南丘陵与苏北平原之间,古称“钟吾”,是西楚霸王项羽的故里。当初人们晓得这座城市,更多是因为守业胜利的刘强东。依照当初划分城市级别的办法,宿迁属于三线或者四线城市。 宿迁市是典型的农业大市,也是江苏省的产粮大区,这决定了扎根当地的民丰农村商业银行服务的对象是数量大、贷款金额小的农户,是反对‘三农’的真正主力军。 2016年7月,从沭阳农商行调任民丰农商行并负责董事长的许尔波,甫一上任就开始了一场对传统信贷服务管理模式的改革,实现信贷“三台六岗”全面拆散,从机制建设上解决过来信贷业务中倒退与危险的矛盾问题。 传统信贷发展模式之下,信贷人员履行“包放、包收、包管”的一生责任制。简略来说,就是对客户的贷款一竿子插到底。 农村熟人模式之下,这种模式的确施展效用,然而这一模式带来的弊病在于,银行信贷人员与大量扩散客户之间数量上的不匹配以及随之造成的信息不对称,造成银行服务被动、效率低下,并且容易引发暗箱操作危险。 民丰农村商业银行的改革从改善岗位职能和流程下手,提出“三台六岗”模式。 具体的做法是,在传统信贷流程的“大三台”框架内打造“小三台”,行将营销与考察作为“小前台”、审批与签约作为“小中台”、管户与催收作为“小后盾”,六大岗位各司其职、高效合作、互相制衡,履行专职化、流水化、标准化作业流程,确保“业余的人做业余的事”。 “三台六岗”模式的施行,激活了民丰农商行团队的生机,成效显著。推广四年来,全行累计受理授信申请13万笔,均匀授信时长仅1.2天,审批效率较改革前晋升了3倍多;以后信贷客户总数已冲破8万户,贷款余额超230亿元,别离较改革初减少了2.5万户、87亿元,年增长率超过10%。 这种成功经验起初被业内成为“民丰模式”,全国数十家农商行前来学习交流经验。民丰农商行也没有藏着掖着,将成功经验全数分享。 在民丰农商行创始这一模式的许尔波看来,这一模式“齐全能够”在其余地区和农信金融机构推广和复制。不过,他加了一个很重要的前提:“这家银行的高管尤其是董事长,须要在策略定位上有弱小的定力,在执行和贯彻过程中有足够的气魄。” 率先启动业务线上化复盘“三台六岗”模式可能顺利推广,民丰农商行针对笼罩宿迁农村地区的授信工程能够说十分重要。早在2009年,民丰农商行即启动农户贷款集中授信流动,并最终实现了对辖区内近26万个农户家庭总额超过150亿元的授信。 全区域笼罩的授信之后,客户经理不须要一家家挨门挨户去授信、放贷,不仅较好地管制了信用风险和操作危险,也把他们从繁冗的管护压力中解放出来,市场营销拓展力量失去了极大的开释,无效地解决了危险和效率之间的固有矛盾。 当然在这背地,科技力量的撑持作用也不得不提。2016年年底,在民丰农商行针对信贷业务进行流程再塑的同时,一场推动业务线上化的改革也在紧锣密鼓地策划。 “全行启动三台六岗模式之后,咱们科技部过后就想,能施展什么作用?”作为民丰农商行研发经理的汪晓涛回顾过后的状况说,“过后民丰农商行的授信客户达到60多万,然而真正应用过信贷额度的客户约为40%左右,咱们始终在思考如何撬动剩下的60%客户。” 2016年底,中国银行业的数字化转型还没有像明天这般深刻,过后即便是江苏省内的一些大银行,在挪动端上的发力也才刚刚开始。但民丰农商行科技部联结销售部将信贷业务线上化的想法汇报下来没多久,就失去了行里高层十分踊跃的回复——可行。 说干就干。通过紧急调研、立项、招投标等流程之后,民丰农商银行找来的科技外包公司在2017年6月开发进去了一款只有线上信贷性能的App——“宿速e”。汪晓涛说,因为宿迁本地区同业外面也没有相似的线上信贷产品,再加上有很大的利率优惠,过后用户增长以及信贷申请量都很大。 背地起因也很简略,业务线上化,给贷款客户以及行里的营销经理带来了极大便当。 宿迁是苏北劳动力输入大市,农村大量青壮年外出务工。在没有推动业务线上化之前,在外务工的人如果须要申请贷款,须要跑回来,到银行柜台提交资料,还要找一两位担保人,当初手机上下载App,依靠数据智能剖析技术,用户实现注册认证之后,能够线上申请、即时签约、实时放款,全流程仅需5分钟。而以前,客户经理筹备这一套根底资料起码就须要30多分钟以上。 不用说,民丰农商行“宿速e”App藉此在省内银行零碎内一炮而红。 云上自主翻新周边兄弟银行反馈也很快,也开踊跃下马手机银行App,推动业务线上化。“给咱们提供服务的外包公司一下接到了6-7个开发App的需要,这家公司人力无限,看到咱们的零碎稳固之后,提出了撤场。” 外包公司撤场,但“宿速e”产品必定还要持续优化和裁减性能,这给没有前端App开发教训的汪晓涛出了一个大难题。“我作为整个我的项目负责人,外包公司离场之后,最关怀的是该如何保障产品的连续,然而咱们人员又十分无限。” 忽然之间,这个发展势头很好的App一下变成了有点烫手的山芋。过后,“宿速e”仅仅投放在Android端,依照布局还要开发iOS版.“咱们在挪动开发端比拟弱,只靠一个人忙不过来,如果要在每端配置两个人,人手又不够。” 汪晓涛说。 技术力量薄弱、人手短缺,这是中小型银行宽泛存在的困境,民丰农商行也不例外。尽管该行通过科技外包公司推动业务翻新,但少数状况下,外包公司其实很难精确了解银行的想法,导致后续开发出的产品不能满足银行须要。 简而言之,外包科技的模式尽管有利于管制老本,然而效率低,有时候还会贻误好的市场时机。 正是在这样的压力下, 民丰农行商银行遇到了在阿里云上刚刚对外商业化的mPaaS挪动利用开发套件。mPaaS套件是一套成熟的金融级挪动端开发解决方案,撑持了国民级App利用12306,可能一次开发多端投放,帮忙金融机构疾速搭建稳固高质量的挪动利用,将更多业务承载在挪动端。 作为阿里云新金融的主打技术产品之一,mPaaS已服务中国农业银行、广发银行,华夏银行,西安银行、南京银行、广东农信、国寿保险等多家金融机构,大幅度晋升了客户的活跃度和日均交易量。 “在阿里云下面看到这款产品之后,我对产品性能和介绍文档进行了深刻理解,很合乎咱们的状况。”汪晓涛说,再加上mPaaS产品技术团队专门到宿迁进行培训,并在钉钉上随时帮咱们解决问题,咱们本人的工程师尽管此前没有挪动端利用的开发教训,然而通过学习,很快就上手了,写很少代码甚至不写代码,通过利落拽相应的功能模块就能开发挪动利用。 2018 年初,刚从 12306 我的项目抽出身来的阿里云mPaaS工程师唐天,高铁之后再打车,辗转来到宿迁。摆在他背后的问题,是须要帮忙民丰农商行疾速解决技术力量薄弱、人手短缺与架构重构对立之间找到均衡。 唐天必须一头扎进我的项目中,帮忙民丰团队批改代码,定位问题,上手精简优化代码。当初回顾起来,唐天感叹道“客户的好学水平让人诧异,mPaaS 在后续迭代推出的新能力,他们简直都是第一工夫做了试用。” 在 mPaaS 产品技术团队的加持下,民丰农商银行得以在 2018 年底彻底重构 App,由此而来的是更晦涩的端上体验和极低的闪退率。第二年,还用时下风行的“小程序”构建了一系列生态,从付款码、扫码领取性能,到生存缴费、淘票票、天猫优选等新场景性能,通过本人独立经营就实现了挪动端App的获客和活客。  往年疫情期间,因为银行网点管控,民丰农商行的线下贷款比同期降落了十分多,而得益于业务很早就进行线上化部署,线上贷款业务反而逆势增长,达到4亿多元。 民丰农商行新发动成立的9家村镇银行因为没有及时推动业务线上化,“疫情期间贷款根本没有什么量。”得悉这样的状况,汪晓涛率领科技团队仅用1个多月,就帮忙他们开发出了线上贷款入口。 当初,挪动端App开始承当更多业务。以“宿速e”手机App贷款服务平台为载体,民丰农商行先后推出了“农e贷”“融e贷”“商e贷”“快e贷”等多种线上信用贷款种类。依照民丰农商行的布局,往年银行会有约40%的贷款业务会承载在挪动端,而在将来,这个比例会进一步增长。 而撑持这样的问题,背地的开发和运维团队至今也不过12集体,而且一年投入在阿里云mPaaS私有云上的费用也只有十几万元,单月均匀投入仅仅1万多元。 作为银行业少见的mPaaS私有云客户,汪晓涛非常渴望把这个套件举荐给江苏省农村信用社联合社,构建一套全省对立的专有云。他好几次前去游说省联社分管手机银行业务的领导,游说格调上也十分间接:你看,咱们只用这么些人,做成了这么多事! 数字化转型的样本意义从金融服务广度和深度来看,民丰农商行这样的银行是县域金融服务的主力队伍,在服务县域经济倒退、金融支农支小中作出了重大贡献。银保监会主席郭树清日前强调,县域金融是我国金融体系的重要组成部分,只能增强不能减弱。 然而要想参加将来竞争,中小银行积极参与数字化转型曾经成为必选项。然而一个残暴的事实摆在面前,相比城商行、股份行、大行等同业,它们数字化上曾经大大落后,局势十分严厉。 2020年10月,中国互联网金融协会进行的专项调研显示,在数字化转型方面,相较于大中型银行和新型互联网银行,中央中小银行总体处于初步摸索阶段。被调研中央中小银行数字化能力自评估均匀得分为2.75分(满分为5分),与大中型银行(3.41分)和新型互联网银行(3.87分)相比存在较大差距。 事实上,最近一年来有很多同类农商行到访民丰农商银行,进行学习。而民丰农商行也总是不厌其烦地分享在阿里云上翻新的实际和教训。有两家心动的兄弟银行间接把App开发的工作甚至间接交给了他们。 汪晓涛还在竭力游说江苏省级农信联社的领导,心愿江苏省内中央农信社可能对立应用一个技术平台,省内50多家农信社只须要把本身业务以小程序的模式投放在这个大平台之上,即可实现数字化转型,独特抱团参加强烈的市场竞争。“省联社也想换技术架构,咱们也始终想推动他们换。”汪晓涛笑称,“这是我短期内最大的指标。”  去年年底,民丰农商行曾思考联结阿里云一起搞一场中小银行数字化转型的论坛,向全国区域性的中小银行,介绍在云上做数字化转型教训,但因为疫情忽然暴发,打算搁浅了。 “咱们这种模式是完完全全能够复制的。” 汪晓涛说,复制胜利的要害,顶层要看一把手的气魄,上面就要看团队是不是有志愿,想做事件。他还认为,中小银行在做数字化转型上要有长远规划,找到一个能够撑持业务倒退的优良技术平台。“从零开始造轮子,并不合乎中小银行需要疾速迭代的要求。” 明天,小银行所处的环境相较几年前曾经有了彻底的扭转,经济增长大幅放缓,息差在进一步放大,另有大中型银行通过数字伎俩一直下沉,进一步争取区域内的客户……数字化能力“不及格”的县域小银行要怎样才能解围,持续连续所在区域的领先地位,民丰农行商银行的模式可能是一条不错的学习门路。 ...

January 4, 2021 · 1 min · jiezi

关于移动应用开发:排查指南-关于-mPaaSiOS-小程序打不开问题的解决方案

在咱们集成 mPaaS 插件并应用小程序的过程中,很多开发者遇到了打不开小程序的问题。明天就举例说明,开发者在实现根本接入后,尝试关上 H5 利用,但容器页面显示谬误提醒“设置标签”时,应该如何解决。 常见起因mPaaS 框架在关上一个H5利用前,首先须要获知该利用离线包的根本信息。 因而客户端会被动通过RPC接口alipay.client.getUnionResource去拉取离线包信息。如果离线包信息获取失败,或没有命中要关上的指标利用,容器会提醒谬误 “零碎忙碌,请稍后再试”。 针对这类问题,排查方向包含:查看 RPC 申请是否失常、查看环境和离线包公布是否匹配等。 问题排查步骤(一)查看 RPC 申请是否失常客户端须要被动拉取离线包信息,而拉取过程依赖 RPC 申请,如果RPC 链路存在问题,则无奈失常获取离线包信息,导致加载失败。要确认 RPC 申请是否存在问题,须要在 Xcode 控制台中搜寻 alipay.client.getUnionResource 察看 RPC 申请是否失常返回。如果存在谬误,个别的错误代码包含 7XXX 或 3XXX 系列等,例如: 失常返回样例(result-status 为 1000):1. Demo[83767:2555863] [mPaaSLog] APMobileNetwork alipay.client.getUnionResource resp:{ 2. "Content-Encoding" = gzip; 3. "Content-Type" = "text/plain;charset=UTF-8"; 4. Date = "Tue, 11 Aug 2020 05:01:37 GMT"; 5. Vary = "Accept-Encoding"; 6. "mgw-traceid" = 0a1cfd401597122097726853822435; 7. "result-status" = 1000; 8. "server-time" = 1597122097739; 9. } 谬误返回样例(result-status 不为 1000):1. Demo[83383:2546279] [mPaaSLog] APMobileNetwork alipay.client.getUnionResource resp:{ 2. "Content-Length" = 0; 3. "Content-Type" = "text/plain;charset=UTF-8"; 4. Date = "Tue, 11 Aug 2020 04:50:08 GMT"; 5. memo = "%E9%AA%8C%E7%AD%BERPC%E6%8E%A5%E5%8F%A3%20%E5%8A%A0%E7%AD%BE%E6%95%B0%E6%8D%AE%E4%B8%BA%E7%A9%BA"; 6. "mgw-traceid" = 0a1d7667159712140890222728553; 7. "result-status" = 7014; 8. "server-time" = 1597121408902; 9. tips = "%E9%AA%8C%E7%AD%BERPC%E6%8E%A5%E5%8F%A3%20%E5%8A%A0%E7%AD%BE%E6%95%B0%E6%8D%AE%E4%B8%BA%E7%A9%BA"; 10. }RPC 7XXX 系列谬误的解决办法7XXX 类谬误均与 RPC 申请的签名验证过程无关,常见错误代码及起因如下: ...

December 14, 2020 · 2 min · jiezi

Flutter-App-软件调试指南

前言推荐:Android学习PDF+架构视频+面试文档+源码笔记 在实际开发中,测试和调试所占的时间比例,在总开发时间中还是比较高的。在修复产品缺陷时,我们通常需要实时观察某个对象的值。虽然可以通过Log的形式进行输出,但在某些情形下,使用更好的调试工具可以使观察这些值变得更加方便。想象一下,如果需要观察一个集合,或者一个对象中所有变量的值,单纯地使用Log需要怎么做?可能会想到用循环,也可能会在输出Log的代码中多次运用“.”运算符对对象内的变量取值。这使得编写Log输出语句本身变得复杂,再加上可能还会冒着空指针的风险。 本文涵盖了 Flutter App 代码的所有调试方式,通过本场 Chat 的学习,您将会得到以下知识: 认识 Dart 语言检查器;如何在 IDE 中进行单步调试;打印 Log 的技巧;利用 Dart 语言中的“断言”;如何查看界面 Widget 树形层级;怎样获取语义树。下面我们来逐一进行学习。 认识 Dart 语言检查器在运行应用程序前,使用Dart语言检查器,通过分析代码,可以帮助开发者排除一些代码隐患。当然,如果读者使用的是Android Studio,Dart检查器在默认情况下会自动启用。若要手动测试代码,可以在工程根目录下执行: flutter analyze命令,检查结果会稍后显示在命令行对话框中。 比如,在默认新建的计数器应用中,去掉一个语句结尾的分号: void _incrementCounter() { setState(() { _counter++ });}看到_counter++后面少了一个分号了吗?此时,运行Dart检查器,命令行输出: error - Expected to find ';' - lib\main.dart:32:15 - expected_token1 issue found. (ran in 8.9s)如何在 IDE 中进行单步调试在某些时候,我们需要进行单步调试。单步调试可以让程序逐条语句地进行,并可以看到当前运行的位置。另外,在单步调试过程中,还能实时关注相应范围内所有变量值的详细变化过程。 Android Studio中提供了单步调试功能。这和开发原生Android平台App时的单步调试方法一样,其具体步骤可以分为三步进行,第一步是标记断点,第二步是运行程序到断点处,第三步则是使用Debug工具进行调试。下面以默认的计数器应用为例,观察代码中_counter值的变化,体会单步调试的全过程。 第一步是标记断点,既然要观察_counter值的变化,则在每次_counter值发生变化后添加断点,观察数值变化是最理想的,因此在行号稍右侧点击鼠标,把断点加载下图所示的位置。 添加断点后,相应的行号右侧将会出现圆形的断点标记,并且整行将会高亮显示。 到此,断点就添加好了,当然,还可以同时添加多个断点,以便实现多个位置的调试。 接下来则是运行程序。和之前的运行方式不同,这一次需要以调试模式启动App。方法是点击Android Studio上方工具栏的小虫子图标,如下图所示: 稍等片刻,程序就启动了。由于我们添加断点的位置在程序启动后会被立即运行到,因此,无需其他操作,即可进入调试视图。如果断点位置并不是在程序一启动就执行,则需要手动让程序运行到断点位置。下图展示了代码运行到断点位置时的IDE视图,它自动进入了Debug视图模式: 这里介绍两种方法来获取_counter的值,一种是在代码处,通过执行表达式的方式,如下图所示: 在相应的变量上点右键,接着在弹出的菜单中选择计算表达式(Evaluate Expression),最后在弹出的对话框中点击Evaluate按钮,得到运算结果如下图所示: ...

October 17, 2019 · 9 min · jiezi

HR视角面了十几个Android开发候选人的一些总结及建议

背景公司前段时间冲上了App Store的第一,为了满足产品的各种需求,公司技术团队扩招,基于这种情况下,前段时间基本上都在面试,上上下下也面了十几个人。趁今天在家休息,就有了写这篇文章的打算。 因为公司都是和猎头公司合作,所以候选人背景基本上都是3年以上开发者。 面试总结 因为项目要快速开发,所以我问的他自己常见的一些第三方框架,数据库用SQLite,开发过程中自己写SQLite语句。我让他写一条学生按成绩排序的SQL语句都出问题。这个就直接pass了。 候选人是做内置应用的,但是对于优化这一块不是很熟悉,直接pass了。 这个是另外一个同事面的结论,pass!pass!pass!要招的就是快速开发,UI只掌握理论,开源框架都没使用过,这还怎么过? 前面感觉还不错,但是到后面事件分发不熟悉,那还怎么搞?pass 这个候选人感觉还是回答比较诚实的,懂就是懂,不懂就是不懂,好感还是有的,但是准备还是不够充分,很多基础的点没回答上。 这个没什么好说的,事件分发过不了直接pass。 技术一般,但简历过于优化。pass! 自定义View的流程都不清楚,基础不牢固,sorry。 事件分发过不了,这一点直接pass。 因为Hierachy那段时间自己也刚好在使用,所以候选人在使用AS 3.2.0的时候能直接通过AS打开确实不应该。 事件分发不熟悉,pass。 这个是面试过程的唯一一个女生,国内一个直播平台背景。前面印象挺不错的(毕竟是妹子,天生的优势),问大的点貌似都懂一点,但是问一些基础的知识点问题就比较大,问事件分发viewGroup的dispatchTouchEvent若返回false,此时事件分发顺序是怎样的?答会回调ViewGroup的onTouchEvent事件。因为是妹子,我告诉她回答有点问题,最终还是陪她看了一遍dispatchTouchEvent的源码,简单分析了一波。哎,最后她自己感觉她自己挂了,ok,pass。一些自己的看法 很多人都说今年是互联网寒冬,找工作不好找,其实不然,只要你自己技术到位,基本上都是各大公司的抢手人才。 再说说面试过程中我常问的一些问题: 1.自定义View相关问题;2.如何让点击事件和常按事件同时响应?3.viewGroup的dispatchTouchEvent若返回false,此时事件分发顺序是怎样的?4.如何处理ViewPager 和RecyclerView的滑动冲突?5.RecyclerView如何一次滑动一个?6.Hanlder的原理及looper的作用?7.开发过程中常用的第三方库有哪些?看过哪些的源码?8.性能检测及使用的工具?上面是我常问的一些问题,而这些恰恰都是基本的问题,然而大部分候选人都不能回答的很好!而我感觉这恰恰反应了行业的现状,很多人,只copy代码,实现就ok了,很少看其中的原理,下次继续copy,但是一到面试跳槽,也不复习,一问三不知。浪费彼此时间。你说你开发多年,没有github,没有博客,然后问你个几个原理还不知道,这谁顶得住? 最后,还是希望写点建议,至于有没有用,自行参考定夺,喷子太多,害怕ing……。 别老问寒冬不寒冬?你先问自己技术到位不到位?面试一家公司之前,尽可能的准备充分,上面所有的这些总结都会反馈给猎头,而恰恰同一个猎头推荐的候选人会在同一个问题上翻车!所以,如果是猎头推荐,多问下其他面试者的反馈,相信猎头们不会吝啬的。简历上的除非真精通,不然不要写精通,你写个了解熟悉,然后深入谈下去发现你好像特别熟悉,好感度会提高很多。简历上不会的千万不要写,除非你觉得你今天人品爆表,面试官这个一定不会问!简历上越熟悉越擅长的写在越前面。ok,面试官一般会从一个问题入手,衍生其他问题,越前面越有机会进入你自己熟悉的话题。(至少我是这样)!带上自己得意的作品!带上自己吃饭的家伙,自己项目的代码,你已经赢了一半了。(至少我之前面试就这么干,你要看什么功能?你要实现什么?有什么问题?我当你面解决这个问题。什么?你觉得我说得不对?来来来,咱们直接看源码,主动权在自己手里!)该有的礼貌要有,该低调还是要低调。这个是对极少数人的建议,你技术再好,一副老子天下第一的样子,那不好意思,请另谋高就。做好有自己的积累,github,博客文章,不仅仅能提升自己的写作能力,积累经验,也是面试过程中别人了解你的绝佳途径。限时分享Android学习PDF+架构视频+面试文档+源码笔记 在这里分享一份我自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习 如果你有需要的话,可以点赞+转发,关注我,然后加入Android开发交流群(820198451)免费领取

October 17, 2019 · 1 min · jiezi

干货Flutter-原理与闲鱼深度实践

王康(正物)—— Flutter 官方成员 阿里巴巴技术专家,之前主要负责 Flutter 在闲鱼中的混合开发体系,目前重点关注 Flutter 深入度以及生态相关的工作。本文将分享三方面内容, Flutter 的原理、 Flutter 在闲鱼中的应用,最后介绍我们在深度方面的一些探索。01、Flutter 原理 当我们谈到跨平台框架时,可能会想到很多备选方案。包括早期的 HTML 和 Cordova , 后来的 React Native , Weex ,以及这两年很是流行的 Flutter ,它们都在不同阶段不同程度上解决了我们对跨平台的诉求。如果我们从一些关键指标包括动态性、性能来观察,他们的区别还比较明显。 HTML 和 Cordova 具有最好的动态性,但他们的性能却是最差的,RN / Weex 具有良好的动态性。Flutter 则是一个纯原生的设计,其设计使它天生具有很好的性能与跨端一致性。 Flutter 是如何实现优秀的性能和跨端一致性的呢?从设计上可以看出 Flutter 在操作系统之上包含了三个层次。最下面是平台相关的嵌入层,其向上提供一个 Surface 用以绘制,建立了相关的线程模型和事件循环机制。在此之上则是一个平台无关的引擎,包括用于绘制的 Skia ;Dart 的运行时,开发模式下包括一个解释器;还有一部分是文本绘制相关内容。最上面就是用 Dart 语言编写的 Flutter 框架,也是我们最常接触到的内容。Flutter 框架包含一个完整分层的 UI 框架,从基础的 Foundation 库,到动画手势,再到渲染,之上又提供了各种丰富的 Widget 库。为了方便开发者使用, Flutter 还提供了两套不同风格的组件库,针对安卓的 Material Design 的组件库和针对 iOS 的 Cupertino 风格的组件库。 从这个设计可以看出,Flutter 和平台相关的内容,其实只提供 Surface 和线程/事件循环模型的嵌入层部分。这种类似用游戏引擎的方式来开发应用的设计很好解释了为什么它具有优秀的跨端一致性。 ...

October 16, 2019 · 3 min · jiezi

干货-把Flutter扩展到微信小程序端的探索

Google Flutter是一个非常优秀的跨端框架,不仅可以运行在Android、 iOS平台,而且可以支持Web和桌面应用。在国内小程序是非常重要的技术平台,我们也一直思考能否把Flutter扩展到小程序端?我们团队之前已经开源了Alita项目(https://github.com/areslabs/a...),Alita可以把React Native的代码转换并运行在微信小程序平台。受此启发,我们认为同样是声明式UI框架的Flutter同样可以运行在小程序平台。 所以,我们发起了flutter_mp(https://github.com/areslabs/f...)开源项目。以微信小程序为例,不过现阶段,flutter_mp项目还处于早期的实验阶段,很多功能还在探索规划中,欢迎大家在Github上随时关注我们的最新进展,或者参与项目共同探索。 原理简介 虽然还有诸多功能未完成,我们先来谈谈整个flutter_mp的实现原理。篇幅原因,下面我们将只对flutter_mp几个重要的部分进行简单说明。 先看下flutter_mp的实际效果: Flutter版官方layout样例: 通过flutter_mp转换并运行在小程序端效果 声明式UI的处理 Flutter是声明式UI框架,声明式UI只需要向框架描述UI长什么样子而不用关心框架具体的实现细节,具体到Flutter,上层的UI描述使用底层的skia图形引擎处理就是原生Flutter,而把底层处理换成html/css/canvas就是flutter_web,flutter_mp则是探索在类小程序上对这些UI描述的处理。 我们看一个最简单例子 var x = 'Hello World'Center( child: Text(x));对于上面的UI结构,我们只需要在小程序的wxml文件里,用如下的结构对应就OK了。 // wxml部分<Center> <Text>{{x}}</Text></Center>// js 部分Component({ data: { x: 'Hello World' }}) 虽然实际的结构要比上面的情况复杂的多,不过通过上面简单的例子,我们知道起码要做两个事情: 我们需要根据Flutter代码生成相关小程序wxml模版文件 收集wxml渲染需要的数据,放置到小程序组件的data字段。 wxml结构生成 我们知道小程序是无法动态操作节点的,wxml结构需要预先生成,所以Flutter运行在小程序之前,会存在一个编译打包阶段,这个阶段会遍历Dart代码, 根据一定规则生成wxml文件(编译阶段还会做下文将要提到的另外一个重要事情 --- 把Dart编译为js)。 具体的,我们首先会将Dart源码处理为可分析的AST结构,AST是源代码的树型表示结构。然后我们深度遍历这份AST语法树结构,生成目标wxml,整个过程如下: 构建wxml结构的难点在于:Flutter不仅是声明式UI还是“值UI”,什么叫“值UI”?简单来说,Flutter把UI看成是一个普通的值,类似于字符串,数字一样的值,既然是一个普通的值,就可以参与所有的控制流程,可以是函数的返回值也可以是函数参数等等。而小程序的wxml虽然也是声明式UI,却不是“值UI”,wxml更加像模版,更加的静态。怎么用静态的wxml表达动态的“值UI”是构建wxml结构的关键所在。 看个例子 Widget getX() { if (condition1) { return Text('Hello'); } else if (condition2) { return Container( child: ... ); } else if (condition3) { return Center( child: ... ); } ...}Widget x = getX();Center( child: x // < --- 如何处理这里的 x??);这里的child: x x是一个动态值,它的具体值需要在运行阶段才能确定,它可能是任意的Widget,如何在静态的wxml上处理这里动态的x?受Alita框架的启发,这里主要是借助于小程序template的动态性(template的is属性可以接受变量值)。有如下几步: ...

October 16, 2019 · 1 min · jiezi

Flutter-命名路由注册-跳转传参接收数据返回数据

注册: MaterialApp( routes: <String, WidgetBuilder> { 'myrouter': (BuildContext context) => new MyRouter(), }, );带参数跳转 Navigator.of(context).pushNamed('myrouter',arguments: "这是传过去的参数");跳转界面接收参数 @override Widget build(BuildContext context) { //获取路由参数 var args=ModalRoute.of(context).settings.arguments }返回时候,传参数 Navigator.of(context).pop('这个是要返回给上一个页面的数据');返回到的上一页接收参数(修改上面的跳转方法) Navigator.of(context).pushNamed('myrouter',arguments: "这是传过去的参数").then((value){ print("value===="+value.toString()); });

October 15, 2019 · 1 min · jiezi

浅谈APP的分享功能有时候社交裂变形式比内容更重要

回顾2018年的移动互联网,“社交裂变”“下沉”等成为年度关键词。一方面我们可以看到社交裂变助推用户增长,另一方面我们也看到了以拼多多、趣头条为代表的互联网企业对于社交裂变模式表现出的空前关注度。作为社交裂变传播中的重要一个环节,APP的社交分享功能的重要性自然就不言而喻了。 如今的社交分享已然成为了APP的标配,用户每天都在从不同的APP中分享内容到朋友圈、QQ、微博等社交平台。这个过程可以实现APP宣传、拉新、留存、和用户保持粘度等各种目标。但是效果的好坏,效率的高低也会因为分享内容、分享时机、分享形式等因素的不同而不同。简单来说,分享的过程就是“触发用户分享的动作——分享——其他用户打开分享内容”。传统认知里,人们会觉得分享的内容是最核心的也是最重要的。但实际上,针对同样的内容,不同的分享形式会带来不同的效果,所以分享形式是个非常值得深度挖掘的部分。目前比较常见的分享形式有文字分享、网页分享、图片分享、音频/视频分享等,不同的形式适用的场景也有所不同。 简单比较一下几种形式。文字分享简单直观,但是在篇幅较长内容较多的时候就会有很大局限性。这个时候图片分享的优势就会比较明显,通过图片的方式划出重点,相对于文字的抽象来说,图形更加生动形象,方便理解,甚至在设计上也会比纯文字的方式更有艺术感和细腻。而音视频分享则针对性更强,因为分享内容的独特性可以保证用户在点开的第一时间就了解内容。可以看出,和纯文字相比,图片、音频、视频的分享形式灵活性更强,适用的场景更丰富,用户的接受度也更高。所以在设计的时候推荐用图片和视频更加直截了当的展现分享内容。虽然分享的内容是大同小异甚至是一样的,但是在视觉和操作上的优化却是大大的。可能有人觉得这是“换汤不换药”,但是从前需要点击链接以后才能看到内容主体的操作确实是有了较大的改善,用户也能在第一时间抓取关键内容。总而言之,更加生动清晰的分享形式,无论是对于分享的发出用户也好,还是对于分享的接收用户也好都是非常友好的使用体验。其实,分享功能中除了分享形式以外还有很多需要深入思考的因素,比如分享时机、分享动机等。每个用户在进行分享的时候可能都有着不一样的分享动机,可能是主动分享,晒好物、求认同,也有可能是被动分享,就是通过分享的行为来获取产品的增值服务,比如分享获得红包、优惠券、免广告。那么结合用户的不同动机,搭配不同的分享形式,是不是会达到1+1>2的效果呢?这也是每个开发者和运营者需要不断思考和尝试的部分。 最后欢迎留言讨论,也欢迎在MobTech官网下载免费的SDK,目前MobTech旗下的第三方移动应用服务平台(MobService)已相继推出十余款SDK产品和场景解决方案,为App开发与运营全面赋能,相信无论是社交分享、深度链接还是短信验证码,总有一款适合你的App。

May 16, 2019 · 1 min · jiezi

Android-Gradle系列入门篇

接下来的一段时间会对Android Gradle的相关知识进行梳理,所以借此整理成一个系列。如果你是刚入行的新秀,那么这个系列将会非常适合你,因为Android基本的配置都与Gradle有关。当然如果你已经入行,但对Gradle还是停留在表面的认知上,这个系列也会对你有所帮助。 这篇文章定义为入门篇,将结合自己刚开始学习Android时的疑惑与现在对Gradle的认识,进一步整理Gradle在Android中的整体结构。 思考当我使用Android Studio时,一直有几个疑问围绕着我: Android Studio是怎样将Java与Kotlin代码的编译成APK文件?Gradle是怎样将Java与Kotlin代码编译成APK文件?后来知道Android Studio自身是不能够编译成APK的,它是集成了Gradle。通过研究Gradle,发现Gradle也只是一个构建工具,真正编译成APK的功能是由Android app plugins提供的。Gradle只是自动化构建工具,提供构建时的各种生命周期,例如:building、testing、publishing等。所以Gradle不仅支持Android还支持C/C++、Scale等。 而这个plugin其实就是在project中的build.gradle中声明的classpath buildscript { repositories { // Gradle 4.1 and higher include support for Google's Maven repo using // the google() method. And you need to include this repo to download // Android Gradle plugin 3.0.0 or higher. google() ... } dependencies { classpath 'com.android.tools.build:gradle:3.4.0' }}所有每次对Android构建进行了优化,我们都要来更新这个版本。 Scripts有了上面的基础,当我们新建一个Android项目时,你将会看到如下与Gradle相关的文件: 你会看到文件名几乎都有gradle字段,下面我会一一介绍它们的作用 Gradle Wrapper首先是gradle-wrapper.properties文件,打开它你将会看到如下类似信息 #Sat Jan 19 08:25:46 CST 2019distributionBase=GRADLE_USER_HOMEdistributionPath=wrapper/distszipStoreBase=GRADLE_USER_HOMEzipStorePath=wrapper/distsdistributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip这个是gradle版本的配置项,申明你当前项目中使用的gradle版本。当我们构建项目的时候,它会根据版本自动下载。并且保存到你的电脑本地中。如果你使用的是Mac,你可以使用如下命令查看你的所有已经下载的gradle版本。 ...

May 7, 2019 · 1 min · jiezi

深入理解Flutter多线程

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> https://www.jianshu.com/p/54da18ed1a9e Flutter默认是单线程任务处理的,如果不开启新的线程,任务默认在主线程中处理。 事件队列和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程叫做isolate。应用程序启动后,开始执行main函数并运行main isolate。 每个isolate包含一个事件循环以及两个事件队列,event loop事件循环,以及event queue和microtask queue事件队列,event和microtask队列有点类似iOS的source0和source1。 event queue:负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件。microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。 这两个队列也是有优先级的,当isolate开始执行后,会先处理microtask的事件,当microtask队列中没有事件后,才会处理event队列中的事件,并按照这个顺序反复执行。但需要注意的是,当执行microtask事件时,会阻塞event队列的事件执行,这样就会导致渲染、手势响应等event事件响应延时。为了保证渲染和手势响应,应该尽量将耗时操作放在event队列中。 async、await在异步调用中有三个关键词,async、await、Future,其中async和await需要一起使用。在Dart中可以通过async和await进行异步操作,async表示开启一个异步操作,也可以返回一个Future结果。如果没有返回值,则默认返回一个返回值为null的Future。 async、await本质上就是Dart对异步操作的一个语法糖,可以减少异步调用的嵌套调用,并且由async修饰后返回一个Future,外界可以以链式调用的方式调用。这个语法是JS的ES7标准中推出的,Dart的设计和JS相同。 下面封装了一个网络请求的异步操作,并且将请求后的Response类型的Future返回给外界,外界可以通过await调用这个请求,并获取返回数据。从代码中可以看到,即便直接返回一个字符串,Dart也会对其进行包装并成为一个Future。 Future<Response> dataReqeust() async { String requestURL = 'https://jsonplaceholder.typicode.com/posts'; Client client = Client(); Future<Response> response = client.get(requestURL); return response;}Future<String> loadData() async { Response response = await dataReqeust(); return response.body;}在代码示例中,执行到loadData方法时,会同步进入方法内部进行执行,当执行到await时就会停止async内部的执行,从而继续执行外面的代码。当await有返回后,会继续从await的位置继续执行。所以await的操作,不会影响后面代码的执行。 下面是一个代码示例,通过async开启一个异步操作,通过await等待请求或其他操作的执行,并接收返回值。当数据发生改变时,调用setState方法并更新数据源,Flutter会更新对应的Widget节点视图。 class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); loadData(); } loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); }}FutureFuture就是延时操作的一个封装,可以将异步任务封装为Future对象。获取到Future对象后,最简单的方法就是用await修饰,并等待返回结果继续向下执行。正如上面async、await中讲到的,使用await修饰时需要配合async一起使用。 ...

April 26, 2019 · 2 min · jiezi

不止微信、支付宝!一文带你了解所有小程序平台

小程序的平台越来越多了,开发者的精力也越来越分散。事实上,这些平台有怎样的特色?他们有怎样的代表作品?他们有几个入口?开发成本高吗?他们有给开发者怎样的扶持政策?一文为你解析小程序六大平台。01 微信关于微信小程序2017 年 1 月 9 日,微信小程序正式上线。经过三年的发展,它俨然成为了开发者最关注的小程序开发平台。在 2018 年 8 月,马化腾就透露已有 150 万开发者加入了小程序的开发队伍,小程序应用数量超过 100 万,覆盖 200 多个细分行业,日活用户达到 2 亿。而在 2018 的全年财报也表明,微信小程序日活跃账户数增长迅速,用户人均日访问量同比增长 54%,而中长尾小程序也占到了小程序日均总访问量的 43%。在小游戏中,微信已有单月流水过亿的杰出小游戏作品。10 款内购道具月流水过千万小游戏变为了 10 款,11 款广告月流水过千万小游戏。头部小游戏的成功已经无需证明,但从整个行业来看,三十天留存率 43% 亦足够优秀。微信里的社交关系让小程序拥有了更多的吸引力,小程序则让微信的「操作系统梦」成为现实。在微信中,小程序拥有着超越公众号之外的连接和服务能力,也能将公众号、群聊、对话、朋友圈串联起来,为用户提供更延展也更丰富的服务。代表作品小程序:拼多多、跳一跳、享物说、小打卡、轻芒、小年糕、乘车码、粤省事等小游戏:跳一跳、物理弹球正版、头脑王者、欢乐斗地主、星途 WeGoing、羽毛球高高手等主要入口微信聊天下拉的小程序界面是小程序最重要的入口之一。聊天的小程序卡片、公众号推文中的、分享小程序、四散的小程序都是小程序的重要入口。据不完全统计,微信小程序已有 60 余个入口。开发成本小程序的主要开发语言是 JavaScript ,小程序的开发同普通的网页开发相比有很大的相似性。对于前端开发者而言,从网页开发迁移到小程序的开发成本并不高,但是二者还是有些许区别的,毕竟小程序还处于一个封闭的程序运行环境。开发文档:https://developers.weixin.qq….扶持政策在小程序和小游戏上,微信都推出了不同的扶持计划。????小程序腾讯云在微信公开课上宣布推出的「小程序 · 云开发」资源扶持计划价值 10 亿。该计划分为长期普惠、进阶扶持和高阶扶持三种机制。腾讯云提供的基础版套餐、专业版资源、旗舰版资源都是扶持奖励。????小游戏创意小游戏鼓励计划:创意标识、初始用户、分成激励、创意保护。种子用户计划:针对新游,微信将无差别分配等额的种子用户。在第一批无差别等额用户发布后,微信会持续观察所有小游戏的数据,根据平台的数据模型,给新游戏第二批的种子用户。在这两个计划之外,微信提供的应收转投放和应收预提将帮助开发者快周转。同时对普通开发者的抽成比例进一步降低。月流水 50 万以下的小游戏免抽成。月流水在 50 万以上的部分,游戏内购抽成 40%;日流水在 100 万以下的部分,广告抽成 50%。02 支付宝关于支付宝小程序2018 年 9 月 12 日,支付宝小程序正式上线,主要活跃在商业和生活生活领域,暂不支持小游戏(严格来说目前仅支持支付宝官方开发游戏类小程序,如「叠叠乐」)。据悉,截至 2019 年 1 月,支付宝小程序的数量已增至 13 万,日活跃用户数突破 2.3 亿,平均 7 日留存率为 43.26%。目前,支付宝小程序对企业账号及个人账号(已开启个人开发者限量公测)开放。支付宝的实名认证并与「钱」挂钩让用户和商家都更具信任感;而有独特优势的信用体系则让支付宝在与押金、租赁相关的场景大放异彩,比如共享单车、共享充电宝等支付宝小程序,在接入芝麻信用后为用户提供了免押金等便捷服务。代表作品免押金类:来电、内啥、人人租机、哈啰出行等其他:好食期、青团社兼职、航旅纵横、淘票票电影等主要入口主要为支付宝 app 首页顶部搜索栏,以及首页下拉呼出「小程序收藏」。更多支付宝小程序入口,可以查看 ???? 《细数支付宝小程序的 35 个入口,我们终于找全了》。开发成本与微信小程序的整体架构基本一致。如果你想将微信小程序迁移至支付宝,只需重命名小程序文件后缀、一些事件函数和部分 API 即可。开发文档:https://docs.alipay.com/mini/…扶持政策3 月 21 日,在 2019 阿里云峰会 · 北京上,阿里巴巴旗下的阿里云、支付宝、淘宝、钉钉、高德等联合发布「阿里巴巴小程序繁星计划」:提供 20 亿元补贴,扶持 200W+ 小程序开发者、100W+ 商家。凡入选「超星」的小程序,入驻支付宝、淘宝、钉钉、高德后还能得到流量重点支持。03 百度关于百度智能小程序2018 年 7 月 4 日,百度智能小程序正式上线,主要运行在百度 app 上,主打「体验、流量、智能、开放」四大特点。据了解,12 月时百度 app 已为小程序开放了 40 多个流量入口,并为小程序开发者提供了超过 60 个 AI 接口和超过 20 个 NA 化组件。百度智能小程序支持小游戏开发,不过目前支持的主体主要为媒体、企业、政府及其他组织,个人主体类型开发者暂时无法入驻。代表作品AI 相关:爱说唱、长隆 AR 动物园、AI 分诊助手等其他:小红书、爱奇艺视频、几何大逃亡、贴吧等主要入口可以从百度 app 的搜索栏联想、搜索结果、底部菜单「我的」中进入百度智能小程序。更多百度智能小程序入口,可以查看 ????《百度的搜索流量终于全面开放!新入口就在王牌产品主场景》。开发成本与微信小程序的整体架构基本一致。如果你想将微信小程序迁移至百度,只需使用百度的「搬家工具」转换已有的微信小程序即可。开发文档:https://smartprogram.baidu.co…扶持政策在 2018 年的百度世界大会上,百度宣布了智能小程序「开发者共筑计划」——「千百十一」,分别对应着百度将为智能小程序开放全域千亿用户流量、提供百亿的广告分成、成立一个提供投资与推广的创新基金以及像公开课一样的一对一专人服务。04 淘宝关于「轻店铺」2018 年 9 月,手机淘宝上的小程序「轻店铺」开始内测。淘宝「轻店铺」是一个支持个人或者企业进行开店的工具,具有群聊、发文章、门店导航等功能。据了解,「轻店铺」将把淘宝品牌主的直播、微淘内容以及门店信息进行汇集,最后形成了一个信息块,当用户在线上搜索该品牌时会打包弹出。代表作品严格来说,目前手淘上只有「星巴克中国」是真正的「轻店铺」,底层与支付宝小程序相通。其他的淘宝店铺虽然同样有小程序标志性「胶囊键」,但并不是小程序,且胶囊键与「轻店铺」按键不同。主要入口在淘宝搜索「星巴克中国」,即可从顶部的横幅进入。开发成本因仍处于内测阶段,开发者可将姓名、联系方式、公司名称、职务发送邮件至 taolite@service.taobao.com 申请成为内测用户。未获取内测资格的用户将无法进行入驻。05 字节跳动关于字节跳动小程序2018 年 11 月 8 日,字节跳动小程序正式上线,小程序旨在利用优质内容所关联和产生的使用场景进行小程序导流,解决开发者流量与转化困扰。开发一套小程序就可以服务多个产品是头条小程序的优势所在。在字节跳动开发者平台开发的小程序有机会出现在抖音、西瓜视频等不同平台。目前字节跳动被人所熟知的小程序,多为其他平台的已有内容。但在游戏、电商、娱乐等领域,字节跳动反倒有了更多拿得出手的作品。不过由于平台尚在发展阶段,很多内容构架都尚未完善。代表作品小游戏:光与森、行星毁灭、音悦球球、一笔画完等主要入口今日头条文章详情页、微头条、小视频、搜索等入口均为今日头条小程序入口,新版今日头条「常用」底部导航栏还在内测一个类似小程序桌面的新界面。而在其他平台,抖音短视频左下角也有对应的小程序入口。开发成本字节跳动小程序暂不支持一键转换工具。暂不支持 wepy,mpvue 等框架,需开发者主动把利用 wepy 框架开发的代码,编译为小程序的语法,才能在头条上正常的渲染。开发文档:https://microapp.bytedance.com/扶持政策目前字节跳动公布的扶持政策仅面向小游戏。针对普通游戏,开发者可以拿到广告日流水不超过 100 万部分的 60%,超过 100 万的部分开发者可保留 50%。而在字节跳动首发的游戏将获得更多资源倾斜,开发者可以拿到广告日流水不超过 100 万部分的 70%,而超过 100 万的部分开发者可保留 60%。06 快应用关于快应用vivo、华为、OPPO、小米、联想、金立、魅族、中兴、努比亚、一加、海信、中国移动终端想要一起做一个基于手机硬件平台的新型应用形态。十二家手机厂商和服务商组成的快应用联盟来势汹汹。覆盖 10 亿设备、月活 2 亿、打开快应用 20 亿次、留存 1 亿个桌面图标,35% 的流量来自桌面留存图标。快应用与其他小程序平台应用同质化严重,联盟 12 个合作伙伴对开发者的资源分配亦有较大不同。代表作品工具类:菜鸟裹裹、西窗烛、吐司工具箱等主要入口快应用有四类入口:应用分发、智能场景、二次入口、开发者自身入口开发成本快应用使用前端技术栈开发,原生渲染,同时具备 HTML 5 页面和原生应用的双重优点。快应用使用 MVVM 的设计模式进行开发,开发者也无需直接操作 DOM 节点的增删,利用数据驱动的方式完成节点更新。相比微信、支付宝小程序,快应用的开发语法标准,其语法也更接近传统网页。开发文档:https://doc.quickapp.cn/除了以上几大平台推出的小程序,其实优酷、钉钉、网易云音乐也出现过小程序的身影:优酷的小程序、钉钉的 E 应用,还有网易云音乐的「私藏推荐」。▲ 左为优酷小程序,右为网易云音乐小程序 在已有几大平台的努力下,小程序存在的方式和其代表的服务形态已经被越来越多的平台所认可。这些平台或许不会 all in 小程序,却也会在自己的生态内对小程序进行更多的尝试。和一个成熟、有包容性的平台相比,这几个平台或有严格的类目限制;或者只是将原有的 H5 内容转为了小程序;有的则只是模仿了小程序相似的界面。对于开发者而言,他们似乎不能算一个开放的开发平台,但也与小程序有着不小的联系。知晓云福利参与 iOS、Android SDK 内测领福利「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更地做出优质的小程序。你的主要业务不在小程序,而是在移动端?没关系,iOS、Android 的应用开发也可以使用知晓云来完成了哦~知晓云移动端(iOS、Android)支持的内测活动已经开启:即日起至 4 月 17 日,填写表单报名参与内测活动的用户,在成功接入移动端(iOS、Android)应用后将获得 100 元无门槛优惠券???? 报名点这里 ???? ...

April 11, 2019 · 1 min · jiezi

想要更精准的小程序模版消息推送?我们来帮你实现

两年多前,为了让更多的人找到好玩、好用的小程序,我们成立了「知晓程序」。再后来,我们推出了后端云服务平台——知晓云,帮助大家降低创业成本,提升开发效率。「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。随着小程序生态的不断发展、用户规模的不断扩大,开发者的项目也在高速增长。他们不再满足于现有的后端服务支持,开始需要更多开箱即用的功能。为此,我们围绕「简化开发流程,让用户仅需专注于业务」这一目标,相继研发了多个好用的功能。在用户喜爱的众多功能中,使用率最高的是模版消息推送。这一功能自 2018 年 4 月上线后,使用率不断提升,完美的达成了我们设计此功能时的初衷——使用户获得【开箱即用,无需编码】的推送能力,并将其用于小规模的消息通知及用户管理。一年来,知晓云平台上有越来越多的小程序,从数百用户的小项目成长到 UV 过百万级,甚至超过千万的明星项目。模版消息推送数的量级也由早期每天几百条,变为后来的每天数百万条。客户的项目逐渐成熟,其需求也从简单的模版消息推送,逐渐转变为基于推送的转化、留存全链路触达与数据分析服务。因此,我们决定再往前迈进一步。5 月 8 日,我们将正式推出「知晓推送」服务,帮助运营者处理好粉丝转化、消息推送、数据分析等多个层面的麻烦事,真正解决复购难、留存差的问题。决定推出「知晓推送」服务前我们考察了市场上现在服务存在的问题,主要有这几条:平台支持少:只做了微信小程序平台支持,无法实现在支付宝小程序及其他平台发推送的需求。服务价格贵:按年收费且价格高昂,发送一千条推送就需要付出 10 块钱的成本。效果追踪差:模板消息发完就完事儿了,推送效果如何、用户是否点击,点击的用户特征,这些数据都无法追踪与分析。这些问题,我们来逐一解决。平台支持少?知晓云已经支持包括微信小程序和支付宝小程序在内的各大小程序平台的消息推送,对 Android、iOS 平台的支持也将在近期上线。大家的业务版图拓展到哪儿,我们支持到哪儿!服务价格贵?不得不说,友商的定价确实高得离谱。我们来算一笔账:假设小王有一个 2 万用户的小程序,日常运营每周一次推送,一年要发 100 万条模板消息。如用 x 神推的服务,要花 2 万块。2 万块啊!痛大家所痛,我们决定把它除以 200——只要 100 块。效果追踪差?经过长期的实践总结,我们推出 3 大重磅特性:过滤低价值用户,提升回访转化率大数据精准过滤有恶意举报行为或被标记为机器人的用户,有效减少无效发送,提升模板消息的回访转化率。一键埋点,自动收集 formid收集用户的 formid 是发送模板消息的前提条件,知晓云 SDK 提供透明页面自动收集 formid 功能,在不打扰用户的前提下即可收集大量 formid。智能发送规则,实现精准推送开发者可以自行定义用户行为,为满足特定条件的用户发送对应的模板消息。实现消息的精准推送。知晓云结合不同行业的海量行业案例,挖掘了一套专有的用户过滤机制,祝你实现业务量的翻倍增长。「知晓推送」活动预告「知晓推送」的主要特性说完了。考虑到 5 月还比较遥远,我们先预告一下福利吧:如你还不是知晓云用户,赶紧注册用起来吧~「知晓推送」正式上线前完全免费,服务上线当天还有额外福利~如你已经是知晓云用户了,也请您放心。5.8 日前注册的用户都可获赠价值不少于 198 元的资源包哦~支持 iOS、Android 的公测福利(4/2 - 4/10)「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。另外,知晓云支持移动端(iOS、Android)的公测活动即将开启:即日起至 4 月 10 日,通过扫描下方的二维码填写表单报名参与公测活动的用户,在成功接入移动端(iOS、Android)应用后将获得 100 元无门槛优惠券???? 报名点这里????知晓云是国内首家专注于小程序开发的后端云服务。使用知晓云,小程序开发快人一步。

April 2, 2019 · 1 min · jiezi

想打通 Web 与小程序的账号系统?来试试知晓云吧

3 月 1 日,知晓云对支付宝小程序的支持已正式上线,参与公测的用户也获得了丰厚的奖励。多平台账号系统打通的体验让一切变得简单;与此同时,希望 Web 应用接入的呼声也越来越强烈。于是,这一次的 Web 端公测活动,我们希望有更多的开发者能拿到这笔「启动资金」,使用知晓云助力你的开发之路。「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了应用开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。我们在逐步兑现我们的承诺 —— 全平台 Serverless ,让未来触手可及。「未来」总来的比想象快,Web 端的公测活动正式开启:即日起至 3 月 15 日,报名并通过审核的公测的用户,在正式接入后都可获得获账户礼金 100 元。报名点这里 ???? 知晓云公测活动 ????知晓云能带给你什么?如果想做一个应用,基本上一定要有用户系统、数据存储、网络接口、管理后台等配套,这都是后端的工作。而通过我们多年实践,在大多数应用场景里,这类配套的定制(需要开发)的部分,均发生在应用层,也即,这类业务模块完全可以做成通用的服务。将后端的用户系统、数据存储、网络接口、服务运维甚至支付网关等通用业务做成服务提供给用户,通过管理后台控制,就可以做到无需后端工程师参与开发而完成项目。因此我们在微信小程序上线后的半年里,倾入全部力量,打造了知晓云——一个为小程序设计的后端云服务。17 年以来,我们花了一年半反复打磨产品,与广大的小程序开发者共同成长。在刚过去的 2018 年 ,我们累计服务了 40000+ 开发者,实现了服务规模同比 1000% 的增长。各大厂纷纷入局重注小程序,标志着移动互联网正式进入新时代——一个强调快速上线、专注服务本身的时代。在这个时代里,各大平台的应用都需要无服务器(Serverless)的开发方式。通过接入 SDK 就可以使用后端服务,开发者可以快速构建一个完整产品,上线推广。我们希望将从服务数万开发者的过程中总结出的,各个应用场景的经验推广到各大平台去,帮助更多的开发者。我们相信,将业务与后端云服务结合,将使开发过程变得更加简单、迅速和高效,让更多富有创造力的开发者创造更多的价值。好用、省心的后端云服务有了一个好的想法,再花上几分钟接入知晓云的 SDK,你就不再需要去管什么 PHP、数据库等后端逻辑,也无需管理服务器或维护后端服务,更不用担心自己服务器的负载和运维……与此同时,你还将获得:成本降低 100%数百元每月的服务器费用,9 元/月的套餐即可替代,大幅节约成本。运维时间节省 100%24 小时待命的线上运维、峰值动态扩容,服务器运维再无后顾之忧。效率提升 200%2 秒即可授权登录,多平台账号系统支持,开发效率将提升数倍!一句话,有了「知晓云」,核心业务逻辑以外的事情都不需要你操心。开发、推广都无后顾之忧我们提供的不仅仅是后端云服务。在历经多年的「移动生态」构建过程后,我们深知产品研发上线只是第一步,一切只算刚刚开始。我们还在推广环节铺了一条「高速车道」,使你的产品更快触达更多的用户:Appso:知名应用推荐服务平台知晓程序公众号:小程序生态第一大号知晓程序商店:首家小程序「应用商店」知晓市场:应用定制对接平台我们将技术服务与推广服务相结合,为用户提供免费的冷启动资源及完善的应用推广服务。知晓云,一个起点还记得 2017 年 6 月,知晓云内测用户线下见面会上,我们打出了「知晓云,一个起点」的 Slogan 。我们的愿景:一个刚开始探索移动互联网的少年,可以从知晓程序了解移动开发的最新趋势,通过知晓课堂学习开发姿势,利用知晓云一天搞定开发并获得我们提供的冷启动资源,逐步做大走上人生巅峰。2019 年知晓云正式开启了全平台 Serverless 计划,希望能服务更多的开发者。我们会为此不懈努力,希望愿望能有实现的那天。知晓云是国内首家专注于小程序开发的后端云服务。使用知晓云,小程序开发快人一步。

March 11, 2019 · 1 min · jiezi

支付宝小程序面向个人开放了!我将以一个 Demo 为例讲解整个流程。

Hello,我是犯迷糊的小 K。目前是 ifanr 的一只前端攻城狮,同时也是知晓云团队的一员。3 月伊始,ifanr 旗下品牌——知晓云 3.0 版本正式上线。此次更新得到业内许多开发者的密切关注和积极支持,在此,我代表知晓云团队表示万分感谢哈。( ̄▽ ̄)~*知晓云是业界第一个支持多平台小程序开发的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。言归正传。和许多童鞋一样,小 K 使用知晓云时,也是第一次开发小程序,开发过程也是百转曲折。因此,小 K 希望通过这篇文章,和各位童鞋进行交流。毕竟,大家的学习历程是相似的,遇到的困惑也应该差不多。本文结构大致如下:谈谈如何成为支付宝小程序开发者。聊聊如何创建我的第一个支付宝小程序。以一个 Demo 为例,详细讲讲如何在支付宝小程序中接入和使用知晓云 SDK。如何成为一名支付宝小程序开发者?申请成为支付宝小程序开发者,是一件再简单不过的事儿,仅需 2 步,比把大象放进冰箱还简单。第一步,登录蚂蚁金服开放平台,注册成为小程序开发用户。此过程需要你依次完成账号信息、邮箱激活和信息登记等流程。第二步,完成上述操作后,就能进入小程序管理后台,点击创建应用并填写信息,创建成功后即可获取开发小程序的 AppID。嗯,现在小 K 已经是一枚准小程序开发者啦。(后续请进入小程序配置-设置-开发设置,根据平台的设置方式教程,配置接口加签方式,获得支付宝公钥和密钥文件)如何创建我的第一个小程序?获得了「准入资格」后,小 K 开始参照小程序官方文档,下载官方的开发者工具并创建了一个初始化的小程序。Well done!小 K 的第一个初始化小程序诞生了~ 接下来,可以看看支付宝小程序官方的体验小程序 Demo 教程文档,熟悉一下小程序代码组织方式和开发特性。现在,有了开发工具和基础知识积累,可以试试 freestyle 咯。唯一的问题是:小 K 应该选择什么类型的小程序作为 Demo 呢?对于 Demo 选择,唯一的原则就是精简「简」是像小 K 这样的小白开发者一看就懂。「精」是尽可能在有限的代码中,体现知晓云功能的强大性。于是,我选择了个经典的 TodoMVC 的小程序——「我的书架」作为示例。由于「我的书架」 Demo 将知晓云的核心模块之一——数据管理的 CRUD 操作很好地展示了出来,所以,我们希望通过这个 Demo 让各位童鞋学会利用知晓云,完成常见的数据增删改查功能。如何在小程序中调用知晓云 SDK?准备工作在正式使用知晓云的 SDK 前,首先确保走完以下 2 个流程:第一,完成小程序的授权。目前,知晓云在注册模块和设置模块都有提供小程序授权操作,二者的授权流程大体一致。在这里,我们演示设置模块的小程序操作。点击应用标签,进入应用的管理面板;进入管理面板后,切换到设置模块并进入应用设置 tab 页,点击平台设置-支付宝小程序-立即开通,点击编辑并填写相关配置信息后即可完成授权。第二,在「小程序后台」配置安全域名。装载 SDK接下来,看看知晓云的 SDK 的使用说明文档。老夫掐指一算,将 SDK 的接入小程序的方法和数据表操作看了一遍,约莫花费 10 分钟。毕竟 Demo 只涉及数据操作嘛,所以要做到有的放矢,要啥看啥。下载知晓云提供的 SDK 后,将其引入小程序的 app.js 中,并通过在前面的设置模块的小程序设置 tab 页中获取当前应用的 ClientID。设计数据结构和创建数据表完成上述操作后,小 K 就可以使用 SDK 提供的各种接口,接下来思考一下「我的书架」将用到什么数据及其结构。由于是第一个 Demo ,本着精简的原则,小 K 在此就只设计了一个 bookName 的字段Tips:知晓云的数据管理模块会为每张数据表自动创建 id,create_by,create_at,update_at 和 acl 等字段。根据文档提示,在使用知晓云的数据管理模块时,需要首先提供存放数据的 tableName。因此,首先要在知晓云开发者平台创建数据表从而获取 tableName。获取 tableName 后,小 K 将其放在了 app.js 文件的 globalData 对象上,以供后面各种数据操作接口的参数调用。开始使用知晓云的 SDK小 K 在这里不会细谈「我的书架」是如何编写的,因为不同的童鞋的对这个功能的实现方式可能不一样。小 K 只会谈在哪些控件中使用知晓云提供的接口,来实现小 K 的需求——添加一本书。创建书目记录翻查了文档,发现创建一条记录很简单,只需要调用 create 创建一条空记录,然后调用 set 为上面创建的空记录赋值,最后调用 save 将创建的记录保存到服务器即可。更新一条记录有时,小 K 手抖,在输入书目的时候填写了错别字,那么理应提供一个更新记录的功能吧;知晓云提供了 update 接口,让更新数据 so easy。删除一条记录最后,当小K的书架不再存在某本书时,必然需要一个删除操作。通过调用 delete 接口就可以实现一条记录的删除操作。最后的话以上就是小 K 用知晓云烹调出的第一个支付宝小程序——「我的书架」,最主要就是用到了知晓云的数据管理功能模块。当然,知晓云还提供作为 BaaS 产品的基础文件上传和数据统计功能等,同时具备贴切小程序的特性功能,譬如支付宝支付和富文本编辑功能。*除了「我的书架」 Demo 外,知晓云官方还提供了知晓云 SDK 官方示例小程序,用于演示 SDK 更丰富的接口使用方法。代码已开源在 ifanrX 的 GitHub 上,链接:https://github.com/ifanrx/hyd… 有兴趣的童鞋可以 star 或是 fork 一下。本文首发于「知晓云」公众号:https://mp.weixin.qq.com/s/Vk…知晓云是国内首家专注于小程序开发的后端云服务。使用知晓云,小程序开发快人一步。 ...

March 5, 2019 · 1 min · jiezi

深度解析:mPaaS 3.0全新组件

摘要:以“数字金融新原力(The New Force of Digital Finance)”为主题,蚂蚁金服ATEC城市峰会于2019年1月4日上海如期举办。分论坛上,蚂蚁金服产品专家杨晓亮和章建军做了主题为《深度解析:mPaaS 3.0全新组件》的精彩分享。演讲中,杨晓亮和章建军解读了mPaaS 3.0中的真机云测、舆情分析、智能投放,以及多媒体服务组件,并与大家分享了mPaaS 3.0如何在APP开发、测试、运营及运维等方面帮助企业构建稳定高质量的移动应用。杨晓亮 蚂蚁金服产品专家章建军 蚂蚁金服产品专家本次的分享主要围绕以下内容展开: 一、真机云测二、舆情分析mPaaS为APP的开发、测试和运维提供了一站式解决方案,能有效降低APP研发成本、提高开发效率,协助企业构建稳定且高质量的移动端产品。mPaaS自2015年发布以来,得到了长足的发展,其客户遍布金融、出行及政务等行业。mPaaS产品架构分为三层:底层负责后台连接,提供网关,及数据、多媒体传输与处理等服务;中间层移动中台,为APP研发、测试、发布和分析、运营提供一系列服务;上层客户端,为Native、H5和小程序提供开发框架、标准化的UI控件,以及面向具体业务的组件。本文将解读测试部分的真机云测、运营部分的移动舆情分析与移动智能投放,以及多媒体+智能部分的多媒体文件传输等产品。一、真机云测互联网的发展使得移动市场逐渐成熟,传统企业对APP产品质量提出了更高的要求,而真机测试是提升APP质量最直接的手段。我们看到,越来越多的企业重视软件测试,一些企业为此组建了自动化测试团队,还有一些企业为App测试项目单独招标。蚂蚁金服内部,真机云测这样的自动化测试产品已存在多年,成熟的应用在各大核心业务线。站在mPaaS的角度上,也在考虑如何将这些测试能力输出到企业,帮助企业构建自动化测试环境,提升企业的软件测试能力。我们认为好的测试能力,需达到如下几个要求:切实提升测试效率,比如高并发的同时在多个设备上执行测试;切实降低测试人力、设备等投入成本;保障测试充分度,如全面覆盖测试维度、研发阶段、平台等;从软硬件层面充分保证测试工具本身的兼容性,无人值守的情况下,测试可长时间稳定运行;技术可控性,保证测试链路绝对安全,测试包、测试代码不被泄露等。蚂蚁金服推出的真机云测产品能够模拟用户的真实操作 ,为企业提供移动端自动化测试方案。产品构成包括以下几个方面:提供自动化测试框架,企业可基于自身的实际业务编写测试case,低成本、高效率,一套case可在Android、iOS两端同时运行;真机调度管理,自研机柜,所有测试都基于真机,并模拟真实用户操作;多维度测试支持,包括安装卸载测试、自动化的功能测试与性能测试、稳定性测试及遍历测试。除自动化测试外,其余无需单独编写case,测试框架已预置;多平台测试覆盖,包括Android、iOS、H5及小程序等全部主流平台;提供详尽的测试报告,包括执行结果统计、详细的错误日志,以及执行过程的记录与截屏等。此外,蚂蚁金服提供的真机云测产品可以应用在实际项目的研发、测试、灰度以及上线等各个阶段。蚂蚁真机云测的核心优势蚂蚁金服真机云测产品的核心优势包括以下四点:软件层面,提供专业领先的自研测试框架;硬件层面,提供稳定高效的自研机柜;产品全局层面,真机云测可与mPaaS产品体系及企业内部软件高度整合,实现一体化服务;部署实施层面,整套真机云测产品支持私有化部署,保证产品的安全性和技术的可控性。首先,软件层面我们自研了测试框架,测试case直接运行在PC上,通过WebDriver连接协议向被测端发送测试执行指令,而在被测端,构建核心代理层,负责接收、解释和分发指令给测试手机。软件层面的自研,保证了测试方案具备如下优势:不侵入被测App代码:测试端和被测端之间有一层WebDriver通用协议,既起到了连接的作用,又可将两端完全隔离,测试case不需侵入被测App;统一测试语言:连接协议WebDriver实际为普通的Http+Socket协议构成,理论上支持这两种协议的语言都可以拿来编写case,我们将Android、iOS端的case编写语言统一为JAVA,而H5和小程序统一为JS,深度兼容Appiumcase编写格式,极大的降低了case编写、迁移和维护成本;更好的执行稳定性:框架针对不同的平台和手机厂商做了大量的适配,并通过Stateful等监控链路连接状态,构建重连机制,确保了测试的设备兼容性和执行可靠性。其次,硬件层面我们自研了连接HUB、机柜,自定义网络设备、拓扑,有效降低网络干扰,从硬件层面确保了执行高效性与稳定性,相比市面上直接采购的硬件设备,掉线率降低80%。具体如下:自研HUB:HUB是连接手机和PC的通路,我们自主设计了HUB电路板,实现一台PC可直连十台以上手机,做到硬件连接层面稳定、可控,极大提升了连接并发数,降低PC投入成本;自研机柜:采用特殊屏蔽材料,屏蔽外部网络信号干扰,同时机柜内置无线AP,确保测试手机处在稳定的网络环境中,并可模拟特定网络环境。最后,真机云测产品支持与mPaaS产品体系及企业自建系统实现功能打通与高度整合。与mPaaS产品体系;mPaaS提供了移动研发协同平台和实时发布组件,真机云测产品与二者天然打通,构建代码提交、构建打包、发起多种测试及版本发布一整套自动化的服务闭环;与企业内部系统:若企业自建了持续集成平台,真机云测可通过API的形式与之打通,同样实现一体化的自动构建、测试平台二、舆情分析身处信息爆炸的时代,任何一个散落在应用内部、应用市场、外部站点等渠道细微的产品问题都可能迅速爆发,早在几年前,蚂蚁内部对此就非常重视,开始研发高效、智能的舆情产品,如今这样的产品已深入应用到蚂蚁几大业务线。站在mPaaS的角度上,也不断收到了企业相关的诉求,如何帮助企业更广泛即时的发现App问题,也逐渐纳入到了我们的考虑范畴。结合内部产品的经验,我们认为企业需要的舆情产品,需要具备如下几大特征:海量抓取:需及时、高效的同时抓取多个渠道海量内容,以确保分析的客观、准确、实时;自动聚合:借助自然语言处理技术,需对抓取的内容自动聚合,彻底避免人工介入;智能预警:需通过机器学习技术,对问题智能预警,将问题发现在萌芽;深度分析:除基本的趋势、热度监控,更能分析语义,提取情感、口碑信息,分析事件转播路径;问题处置:发现问题后,需提供统一、低成本的问题处置能力。mPaaS提供的移动舆情分析产品,采集应用内、应用市场及外部站点的内容,通过机器学习及自然语言处理技术,为企业构建舆情监控-分析-预测-处置的闭环能力。帮助企业发现与跟踪产品问题,收集产品建议,危机公关,辅助市场调研、产品营销与竞争分析。移动舆情分析产品可应用于需求计划、发布、分析、运营及公关等多个业务环节。产品构成主要包含如下几部分:内容采集:移动端采集应用内反馈、应用市场评论及微信公众号内容;PC端采集微博及其他外部媒体站点,客户可自定义内容源,确保所需渠道全部覆盖;数据计算:允许客户订阅关键词组,并针对关键词,通过机器学习及自然语言处理技术,进行热度计算、情感计算、口碑热词提取、相似内容聚合及垃圾内容过滤等;舆情应用:提供舆情的监控-分析-预测-处理的应用闭环,监控环节,监控反馈走势、内容热度及舆情原文等;分析环节,分析情感倾向、区域舆情及话题分布等;预测环节,预测可能爆发的舆情事件,并自动归因;处置环节,提供应用内反馈回复,并与mPaaS产品体系及企业内部系统打通,提供问题的归一化处理。蚂蚁移动舆情分析的核心优势蚂蚁金服移动舆情分析的核心优势包括以下四点:应用内、应用市场反馈:除监控外部媒体,更提供了针对应用内部、应用市场、公众号等移动端内容的采集、分析和处置能力;预测与归因智能化:通过智能化的手段,预测舆情事件,并自动归因;产品深度打通:支持与mPaaS产品体系以及企业内部系统深度打通;支持私有云部署:支持整套舆情分析服务私有化部署,从物理层面确保隐私性。移动端内容抓取上,一方面,我们将支付宝客户端用户反馈功能对外封装,提供反馈内容上报能力;另一方面,我们与前面介绍的真机云测产品结合,通过真机自动化测试的手段抓取应用市场、公众号的内容,需要特别提到的是,市面上的同类产品,若没有真机云测产品的辅助,抓取到的移动端渠道一定是不全的。舆情事件智能预测上,舆情分析产品利用机器学习技术,通过对事件转播特征、传播速度及事件本身特征等因素建模,将舆情事件发现在萌芽期。同时,对预测事件进行智能化归因,自动分析事件传播,寻找传播的核心路径与关键点,以辅助后续问题的跟进。在产品整合上,移动舆情分析产品同样支持和mPaaS产品体系以及企业内部系统实现深度打通与整合。首先,舆情产品可以直接和mPaaS体系中的研发协同平台对接,由后者缺陷管理模块处理舆情问题并同步结果给舆情平台。其次,舆情产品还可以和智能投放产品结合,在投放前,通过舆情分析产品分析目标受众,使投放过程更具有针对性,在投放后,又可通过舆情产品监控活动在全网的传播情况。类似地,舆情分析也可以和企业内部的办公系统、营销系统等实现打通。移动舆情分析对企业的业务价值舆情分析产品对企业的业务价值非常广泛,主要表现在:收集和处理日常用户反馈;监控App新版本发布后的问题反馈;监控产品、品牌口碑和热度;帮助企业挖掘内容营销方向,收集营销素材,分析营销效果;辅助产品需求阶段的市场和竞品调研;为区域性企业,提供区域舆情定向分析;最后,舆情分析提供的预测、归因能力可以与企业公关结合,让公关活动更加及时、有效。点击阅读更多,查看更多详情

February 25, 2019 · 1 min · jiezi

蚂蚁移动开发平台 mPaaS 3.0 智能化 + 生态化

摘要: 以“数字金融新原力(The New Force of Digital Finance)”为主题,蚂蚁金服ATEC城市峰会于2019年1月4日上海如期举办。分论坛上,蚂蚁金服高级产品专家张亮做了主题为《蚂蚁移动开发平台 mPaaS 3.0 智能化 + 生态化》的精彩分享。演讲中,张亮代表蚂蚁金服发布了移动开发平台 mPaaS 3.0。3.0主要以智能化和生态化为主题,助力客户做精细化智能运营,做业务开放,并推出小程序解决方案助力客户构建自有的 App 生态。张亮 蚂蚁金服高级产品专家本次的分享主要围绕以下内容展开:一、mPaaS的发展历程二、mPaaS 3.0的产品体系三、mPaaS的智能化升级四、mPaaS小程序的生态构建一、mPaaS的发展历程2016年12月,mPaaS发布了 1.0 版本正式对外,1.0主要是想要延续支付宝的金融属性,服务金融行业,因为我们相信作为同一类别的公司,支付宝走过金融行业都要走的一条路。支付宝的经验是可以复制的。当时跟很多金融机构做了深入的沟通,也了解了很多金融 App 的现状,我们发现大部分金融机构其实都已经有了 App, 但随着2C行业的崛起,金融机构开始越来越重视 App 的体验和性能,做 App 已经不是难事了,而是要做好的 App,解决性能问题,提高用户体验。所以1.0优先开放支付宝的底层开发框架、UI库、消息推送、网关服务以及移动分析能力,并以组件化的方式提供服务,让用户可以自行挑选适合自己需求的组件,像搭积木一样快速构建 App 基础架构和通用能力。随着在行业的深入,我们发现一些走在前列的金融机构的业务逐渐成熟,开始希望对客户进行细分,进行精细化运营,做数字化转型,期间重庆农商行就提出了智慧银行的概念,重点是建设数据采集,分析平台。同时由于互联网金融的兴起,金融机构,包括其他机构的推出新产品的节奏,包括研发节奏都越来越像互联网公司,都希望能够加快新功能上线的节奏,可快速扩展更新,应对突发事件,增加 App 的动态化能力。顺应客户的需求,mPaaS 2.0版本逐步开放发布平台、热修复、离线包、数据同步,自定义分析等能力,更深入地改变企业移动开发的模式,助力企业做数字化转型,打造动态化超级 App。随着时间地推移,金融机构对用户有了更深刻的理解,同时对技术提出了更高的要求。为了更有效地利用数据,提高运营的ROI,APP需要向智能化方向发展。另外,小程序作为2018年技术圈的热点,同样引起了金融行业的重视,金融公司普遍选择小程序作为抢占市场的利器。因此,蚂蚁金服将小程序框架抽离出来,进行产品化输出,金融机构可以基于此构建自己的APP 生态。二、mPaaS 的产品体系三年的深耕细作,mPaaS 不仅积累了数百家付费用户,同时也极大程度地丰富了产品体系。mPaaS产品体系主要分为三层:首先,是动态灵活的前端能力,目前mPaaS能够提供Native、H5、支付宝小程序三大开发框架;100+的UI控件;以及包括扫码,本地缓存,客户端埋点等20+功能性SDK,可以让开发者快速接入搭建App所需要的基础能力。其次,是坚实的移动中台能力,除了客户端开发之外,mPaaS还提供了移动中台中台能力,可以实现对App的整个生命周期的管理,包括App研发、测试、发布、分析、运营在内的各个环节。最后,是稳定的后台连接能力,mPaaS为客户提供了移动王冠和大文件通道来服务不同的场景,为用户开发APP提供了一个高稳定、高可靠以及高效率的后台连接服务,支持 App 与后台服务的连接。三、mPaaS 3.0的智能化升级“智能化升级”是mPaaS历经两个版本迭代与升级后的自然过渡。是市场发展,客户需求驱动的结果。 企业开发了App,之后产生了一定规模的数据,那么如何利用数据做到精细化、智能化运营,如何针对不同用户完成个性化的决策与推荐,这些是mPaaS智能化要关注的问题。市场需求市场的发展要求企业做精细化运营,要求更好的产品体验,而从市场需求上看,智能化的落地场景,也分别与“运营”和“体验”相关。运营层面,利用数据,实现千人千面、个性化营销,预测事件趋势及用户行为,更深入的洞察用户,提取用户画像;体验层面,通过智能化的技术实现文字、图片、语音识别,提升用户操作体验,驱动业务创新。蚂蚁业务的进化其实,支付宝也符合上述市场需求的发展顺序。支付宝刚起步时确保服务7*24小时在线可用。mPaaS 1.0主要帮助金融级APP提高兼容性和稳定性,与支付宝一样,强调服务可用。接下来,mPaaS 2.0提倡精细化运营,用数据管理服务,在系统内部建立数字化体系,实现大数据平台。有了数据之后,mPaaS 3.0进而实现智能化平台以支持决策。在市场需求发展道路中,一直走在市场前列的蚂蚁金服致力于将技术分享给更广阔的金融行业,一起为更多用户提供更优质的服务。mPaaS智能化特色智能化能力的落地一直比较困难,因为AI/ML都需要大量的数据来做模型训练和判断依据,需要大量的系统改造和对接。但 mPaaS 中的移动分析服务(MAS)本身就提供客户端数据采集能力,而底层自带的智能化平台包含与 MAS配套的AI模型和决策能力,所以 mPaaS基于本身的数据就可以进行精确的预测,提供的所智能化能力都可以开箱即用。mPaaS平台的智能化特色主要包括三点,即数据,场景和轻量级。数据:自带数据源mPaaS 2.0 中已经为数据化转型实现了一整套数据采集机制,包括机型环境信息、用户行为、数据相关的闪退和卡顿信息、组件使用情况以及自定义事件,基于这些数据就可以对智能预测模型进行预测。如下图右边的例子说明了基于mPaaS可以实现的功能。以扫二维码进站为例,存在某些用户生成了二维码但没有进站,通过算法建模及模型训练,分析这些用户的特征,智能预测,找到未来可能同样发生此类事件的群体,针对性地运营,比如推荐教程指导用户使用二维码等,这使得APP扫码进站功能的使用率得到了极大提升。场景:一体化移动智能场景有了数据和智能引擎之后, 智能化还需要结合场景落地。mPaaS 提供了从 App 研发,测试,发布,分析,运营全生命周期的管理,天然就提供了很多智能化的应用场景。例如前面介绍了mPaaS如何通过对比部分用户的行为特征,智能预测,推测出大群体里未来一段时间可能同样发生此类行为的用户群体。那针对这些群体就可以有针对性的进行灰度发布,消息推送,智能营销,AB测试等。mPaaS提供的数据+场景可以让智能化快速的落地,无需任何的系统对接和研发工作。轻量:客户端智能化解决方案AI的轻量化是蚂蚁金服不断追求的目标。AR红包是近年春节流行的游戏, AR红包70%的扫描和识别任务都在客户端进行,只有不到30%的任务在服务端进行。主要是因为,蚂蚁可以通过后台的训练模型生成客户端识别模块,直接在客户端就可以完成大部分的识别。基于 AR 红包的具体实践,mPaaS推出了轻量化的客户端智能化解决方案,AI模型运行在客户端。智能组件发布本次发布,除了对mPaaS整体做全面的智能化升级之外,也推出智能化相关的独立组件:移动智能投放:通过智能决策引擎,让营销内容个性化的展示给适合的群体;移动舆情分析:对应用内、应用市场及外部媒体内容,做热度、情感、口碑等智能化的分析,对舆情事件智能预警及归因;多媒体服务:除了实现基本文件上传、下载以外,还支持图片的特征提取、智能处理和识别。四、mPaaS 3.0小程序的生态构建小程序是2018年最火的技术之一。目前,各大入口级APP都推出了自家的小程序开发平台,小程序的生态逐渐形成。同时,许多传统行业的公司希望把业务迁移到小程序上以实现业务转型,许多创业公司也想借着小程序发展,横跨行业开发小程序的套件和工具,这些都是小程序生态流行的体现。小程序产品化输出mPaaS将整套支付宝小程序平台进行了产品化包装,提供了小程序框架、UI组件、IDE,并提供匹配的运维、分析平台。让企业用户可以基于 mPaaS 小程序来构建自有 App 生态,让其他开发者为自己的APP开发小程序。同时开发的小程序也可以通过简单的修改就能够投放到阿里生态的 App 中,节省开发成本。mPaaS 小程序的目标是可以做到开发一次多端投放, 打通“客户自有App”、“阿里系App”及“mPaaS生态 App”三方,起到了“串联互通”的作用。框架-小程序标准开放框架小程序框架主要负责渲染小程序的页面,运行时依赖的各种技术,包括网络、存储、蓝牙等,mPaaS平台提供了这些服务。蚂蚁金服为支付宝小程序开放了两千多个业务能力接口,包括支付信用等。此外,高德地图也基于支付宝小程序做了拓展,实现了室内导航框架定点监测。框架-扩展能力使用小程序框架扩展,能够让 App获得更好的开发体验和更强的扩展管控,使用一整套PaaS平台可以把所有情况都管控起来。小程序框架提供JSAPI拓展,如从小程序页面发起的支付调用通过JSAPI调用接入方Native的服务,Native提供的接口允许用户拓展,实现各种各样的交互;另外,小程序的每一个页面都存在生命周期,生命周期的每个阶段都提供了钩子,基于此用户可以实现定制事件,比如页面加载前事件等。IDE-小程序包上传/发布IDE除了提供写代码的平台,还支持将代码上传到服务端,由服务端进行代码的打包。此外,小程序IDE还支持直接对接mPaaS管理后台。比如,12306开发者登录小程序IDE开发之后,可以把代码上传到mPaaS后台,由mPaaS后台打包,再下发到12306。IDE-小程序真机测试/远程调试另外,真机调试是本地研发时经常用到的技术。mPaaS提供了两种途径的真机调试功能。如果APP提供扫码能力,用户可以通过扫描IDE生成的二维码运行小程序;如果APP不提供扫码能力,mPaaS允许用户在IDE里直接将代码包推到发布平台,进而通过白名单机制下载测试包,从而实现真机调试。IDE-基于模板快速启动同时,IDE还提供了许多模板,可以帮助用户快速地搭建自己的小程序。PaaS-小程序发布服务此外,小程序还提供发布服务的平台。用户在IDE中开发小程序包后,通过该平台加密上传将小程序包发布到用户手机上。PaaS –小程序分析服务蚂蚁金服还提供了数据采集的平台,集成了数据埋点、数据分析等解决方案,支持采集的数据包括用户点击行为等。提供的数据分析页面允许监测系统的技术指标。当然,蚂蚁金服还允许用户自定义埋点和数据采集项,能够支持业务相关的数据采集平台的搭建。以上就是mPaaS 3.0发布的所有内容。总之,蚂蚁金服实现了mPaaS的智能化升级,同时推出mPaaS小程序,能够帮助大家构建属于自己APP的生态。点击阅读更多,查看更多详情 ...

February 25, 2019 · 1 min · jiezi

技术风险防控平台:打造金融交易系统的故障免疫能力

摘要:以“数字金融新原力(The New Force of Digital Finance)”为主题,蚂蚁金服ATEC城市峰会于2019年1月4日上海如期举办。分论坛上,蚂蚁金服高级技术专家王亚宏做了主题为《技术风险防控平台:打造金融交易系统的故障免疫能力》的精彩分享。演讲中,王亚宏介绍了蚂蚁金服的技术风险保障体系,同时分享了蚂蚁金服如何将自己在过去多年积累的的实践经验通过TRaasS平台分享给整个金融生态圈的合作伙伴,助力金融机构建立稳定可靠的金融业务。王亚宏 蚂蚁金服高级技术专家本次的分享主要围绕以下内容展开:一、金融级分布式架构面临的挑战和机遇二、蚂蚁金服技术风险保障体系的整体规划三、TRaaS技术风险防控平台整体介绍四、蚂蚁金服技术风险防控能力实践五、技术风险防控平台如何助力合作伙伴一、金融级分布式架构面临的挑战和机遇软件产品向分布式架构和微服务转型已经成为行业的共识。在架构的转型过程中,作为守门员的企业运维保障部门面临着巨大的挑战。这些挑战包括但不限于:1:产品需求变更频繁、软件开发速度也越来越快,但运维保障部门由于缺乏完善的保障措施只能通过控制发布次数来降低风险,使得业务更新速度下降。2: 从大型机器转移到PC服务器使得系统单机故障率更高,现有的容灾与FO机制能否支撑系统准确地运行仍存在疑问。3:从单体架构迁移到微服务架构要求系统进行全面的回归测试,这带来了巨大的测试工作量。4:,分布式系统中的一条调用链路可能横跨多个系统,问题排查变得非常困难。5:,对金融行业而言,解决分布式系统中的数据一致性问题也非常关键。从另外一个角度看,企业运维保障部门也应看到分布式架构带来的机遇。在过去,单体系统发布新版本通常要求产品通过层层测试来保证新版本上线后不出问题。但分布式系统允许使用真实用户和流量实现线上验证以确保产品上线后出现问题带来的影响最小。分布式架构使灰度验证和快速发布成为可能。此外,从前只能在线下进行的模拟演练,现在可以随时在生产环境中进行。全链路压测还让获取真实流量成为可能,让我们能在每次大促时做到高枕无忧。在过去十年中,蚂蚁金服的运维保障体系也经历了深刻的变革。通过抓住架构升级带来的机遇,通过技术和平台提升解决系统问题,运维保障部门做到了在架构升级的过程中平滑过渡,系统保持非常高的可用性的同时充分享受架构升级带来的收益。二、蚂蚁金服技术风险保障体系下图是蚂蚁金服的技术风险保障体系的总体设计图。在目标层,蚂蚁金服的运维保障部门力求做到99.99%的系统高可用、系统全年无重大资金安全故障,同时实现系统运维零成本。这些也是蚂蚁金服给客户的SLA承诺。治理层:与蚂蚁金服的团队、文化和制度相关。为了应对技术风险领域的挑战,蚂蚁金服组建了技术风险保障部门,聚焦于使用技术升级运维和质量保障手段以解决系统面临的技术风险难题。同时,蚂蚁金服建立了一系列制度保证系统内的任何变更都符合可监控、可灰度、可回滚的“三板斧”要求。在运营层:设置了四道防线。前两条防线即需求和研发包含了风险评审、自动化测试等常规内容。第三道防线通过灰度发布和蓝绿发布实现产品变更的验证和管理。第四道防线即系统监控,保证了系统在线上运行过程中具有最小的运行风险。最后一层是平台层:技术风险保障体系的目标、治理和运营层需要依赖平台层来实现,平台层包括业务监控、演练中心、预案中心和变更管控等各种技术平台。三、TRaaS技术风险防控平台蚂蚁金服将其构建技术风险保障体系的实践和经验沉淀下来,形成了TRaaS技术风险防控平台,开放给金融生态圈的所有合作伙伴。下图右侧的能力闭环展示了技术风险防控平台的核心方法论,分为三个部分。风险基线衡量了系统的风险保障能力,使风险保障人员了解系统的风险防控水平。监控/巡检、预案自愈以及演练属于第二个部分,确保系统在发生问题是能快速发现,定位和自动修复。此外,变更是系统风险的最大来源,蚂蚁金服对系统变更进行了严格的管控,确保所有变更都符合三板斧制度。下面我们分别看一下这三个关键环节中,平台具体所做的事情。风险基线风险基线这部分核心要解决的问题是如何评估我们当前的风险保障能力,以及发现哪些地方存在风险隐患。 所有做风险管理的同学都会面对一个问题:“风险分母到底有多大?”,这是一个理论上就无解的问题。那怎么办呢,蚂蚁金服的实践经验是接受风险本身的未知性,但在平台中将已知的风险场景沉淀下来,确保未来不再犯类似的错误。通过对这些沉淀下来的风险场景的风险保障能力的自动检查,来对我们的风险保障能力做个打分,并且发现存在问题的地方。具体到平台中的做法是:第一步,先获取到所有和风险相关的实体的元信息,包括应用,服务,网络,容器,物理机 等各个层次的风险实体,并将他们通过业务链路或部署拓扑关联起来。 第二部:将风险知识沉淀为风险模型,一个风险模型的内容可以用一句话来概括当XXX类型的风险实体具备XXX属性时,必须有XXX,XXX。。。。 的风险保障措施。这里的保障措施包括 监控,巡检,核对,预案,演练等一系列能力。 有了第一步,第二步获取到的信息,我们可以获取到一个相乘的笛卡尔集。在这个集合中可以清晰的看到,基于我们已有的风险知识沉淀,我们当前的技术风险保障水位如何,哪些地方是存在隐患的。针对每个风险点,风险揭示关注系统是否有完整的风险保障措施,以及对保障措施不完整的风险点应当如何处理等问题。风险揭示为一 线风险防控人员包括业务负责人和架构师等都提供了监控视角,以评估系统的风险防控能力,应对当前系统的风险防控问题。总的来说,将风险防控经验沉淀为风险模型,辅以完善的系统元信息和保障机制,风险揭示平台提高了系统发现风险的能力。风险处理风险处理平台对接各种监控系统获取监控报警信息,再将收集到的监控信息聚合为风险事件。对于业务报警信息或单机报警信息,在聚合为风险事件后,处理平台提供标准的分析引擎(同时支持自定义引擎)帮助提供异常链路,相关变更,主成分等各种辅助定位信息。在有了对应的信息后,给用户推送自动的应急自愈预案,或手工的业务应急预案。 最后,在问题解决后对整个故障进行复盘,将一些新产生的风险知识沉淀到风险基线中去,最终实现闭环。变更管控蚂蚁金服的内部统计数据显示,80%的系统生产故障都来自于代码变更,因此变更管控对技术风险防控而言至关重要。大规模系统内往往包含着几十乃至上百个变更来源,一旦线上发生问题,很难第一时间找到对应的变更,变更本身的质量也很难有效控制住。变更管控平台利用变更接入API整合所有系统变更,使变更可见。同时,变更管控平台还提供了变更编排,变更灰度检查,变更预检,变更结果监控等能力。确保所有生产变更都符合可灰度,可监控,可回滚的三板斧原则。 同时,通过提供变更关联,以及变更回滚来加快线上问题处理速度。其他SaaS服务上面所讲的整体体系,大家可能会感觉特别重,整个体系实施起来的组织成本,技术成本都还是比较高的。 这点上确实如此,要想系统性的对技术风险进行托底保障,确实需要一套相对比较重的体系和机制来。对于一些小型企业,或者说想解决一些特定领域的问题的客户,技术风险防控平台还提供了一些轻量级的SaaS服务,包括全链路压测、资金安全监控、流量仿真测试、高可用巡检以及智能监控等。这些服务都可以在蚂蚁金融科技的公有云或专有云平台上轻松获取,快速提升某一方面的能力。四、蚂蚁金服技术风险防控能力实践下图是我这边摘抄出来的一些内部运行数据,可以让大家更有体感的去感受蚂蚁金服技术风险保障体系是怎么运作的。第一张图是1218红蓝大攻防的数据,在蚂蚁内部,有一支专门负责搞破坏的技术蓝军,他们每天所做的事情就是不停的对系统进行攻击,注入,制造高可用和资金安全故障。以此来检验我们的发现机制,应急机制是持续有效,不断保鲜的。蚂蚁金服在执行红蓝攻防演练时做到了每5分钟产生超过500个故障场景,同时保持每周超过200次故障场景演练。第二张图是今年双11大促前,我们所做的技术保障工作。为了应对双十一大促的流量高峰,蚂蚁金服进行了长达三个月甚至更长时间的全链路压测、预案验证以及线上巡检炎症。高频率的故障场景演习以及高时长的大促技术备战使得蚂蚁金服具有极强的防御能力。第三张图摘自技术风险保障周报,这里能看到的是蚂蚁金服技术风险防控团队在日常工作中也不在断优化系统的风险防控能力。蚂蚁金服通过将容灾仿真演练、高可用常态演练、资金安全常态演练以及自愈检查等纳入每周,每天都要做的事情,以此保证蚂蚁金服系统能够持续稳定安全地运行。五、技术风险防控平台如何助力合作伙伴我们认为传统的风险保障体系具有以下三个特点:第一, 风险保障设计聚焦于风险出现后的问题处理,系统本身风险防控能力处于未知状态,只能被动地等待出现“黑天鹅”事件。第二, 风险保障部门通过严格控制发布周期、尽量不发布和不变更来避免系统出现风险事件。第三, 每次新版本发布都伴随着大量测试资源的投入和层层评审来降低新版本中出现风险的概率。下一代技术风险防控体系具有以下四个特征:第一, 风险保障通过建设反脆弱的系统来降低意外事件带来的影响,聚焦于系统健壮性的提升。第二, 强调风险保障能力可见,并且能够通过持续演练持续保鲜。第三, 重视灰度变更,强化变更过程的监控能力和回滚能力以降低系统变更风险。同时,通过技术手段和制度规约限制每次变更的影响范围,使变更可控。第四, 依赖于自动化测试、线上压测和线上仿真测试等技术手段来降低系统风险。蚂蚁金服技术技术风险防控平台希望能在这个技术升级的变革过程中。将我们积累的保障能力沉淀为平台能力,将平台能力开放给我们的合作伙伴。将我们积累的风险知识沉淀为沉淀为模型,规则。将风险知识来分享给生态圈中的合作伙伴们。系统需要通过持续演进得以不断完善,而蚂蚁金服的愿景在于将自身在技术风险防控中的平台积累及实践经验分享给整个金融生态圈的合作伙伴,让生态圈中的伙伴们能通力合作、共享风险保障技术,更重要的是共享风险防御知识,一起打造稳定可靠的在线业务。点击阅读更多,查看更多详情

February 24, 2019 · 1 min · jiezi

蚂蚁金融科技守护金融安全,蚂蚁风险大脑助阵

摘要:以“数字金融新原力(The New Force of Digital Finance)”为主题,蚂蚁金服ATEC城市峰会于2019年1月4日上海如期举办。金融智能专场分论坛上,蚂蚁金服大安全副总经理王黎强做了主题为《金融科技守护金融安全》的精彩分享。演讲中,王黎强分享了蚂蚁金服逐步完善的金融风控体系,介绍了蚂蚁金服的风控理念以及蚂蚁金服风险大脑的核心模块,展示了蚂蚁风险大脑所开放的领域和所能够提供的能力。王黎强 蚂蚁金服大安全副总经理蚂蚁金服十余年风控路金融的核心是风控,它是一切金融业务创新的基础。蚂蚁金服的风控安全能力并不是来自于实验室,而是来自于真实的业务场景、攻防实战和业务发展的需要。蚂蚁金服大安全部门的职责非常明确,就是保障业务的安全,帮助业务拓展到全球每一个角落,服务好每一个普通民众。蚂蚁金服的前身——支付宝诞生于2004年,支付宝成立的初衷是为了解决买卖双方的信任保障问题。因此,在网上买卖双方进行交易的过程中,支付宝就承担了交易担保的职责。蚂蚁金服一直把风控安全作为生命线,2005年推出了“你敢付我敢赔”应用保障体系,解决了客户支付的安全顾虑; 2013年,蚂蚁推出了余额宝,大家开始把大额资金放到支付宝里,支付宝因此开始变成一个真正的大钱包,而对于资金安全也提出新的挑战。网商银行上线之后,蚂蚁金服构建了基于银行服务线上化的整套风控解决方案,这套方案将会解决信贷账户安全以及反洗钱等问题。支付宝在2017年推出了“你敢扫我敢赔”,在用户保障初心回归和客户承诺的背后,是蚂蚁金服的强大风控安全能力在进行着守护。蚂蚁金服的风控理念蚂蚁金服是中国数字金融的开创者与领导者。从第一天开始,蚂蚁金服的业务就发生在线上,通过支付场景,就连接现实物理世界和数字世界。数字世界最显著的特征就是数据可存储、可计算和可量化,基于数字化模型,蚂蚁可以对每笔交易作出实时的风险量化预测。蚂蚁金服的所追求的风控理念是安全与体验的平衡。安全和体验就像是天平的两端,一端需要考虑控制资损,另外一端需要考虑为客户提供更好的互联网体验。风险防控不是一个非黑即白的过程,它充满着不确定性。蚂蚁金服主要依靠计算力、数据和算法这三项基础,构建了风险精准识别与智能化决策的能力。计算力是蚂蚁金服数据风控体系的底盘,它支撑着蚂蚁金服处理每天亿万用户的交易数据,并进行风险识别。第二个基础能力是数据,它是风险大脑的能量和血液,结合着算法流动起来,构建风控防控的闭环。第三个基础能力是算法,基于用户、环境和网络的数据信息之上,运用算法做出精准的判断和决策。蚂蚁金服风控演进之路蚂蚁的风险大脑是蚂蚁金服风控的杀手锏,他不是一天建成的,而是经过一代又一代安全人的共同努力,经历多次迭代和业务发展需要构建出来。整体而言,蚂蚁金服的风险大脑走过了三个阶段:人工规则(平衡):这一阶段主要构建了基于专家经验的人工规则风险防控体系。其优点是防控的覆盖性、调整的及时性较好,但同时带来了极高的管理成本。一方面,规则体系会随着业务系统的复杂度不断增加,规则易上难下,相互之间的关系很难理解。另一方面,规则的设计和维护依赖人力,对人的经验性和专业度要求较高。规则 + 模型(攻防):人工规则在提高防控覆盖性的同时,很难平衡好用户体验。这是因为在实际风控中,存在一些人很难发现的复杂的风险行为模式,很难通过规则来进行刻画。同时,大量积累的数据,也让我们可以从经验驱动的风控,进入数据驱动的风控。利用数据来训练风控模型,与人工经验刻画的规则,形成互补。风控系统进入了第二阶段——运用规则加模型来建设风险防控体系。大数据智能(协同):模型是基于已知风险进行训练得到的,其虽然能识别很多复杂的风险模式,能够较好地防控已知风险,但也具有一些不足。一方面,其对于未知风险的识别往往不足,风险实时攻防是风控引擎的常态,过程中需要较多规则去补防。另一方面,模型的更新需要大量数据来驱动,同时需要较多的专家人力投入,对未知风险的防控存在一定滞后性。这个问题逼迫我们走向大数据智能和人机协同风控。利用弱监督算法去感知可能存在的异常潜在风险,采用人机交互学习算法技术,让专家反馈能更快的推动模型的进化调整。在这样的演进过程中,蚂蚁金服建立了有效的风险感知体系,风险防控具备自我优化、自我进化的能力,使得风险防控体系变得更加及时和有效。蚂蚁风险大脑,全方位、立体化的风控体系对于蚂蚁风险大脑而言,总体具有四个核心板块,分别是风险检测预警、风险识别决策、风险智能优化和风险分析洞察。这四个板块相互协同,紧密协作,并且构建起来了全方位、立体化的风控体系。风险监测预警相当于风控系统的眼睛,用于感知和预测风险;风险识别决策构建了全方位和立体化风控引擎,是风险精准防控的判断依据;风险分析洞察则是专家和机器智能在人机协同过程中的基于多维度进行的分析洞察;风险智能优化就是风险防控中构建的自学习和自优化的能力,应对风险攻防的及时性和有效性;蚂蚁风险大脑——风险预警体系。面对未知风险的挑战,需要基于算法监测识别个体异常和群体异常。通过风险异常识别发现风险群体,通过图形化、可视化方式展现出来,并通过风险专家来判断风险和异常。这个过程会实时推动必要的模型/策略调整,形成风控系统的闭环优化。另一方面,面对蚂蚁金服如此大体量的业务,如此多的交易,就需要风险大盘进行整体风险监测,快速定位问题并分析原因,保证风控系统自身的安全运行。蚂蚁风险大脑—风险识别体系。风险识别体系是风险防控的精髓与核心,对其而言主要有两个关键词——全方位和立体化。对于全方位而言,需要构建基于人、环境、网络、设备、黑名单、突变和冲突的全方位风险扫描体系。传统专家风控体系是一个网状平面,风险防控最担心的就是一层被击穿。而蚂蚁金服的风控体系是立体化的、层层深入下去的防控体系,如果某一层被击穿,还会有下一层,其比平面风险防控体系要更加安全、可靠。值得一提的是,蚂蚁金服从2017年开始尝试“端+云”风险防控方式,因此节省了大量时间和成本。蚂蚁风险大脑—风险决策体系。在风险识别的背后其实是安全策略模型,如果识别出某个客户可能存在风险,最终判断就需要从多个维度进行智能化分析,比如会考虑实际使用环境的可用性和适用性以及风险偏好,在风险防控和用户体验上做智能化的个性化决策。蚂蚁风险大脑—风险智能优化。蚂蚁金服也在实践通过人机协作方式更快、更有效地防控风险。这里主要有3种方式。第一个,就是基于单个案例分析用户标签,通过时间、空间、行为的过程连接在一起还原出案件发生的整个过程,定位出风控体系中疏漏点和风险点。第二个,就是基于知识图谱进行分析,发现欺诈团伙及其特性。第三个是策略智能推荐,可以将智能算法和专家经验快速结合,应用到风险防控体系的调整。此外,蚂蚁金服的风控体系具备自我优化和自我进化的能力,能够针对一些新的风险形势及时作出调整和优化,兼顾及时性和有效性.蚂蚁风险大脑,三大领域开放赋能上面为大家介绍了蚂蚁金服的风控体系构建,接下来分享蚂蚁风险大脑能力所开放的三个领域。第一个是政务民生领域,蚂蚁金服为航空、税务、中国铁路等输出风险安全能力;第二个是银行领域,在“信息化、线上化、数据化”数字银行升级大背景下,蚂蚁金服为他们输出了风险防控的方案和基础设施;第三个是监管科技领域,相比于金融科技侧重于金融创新和金融发展,监管科技更加侧重于金融风险防范,其实这就是天平的两端。在数字金融大发展的背景下,既要鼓励金融创新,还要防范好金融系统性风险。蚂蚁风险大脑,助力政务民生安全在社会民生安全方面,蚂蚁金服主要提供了六大核心能力:账户保护、营销保护、渠道保护、交易保护、移动端保护和内容保护。通过这六大能力,蚂蚁风险大脑针对一个行业可以输出一整套解决方案。这里以东航APP为案例进行说明,蚂蚁风险大脑为东航提供了全链路的风控解决方案,防止了黑产盗用用户积分以及不法分子恶意占用飞机座位等问题。蚂蚁风险大脑,加码银行风控过去,银行通过网点提供服务,因为固定地点服务模式使得银行的服务半径就在几公里之内。而随着移动互联网的普及,用户需要触手可得的金融服务。数字银行转型势在必行,就是为用户提供随时、随地、随需、随取的永远在线的金融服务。在这样的转变中,银行的金融服务模式也发生了非常大的变化,这一过程的数字化改造成本非常巨大,而风险防控模式在数字化银行的今天也发生了翻天覆地的变化。下图所示的是蚂蚁风险大脑为银行所提供的整体解决方案,该方案包含了从底层数据,到中间组件化模块,再到模型和策略体系的构建过程。目前,蚂蚁风险大脑已经和很多银行展开了合作,而蚂蚁金服承担的是在银行进行数字化转型过程中的助力者角色。蚂蚁风险大脑,辅助金融风险防控“监管科技”是监管和科技的结合体,兴起于2014年英国FCA所提出的“监管沙箱理论”,其基于政策和流程的设计,为金融机构在一定范围内提供创新空间,随后香港和新加坡等引入该理论,在金融监管中发挥一定促进作用。近年来,中国随着互联网金融和科技金融的迅猛发展,在为生活带来便利的同时,也伴随着非常多的风险。跨行业、跨市场、跨地域的风险不断加剧。比如e租宝、钱宝网以及善林金融等,涉案资金过百亿,并且涉案人群特别广泛。这已经对社会民生和金融稳定造成了较大影响。因此,蚂蚁金服积极响应国家的战略部署,助力政府应用科技防范和化解金融风险。在监管科技上,蚂蚁风险大脑运用大数据、云计算、区块链和人工智能等先进技术,构建更加高效、更加有效的智能化监管系统。对于产品系统设施而言,助力金融监管部门实现金融风险监测实时化、识别智能化和风险可视化。蚂蚁风险大脑主要提供了两大能力。在微观层面,助力地方监管部门识别单个企业机构的金融风险。在宏观层面,助力地方监管部门,对于某个行业和区域进行整体风险排查感知其整体风险水位。蚂蚁风险大脑在防范化解金融风险方面提供三个方面的价值:管机构、防风险和促发展。管机构; 一个地方监管部门,管理的企业少则几万多则十几万,因此蚂蚁金服需要帮助监管部门用科技化、现代化、数据化的方式管理机构,实时感知企业的动态风险。防风险; 在管机构基础上,需要能够实现对机构的风险识别和洞察,对于风险实现早预警、早处置、早识别。促发展; 金融就像水,而科技则推动了水的流动速率,促使它流进实体经济。金融科技更加侧重于发展,监管科技助力监管部门有效识别金融风险,构建双方平衡发展的模式,这也是蚂蚁金服与地方政府一同探索的平衡发展模式。科技让风控更智能、更安全从事后风控,到大数据实时风控,再到AI驱动智能化风控,不断发展的科技使得风控变得更加智能和安全。针对于每个具体的行为,都可以使用智能化算法精确判断是否存在风险,并构建立体化的风险防控。依托于数字技术的发展,金融作为现代经济的血液,科技加速了金融的流程性和普适性,让金融服务更活跃更完善,蚂蚁风险大脑一直会守护着你的金融安全。点击阅读更多,查看更多详情

February 24, 2019 · 1 min · jiezi

金融行业容器平台落地路径:敏捷响应业务更迭

演讲中,盛延敏主要围绕着蚂蚁金服容器平台的双模容器落地路径,所能够提供的金融级云原生能力,以及所经历的实际严苛场景验证三个方面,分享了蚂蚁金服容器平台如何帮助企业实现敏捷响应业务更迭。盛延敏 蚂蚁金服高级技术专家基础设施架构变革 正彻底改变业务应用的交付模式首先来回顾一下软件行业交付模式的演变历史。对于交付模式而言,最早大家比较关心IaaS层面的内容,以OpenStack、KVM或者VMWare等为代表的技术大行其道,这个阶段大家比较关心虚拟机,无论是应用还是分布式中间件,基本都是通过虚拟机来搭建和运维。随着技术的进步,通过应用无状态化以及运维自动化的技术升级,软件交付来到了一个新时代。这个时代,大家所关注的中心上移到了PaaS领域,此时应用开发者重点关注自己所开发的应用,我们可以称之为Cloud-Ready时代,这个阶段中,无论是分布式应用、所使用的语言环境还是运维和监控,都统一地托管在PaaS平台之上的。在Cloud-Ready时代,如果想要支持多语言和多框架,就要提供语言和框架的技术规范化体系,例如buildpack。技术再向前发展,到了2013年,2014年左右,一家叫做Docker的公司走向了历史舞台,它将cgroup和namespace等技术实现了极致的产品化,创造性地提出了Docker image方式,此时软件交付的方式,应用程序本身和语言、技术栈以及框架紧密地耦合在一起了,此时就进入了CaaS时代,我们可以称之为Cloud-Native的时代。在这个时代,无论是对于中间件还是微服务,完全都可以通过云原生的方式来实现,这就带来了架构、效率和运维体验的极致提升。基于容器技术的“云原生”已成为事实标准一直以来,容器技术所标榜的就是通过“集装箱”方式进行交付。将软件和产品打包成为标准化的镜像,而镜像就像是集装箱一样,可以帮助我们将软件托运到任何地方、任何环境,并通过一键拉起实现部署。现在,无论是技术社区、开发者还是云服务提供商,都在积极地拥抱和倡导基于容器技术的“云原生”时代。CNCF云原生计算基金会在2018年的调查显示“云原生生产环境应用增长200%,社区关注度和评估量增长近3倍”。当然,各个组织的关注点可能不同,但是却集中在技术可用性以及生产效率的提升上。这里以Kubernetes和Docker为代表的云原生技术的关注度在下图中也得到了非常明显的体现。当谈到云原生的时候,大家不禁要问什么是云原生呢?是Kubernetes还是Docker?还是OCR的标准?CNCF组织给云原生定义了一套标准,而这套标准也在不断实践和打磨,在2018年他们也推出了一个新版本,主要包括了五种技术基石和三种云形态支持。这五中技术基石分别是容器、服务网络、微服务、不可变基础设施以及声明式API,三种云形态则包括公共云、混合云以及专有云。这里需要强调的是声明式API,这是云原生时代与之前时代所不同的重要理念。在过去,无论是运维还是发布,都习惯于通过发送指令来启动触发事件,比如启动或者停止某一个应用。而到了云原生时代,声明式API的理念就是“想要一个应用处于运行状态、想要一个应用处于停止状态”,其表达了终局一致的理念,非常像微积分中的无穷逼近,而Kubernetes的编排模块就是按照这个理念设计出来的,其帮助运维人员维护最终状态的驱动,这就是云原生时代和过去的运维时代非常显著的区别。蚂蚁金服内部经过多年的实践所得到的启示是:云原生架构看上去非常好,但是云原生架构的转型并不能一蹴而就。在向云原生架构转型的过程中也存在很多问题,原本的系统中存在很多历史负债,无法实现直接跳跃,因此需要渐进的架构方案;在架构转型上,需要一个大规模、金融级的运维支持,帮助把云原生方案落地;此外,还需要经过各种严苛的金融业务场景的验证,我们的技术和产品才能提供给蚂蚁金服的客户和合作伙伴使用。渐进式云原生架构转型方案传统架构迁移到云原生架构会存在一些普遍问题,也有一些存在于开发和运维同学心中的观点。第一种理念就是非常习惯原本虚拟机方式,希望所做的任何事情都围绕机器展开,比如能够到机器上看日志,并跟着机器和IP进行监控,包括将遗留资产体系的打通都是围绕这套理念实现的。第二种理念就是虽然非常推崇云原生,也希望使用云原生,但是云原生在自身实际场景中不能玩得转。因为云原生基于终局一致的理念,这就意味着它意味着无时无刻都在发生着生产线的变更。这些变更能否在传统运维体系中被完全的掌控和监控,以及调度上的一些问题能否完全解决,都存在很大的问题,这或许会使得开发和运维对于云原生架构产生不信任甚至怀疑。还有一种观点就是敢于拥抱云原生,但是又不得不思考已有系统架构和已有业务的问题,考虑能否在传统方式和云原生方式之间提供一些“鱼与熊掌皆可兼得”的方案。蚂蚁金服所提供的答案是“Yes”。蚂蚁提供了在已有基础设施之上进行的渐进式架构迁移方案,提供了基于Docker VM轻量级虚拟机的一整套运维体系的规范,同时也提供了支持云原生体系的发布、部署以及运维的规范,前者能够完美地对接已有资产和系统监控运维等体系,使得用户不再困惑。此外,蚂蚁还将中间件最佳实践落实到大规模容器云平台之上,包括了Service Mesh的落地以及跨语言的架构。并且容器平台还会能够支持弹性能力的诉求,可以完美适配公有云、专有云、混合云。传统与云原生架构的双模运维可以大致将组织里面的业务分成两种形态:稳态和敏态。稳态业务就是比较传统和核心的业务,这些业务追求稳定和已有的运维、监控、发布体系的兼容性,蚂蚁的金融行业容器平台对此提供了轻量级虚机解决方案,融合了分组、灰度以及无损发布等能力,让用户在原本的体系下可以继续玩得转。另外一方面,一些创新业务归为敏态业务,这些业务需要依靠大数据以及人工智能等技术的创新,因此对于敏态业务而言,拥抱新开源框架和新技术的诉求非常强烈。而基于云原生理念,可以帮助用户迅速拥抱这种变化,并且可以提供安全容器技术帮助用户实现更好的安全隔离,保证企业和组织实现“鱼与熊掌兼得”,使得传统业务和创新业务同步开展,并且是通过一套系统支持业务的迭代和创新。原生支持Service Mesh对于任何一个组织而言,想要打造像蚂蚁金服这样经过十几年的风雨所打造的微服务架构体系,所付出的努力是难以想象的。而如今,开发组织可以通过对接SOFA Mesh,并通过在透明的Pod里面注入Sidecar的方式,瞬间就可以拥有蚂蚁金服沉淀多年的分布式架构技术红利,从而对企业产生巨大的帮助。此外,结合蚂蚁金服分布式单元化架构能够提供更好的容灾服务能力。混合云架构此外,蚂蚁金服还提供了混合云架构解决方案,因为底层使用的是容器化技术,所以可以对接物理机以及OpenStack和VMWare等虚拟化平台。云上和云下都提供了一套体验一致的分布式中间件, 通过服务目录的方式把应用所依赖的服务进行注入,这样就能够在公有云和专有云之间灵活地分配负载。当大促来临的时候,可以将负载更多地切换到公有云上,大促结束之后可以将资源返还给公有云资源池,这样就可以从根本上解决企业运营所面对的资源伸缩问题。大规模金融级运维能力支撑整套体系想要落地就离不开大规模金融级运维能力的支撑。接下来从规模化集群运维、单元化发布、一体化监控分析以及自动化流程编排这四个方面来阐述大规模金融级运维能力。大规模集群运维能力;对于Kubernetes而言,想要管理好一套Kubernetes集群也都是极其困难的事情,更不用说在生产环境中运维和管理多套Kubernetes集群了。经过技术性探索,蚂蚁金服创造性地提出了Kubernetes管理方案——K8S on K8S(KOK)。对于KOK方案而言,底层存在元集群,元集群采用一体化方式安装起来的。元集群的使命就是运维业务集群,而元集群的组件不会频繁升级,所以安装基本上就是一次性工作。元集群安装完成之后,可以在其上面布置组件,这里包括两个非常关键的组件——Machine Operator和Cluster Operator,这两个组件负责对业务集群进行管理。业务集群的Master节点将会作为元集群的Worker节点加入到元集群中,这样就可以被元集群管理,而且其部署方式也是通过原生方式实现的,这样就可以在元集群的监控面板上看到业务集群所有中控组件,可以很方便地进行运维、升级以及版本管理,借助Kubernetes本身的能力就可以管理好多个集群,带来业务和运维上的便利。单元化发布运维能力;蚂蚁容器平台支持VM和容器的双模发布,这一点在单元化发布方式上都会得到支持。单元化发布能够轻松支持万级规模节点的蓝绿机房发布和灰度发布,实现分钟级容灾能力。并且我们支持蓝绿发布和弹性伸缩。下图中最右边是动态弹性伸缩模块,其通过收集监控告警信息并进行处理分析,进而触发弹性伸缩,这样就会保证对外服务的节点和副本数保持不变。一体化监控分析;对于任何大规模的容器平台或者PaaS平台而言,监控体系都是其灵活的“眼睛”。通过一体化监控分析平台可以统一收集监控数据,并送到后台模块进行统一处理和建模,经过一体化处理以后,数据将会呈现在可视化大盘上,并可以提供给业务进行自定义分析。一体化监控体系也秉持“兼容社区,拥抱开源”的理念,不仅可以兼容社区的很多技术,还在积极地回馈社区。自动化流程编排;如下图左侧所示的是自动化流程编排的案例,提供了一些基于条件驱动的模块,使得用户可以不断沉淀流程模板并且加入到流程商店里面,通过这种方式将一些碎片化的操作整合成相应的自动化流程模板。在下图中右侧还展示了两个例子,一个是网商银行故障自愈场景的案例,另外一个则是蚂蚁国际运维自动化场景的案例。严苛金融业务场景实际验证下图所示的是网商银行基于Kubernetes的实践案例。网商银行是中国第一家将核心系统全部运行在SOFA Stack分布式架构和蚂蚁金融云上的银行。在2018年的大促中,网商银行做了几件重要的事情,包括了分布式架构数据的拆分、单元化、异地多活等。另外,我们还对底层的技术架构进行了升级,将2018年双11和双12的所有工作负载都放在了容器引擎上,支持了数千的节点和数万的Pod,有力的保证了网商银行大促的增长达到400%。我们的容器引擎带来的技术红利包括两部分,一方面使得资源利用率更高,通过高密度部署,使得物理机使用效率更高;再加上离线在线任务的弹性混部,使得CPU的利用率也更高。第二方面还使得整体效率得到提升,从原本的软件包交付模式变成集装箱式交付模式,使得整个开发者工程效率和SRE运维效率都得以提升。如今,蚂蚁金服提供了一整套基于Ant Stack的产品,在这套产品之上构筑了各种场景的解决方案,包括了大家所熟知的蚂蚁风控、生物识别、移动开发以及蚂蚁国际化和国际支付等,都是基于蚂蚁的容器引擎之上的。可以说,蚂蚁金服围绕着金融决策和金融分析打造了一整套面向合作伙伴的解决方案。现在,蚂蚁将自身的容器引擎实现了产品化,以PaaS平台的方式为企业客户提供。通过蚂蚁金服DevOps的持续集成流水线,大规模容器化发布部署以及高效资源编排的能力,最终为用户提供完整的PaaS平台。如下图所示的是蚂蚁的SOFA Stack为大家提供的PaaS产品和解决方案的全景图。在这张全景图上有两套标准、三个平台和三种形态。第一个标准就是Cloud-Provider,其意义在于屏蔽掉底层基础设施的区别,通过Cloud-Provider可以对接虚拟化平台,隔离底层基础设施。另外一个标准Open Service Broker API的标准,这一标准主要是为了拥抱生态,使得PaaS平台能够更好地扩展能力,对接计算、存储以及网络能力,将这些能力整合进来,变成应用PaaS平台所能提供的能力,这也符合整个社区一贯的做法。三个平台则是应用与容器平台、监控分析平台以及容灾应急平台。三种形态是公有云,专有云和混合云。最上面则会提供面向各种应用场景的解决方案,包括了DevOps解决方案、容器化解决方案、单元化结构,以及异地和同城容灾的解决方案。关键信息总结双模容器落地路径;在本文所分享的主要内容中,蚂蚁金服的金融行业容器平台所提供的能力为的是帮助企业和组织降低云原生的门槛,从传统的运维模式渐进地迈向云原生。蚂蚁金服为企业提供了所谓的双模能力,使得新系统既能够兼容已有系统,也能提供面向未来的体验。金融级云原生能力;蚂蚁金服助力企业打造大规模金融级运维能力,使金融级云原生能力能够落到实处,带来真正的价值。实际严苛场景验证; 无论是产品还是内核的整个平台,都是包括网商银行和支付宝在内的整个蚂蚁金服,在经过了一系列严苛的场景考验之后,最终才会交付给客户和合作伙伴的。点击阅读更多,查看更多详情

February 24, 2019 · 1 min · jiezi

Android调试神器stetho使用详解和改造

本文由云+社区发表作者:NaOH概述stetho是Facebook开源的一个Android调试工具,项目地址:facebook/stetho 通过Stetho,开发者可以使用chrome的inspect功能,对Android应用进行调试和查看。 功能概述stetho提供的功能主要有:Network Inspection:网络抓包,如果你使用的是当前流行的OkHttp或者Android自带的 HttpURLConnection,你可以轻松地在chrome inspect窗口的network一栏抓到所有的网络请求和回包,还用啥Postman,还用啥Fiddler哦(开个玩笑,一些场合还是需要用的,毕竟Stetho Network Inspection 只是用来查看回报和发送数据是否有误,在开发初期,调试API还是用Postman快一点)Database Inspection:数据库查看,可以直接看到当前应用的sqlite数据库,而且是可视化的,不需要再下什么奇怪的工具或者用命令行看了。这个确实非常棒!View Hierarchy:布局层级查看,免去使用查看布局边界的花花绿绿带来的痛苦和卡顿,而且能看到每个view和layout的各类属性。Dump App:命令行拓展,构造了一个命令行与Android App的交互通道,在命令行输入一行命令,App可以收到并且在命令行上进行反馈输出。Javascript Console:Javascript控制台,在inspect的console窗口,输入Javascript可以直接进行Java调用。使用这个功能,得先引入facebook/stethostetho-js-rhino和mozilla/rhino。在这里,笔者先承认这个文章有点标题党了——在我实际使用体验过后,第一感觉是:这个所谓神器也没有特别神的感觉…造成首次使用感觉不太好的原因在于:使用教程不太全,尤其是Dump App的使用,不管是在README还是wiki中都没有太多的叙述。Network Inspection 抓包只封装了OkHttp和HttpURLConnection的,然而大多数情况下,各个应用开发者可能都会有自己的一套网络请求库,它提供的接口这时候就不太友好了,得自己包装一下。View Hierarchy 用起来有一丝丝的不方便,因为调试视图还包括了Android系统自带的状态栏布局之类的,导致Activity的布局天然处于一个比较深的节点,每次还要手动一层一层展开(其实这里有一个技巧,后面会提到)。Javascript Console 感觉是最鸡肋的功能,因为自带的console只能关联到application的context,能进行的操作非常有限,且在控制台写js调用Java层的函数是没有自动补全的,容易写错不说,要换成Js的语法也是相当费劲。就算解决这几个问题,也还是想不到什么合适的使用场景。后面将会对Dump App和Network Inspection进行详细介绍(其他的几个功能都比较简单)。初始化Stetho首先引入在安卓项目中引用必要的依赖包,可以使用gradle,也可以直接下载jar包。dependencies { compile ‘com.facebook.stetho:stetho:1.5.0’ } 需要注意的是如果使用Javascript Console需要额外引入facebook/stethostetho-js-rhino和mozilla/rhino。 然后在应用的Application初始化时,进行Stetho初始化。这些都在官网有详细的说明,不再赘述了。开始使用由于大部分功能依赖于Chrome DevTools 所以第一步你需要先打开Chrome,然后在浏览器地址栏输入:chrome://inspect 接触过前端开发或者Webview开发的捧油应该是很熟悉这个套路了。你会看到一个如下界面: inspect界面你会发现这里有两项,是因为我的这个示例应用有两个进程。由于App的每个进程都会单独创建一个Application,所以在应用包含多个进程时,Stetho也会为每个进程都初始化一次。那么这里我要调试的是主进程,就点击第一项inspect就行了。 接下来我们就开始搞事情了:View Hierarchy查看布局层级没啥好说的,但是之前提到,由于系统的view层级也包括进来了,所以我们Activity的Layout层级都很深,每次一层一层点开很难找,这里提供一个简便方法,在Elements面板,按Ctrl + F,搜索 @android:id/content 即可快速定位到我们当前界面根布局,例如这里的Constraintlayout:Database Inspection点击Resource-Web SQL即可查看App的数据库:Javascript Console在Console面板,输入context可以看到目前的ApplicationContext:输入如下代码弹出Toast:importPackage(android.widget);importPackage(android.os);var handler = new Handler(Looper.getMainLooper());handler.post(function() { Toast.makeText(context, “Hello from JavaScript”, Toast.LENGTH_LONG).show() });应用场景比较有限,但是mozilla/rhino这个Javascript引擎倒是挺有意思的,可以用来做一些有趣的事情,以后有机会再分享一下。Dump App官方对dump app的使用说明实在太少了,感觉非常捉急。研究了一番,大概知道了使用流程,即首先需要在App内,通过enableDumpapp方法注册自己的插件: Stetho.initialize(Stetho.newInitializerBuilder(context).enableDumpapp(new DumperPluginsProvider() { @Override public Iterable<DumperPlugin> get() { return new Stetho.DefaultDumperPluginsBuilder(context) .provide(new MyDumperPlugin()) .finish(); }}).enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context)).build())也可以使用默认的插件: Stetho.initialize(Stetho.newInitializerBuilder(this) .enableDumpapp(new DumperPluginsProvider() { public Iterable<DumperPlugin> get() { return (new Stetho.DefaultDumperPluginsBuilder(StethoNetworkApplication.this)).finish(); } }).enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context)).build())然后,stetho的github项目地址下有一个script文件夹:facebook/stetho-script 把这个文件夹下到本地,发现里面有几个文件: .gitignore dumpapp hprof_dump.sh stetho_open.py 说实话第一眼看上去根本不知道这东西干啥用的,dumpapp这文件看起来就跟可执行文件似的,但事实上它又不是exe,用记事本打开一看,是Python3的文件,我也是醉了…所以使用Python3.x来运行这个文件即可。(由于他还引用了stetho_open.py,为了看起来不那么别扭,我把几个文件都整合在一齐,搞了一个dump.py) 这里我并没有注册任何插件,但是由于Stetho自带了几个插件,我们可以看看他们的实现:例如files插件,来试用一下:即用户发送命令时,Plugin的dump方法会被调用,Plugin通过dumpContext.getStdout()来获取输出流,将反馈输出到命令行:public void dump(DumperContext dumpContext) throws DumpException { Iterator<String> args = dumpContext.getArgsAsList().iterator(); String command = ArgsHelper.nextOptionalArg(args, “”); if(“ls”.equals(command)) { this.doLs(dumpContext.getStdout()); } else if(“tree”.equals(command)) { this.doTree(dumpContext.getStdout()); } else if(“download”.equals(command)) { this.doDownload(dumpContext.getStdout(), args); } else { this.doUsage(dumpContext.getStdout()); if(!"".equals(command)) { throw new DumpUsageException(“Unknown command: " + command); } } }Network Inspection其实这也是重点之一了。我在这里添加了一个OkHttp的Inspector。 注意:此处有坑,因为你会发现用gradle添加的stetho依赖中没有StethoInterceptor这个类,你可以到stetho的github页面下载一下,同事需要跟你的OkHttp版本对应,因为2.x跟3.x对应的StethoInterceptor还有差异): 下载地址: facebook/stetho-okhttp3 facebook/stetho-okhttp 代码示例如下: public void testOkHttp(){Thread thread = new Thread(new Runnable() { @Override public void run() { String url = “http://www.zhihu.com/"; OkHttpClient.Builder builder = new OkHttpClient.Builder().addNetworkInterceptor(new StethoInterceptor()); OkHttpClient client = builder.build(); Request request = new Request.Builder() .url(url) .get() .build(); try { Response response = client.newCall(request).execute(); } catch (IOException e) { e.printStackTrace(); } }});thread.start();}运行这个函数,可以看到Network一栏的请求,每项网络请求发出时,Status处于Pending状态,收到回包后,Status等栏目都会变化,展示httpcode,请求耗时、回包数据类型等信息。当然这不是重点。重点是我们要对这个东西改造一下,他是如何抓下包来发送给Chrome的呢? 看一下StethoInterceptor的intercept函数,写了些注释:private final NetworkEventReporter mEventReporter = NetworkEventReporterImpl.get();public Response intercept(Chain chain) throws IOException { // 构造一个独特的eventID,一对网络事件(请求和回包)对应一个eventID String requestId = mEventReporter.nextRequestId(); Request request = chain.request(); // 准备发送请求 RequestBodyHelper requestBodyHelper = null; if (mEventReporter.isEnabled()) { requestBodyHelper = new RequestBodyHelper(mEventReporter, requestId); OkHttpInspectorRequest inspectorRequest = new OkHttpInspectorRequest(requestId, request, requestBodyHelper); // 请求即将发送,构造一个OkHttpInspectorRequest,报告给Chrome,此时Network会显示一条请求,处于Pending状态 mEventReporter.requestWillBeSent(inspectorRequest); } Response response; try { // 发送请求,获得回包 response = chain.proceed(request); } catch (IOException e) { // 如果发生了IO Exception,则通知Chrome网络请求失败了,显示对应的错误信息 if (mEventReporter.isEnabled()) { mEventReporter.httpExchangeFailed(requestId, e.toString()); } throw e; } if (mEventReporter.isEnabled()) { if (requestBodyHelper != null && requestBodyHelper.hasBody()) { requestBodyHelper.reportDataSent(); } Connection connection = chain.connection(); // 回包的header已收到,构造一个OkHttpInspectorResponse,发送给Chrome用于展示 mEventReporter.responseHeadersReceived( new OkHttpInspectorResponse( requestId, request, response, connection)); // 展示回包信息 ResponseBody body = response.body(); MediaType contentType = null; InputStream responseStream = null; if (body != null) { contentType = body.contentType(); responseStream = body.byteStream(); } responseStream = mEventReporter.interpretResponseStream( requestId, contentType != null ? contentType.toString() : null, response.header(“Content-Encoding”), responseStream, new DefaultResponseHandler(mEventReporter, requestId)); if (responseStream != null) { response = response.newBuilder() .body(new ForwardingResponseBody(body, responseStream)) .build(); } } return response; }所以整个流程我们可以简化为:发送请求时,给Chrome发了条消息,收到请求时,再给Chrome发条消息(具体怎么发的可以看NetworkEventReporterImpl的实现) 两条消息通过EventID联系起来,它们的类型分别是OkHttpInspectorRequest 和 OkHttpInspectorResponse,两者分别继承自NetworkEventReporter.InspectorRequest和NetworkEventReporter.InspectorResponse。我们只要也继承自这两个类,在自己的网络库发送和收到请求时,构造一个Request和Response并发送给Chrome即可。 发送部分示例:PulseInspectorRequest 继承自NetworkEventReporter.InspectorRequest public void reportRequestSend(PulseInspectorRequest request){ String requestId = request.id(); // request will be sent RequestBodyHelper requestBodyHelper = null; if (mEventReporter.isEnabled()) { requestBodyHelper = new RequestBodyHelper(mEventReporter, requestId); mEventReporter.requestWillBeSent(request); // report request send if (requestBodyHelper.hasBody()) { requestBodyHelper.reportDataSent(); } } }回包获取成功:public void reportRequestSuccess(PulseInspectorResponse response){ mEventReporter.responseHeadersReceived(response); mEventReporter.responseReadFinished(response.requestId()); String requestId = response.requestId(); String contentType = “application/json”; String encoding = null; InputStream responseStream = new ByteArrayInputStream(response.getResponseBody().getBytes()); InputStream responseHandlingInputStream = mEventReporter.interpretResponseStream( requestId, contentType, encoding, responseStream, new DefaultResponseHandler(mEventReporter, requestId)); try { if (responseHandlingInputStream == null) return; // 重点在这,这两行代码一定要加上,StethoInterceptor之所以不需要加, // 是因为OkHttp本身对请求采取了职责链式的处理, // 虽然在StethoInterceptor的intercept函数里没有进行read和close // 但是后续的Interceptor会进行这个操作,实际上这里,才把回包数据发送给了Chrome responseHandlingInputStream.read(response.getResponseBody().getBytes()); responseHandlingInputStream.close(); } catch (IOException e) { e.printStackTrace(); }}回包获取失败public void reportRequestFail(String eventId,String errMsg){ mEventReporter.httpExchangeFailed(eventId, errMsg);}至于PulseInspectorResponse 和PulseInspectorRequest如何实现,就依赖实际使用场景了。总结stetho 为开发者提供了一个很好的调试手段,但是自带的基础功能还比较弱,开发者可以根据自己的需求去改造。(不过官网文档是有点太少了……) 如果说这个工具有啥亮点,想来想去,大概App跟Chrome的通信,火狐的rhino引擎更可以被称之为亮点= .=|||3此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

February 15, 2019 · 3 min · jiezi

Flutter 2019 产品路线图正式公布

2019Flutter 1.0 的发布对我们来说是一个很重要的起点,长路漫漫,我们仍有很多工作要做。这里我们向大家公开我们的产品路线图(Roadmap)规划,一方面是保持开源项目的透明度,另一方面,开发者们也可以根据我们的工作优先级来制定更适合的工程方案。以下几点我们今年会着重关注:核心和基础易用性生态系统移动端之外的支持动态更新工具链我们的计划会根据大家的反馈以及新的市场变化来做调整,这份路线图里的内容不尽然是我们一定会完成的工作。如果你有任何反馈,我们鼓励你通过 Issuse,或者在我们的邮件群组等与我们保持联系。Flutter 是一个开源项目,我们鼓励你参与到我们当中来。版本发布使用 Flutter 的开发者们可以选择一个「频道」来「接收」我们的版本更新和变化,我们目前有四个频道:master、dev、beta 和 stable,质量和稳定性从前向后依次递增,发布速度当然也会是依次相对放缓。我们计划每个月发布一个 beta 频道的版本,这个发布通常会是在月初,全年会在 stable 频道发布四个较大的「正式」版本。在生产环境里,我们建议开发者们使用 stable 频发布的 Flutter 版本。如果你想了解更多关于我们的版本发布流程,可以查看 发布流程 这篇 Wiki。关注领域核心和基础我们的首要任务依然是为 Flutter 现有的核心和基础添砖加瓦:修复 Bug:Bug 修复的优先级主要是基于 Issue 下的互动数量,比如 GitHub 自带的一些针对 Issue 的表情互动,点赞等;性能调优:包括减少内存、引擎占用空间(包大小),提高帧率等。如果开发者们有特别的性能基准要求,可以通过 devicelab 测试数据给我们看一下;改进 Flutter 测试流程:以确保为开发者们提供稳定的版本构建不会出现版本回归;改进错误消息提醒:通过 Google 用户研究(User Research)团队的工作,使错误提醒更具备可操作性以及包含一些常见的解决方案;API 文档改进:特别是提供示例代码和图表等,让我们的 API 文档更易用。易用性为新晋使用 Flutter 的开发者清扫绊脚石,如:完善和满足希望使用混合工程(将 Flutter 集成到于现有的 Native 工程项目)的开发者们的需求,如提供新的插件模板和 Android 内嵌 API;更新 Flutter 官方文档以提供更详尽的文档和使用教程;在 Flutter 应用里管理 state 的最佳实践;更好的帮助 iOS 开发者:投入时间持续更新和维护我们的 Cupertino widgets;在非完整工具链和运行环境下更容易体验和使用 Flutter。生态系统在 Flutter 中生态系统意味着使用 Flutter 的开发者们可以便捷地完成任何他们想做的事情,甚至在 Flutter 框架不提供提供开箱即用支持的情况下也如此。我们花费了大量的精力在工具和基础设施建设的工作上,以支持围绕着核心 Flutter 技术而蓬勃发展的生态系统。Google 也会投入时间开发插件和工具来贡献这个生态。2019 年我们会特别关注的生态系统建设工作:更好的 C/C++ 库支持,包括从 Dart 到 C 或 C++ 之间的相互调用;推进官方开发 / 维护的 Packages(调用原生系统的插件和纯 Dart Package)达到与核心框架代码相同的质量和完整性;在 iOS 和 Android 上完成地图和 WebView 插件的开发;确保 Flutter 应用可以使用一些谷歌服务,比如应用内支付和 YouTube;提供本地推送通知和本地数据存储的支持。移动端之外的支持我们将继续把 Flutter 拓展到更多形态的终端,以实现我们的目标:构建一个便携 UI 工具包,在任何需要的地方画出每一帧像素。更好的支持键盘和鼠标的输入;完善可以让 Flutter 可以运行在 Web 平台的 Hummingbird 项目;继续尝试让 Flutter 运行在桌面级的平台之上(如 macOS 和 Windows)。动态更新Dart 语言平台为 Flutter 应用开发提供了热重载(Hot Reload)的特性,让开发者们无需重新部署就可以把代码推送到应用中去。Android 上的动态修复:让开发者直接将代码更新从服务器推送到 Android 应用里;动态载入:让应用里不常用的部分延迟加载。工具链继续投入精力支持 Visual Studio Code,Android Studio 和 IntelliJ,使它们能够作为开发 Flutter 的主力 IDE;增加对 Language Server Protocol 以及其他开放协议的支持;通过改进开发过程中的分析、调试体验,让开发者更简单地提高应用的整体质量和性能;持续提升模版的体验,让 Flutter 的上手开发既快又简单。里程碑及计划时间如果你对我们每个月将会发布什么感兴趣的话,你可以我们 GitHub 上的 milestones 页面查看。计划赶不上变化,我们的里程碑可能会因为某些 Issue 而被改变,所以我们不能保证每个里程碑的确定完成时间。欢迎对本文作出反馈。文/ Flutter 社区:(微信 ID:flutter-io)原始 Wiki 地址 https://github.com/flutter/fl… ...

January 18, 2019 · 1 min · jiezi

高可用、弹性动态的金融级移动架构在蚂蚁金服的演进之路

摘要: 支付宝作为国民级应用,当前全球用户已经超过 10 亿,提供了超过 200 项以上的服务,而崩溃率始终维持在万分之五以下,而且每天支付宝都上线新的功能和改进。做到今天这样的成绩,并不容易,是经过长时间的实践经验积累下来的。导语本文基于重岳在 2018 年 Arch Summit 北京站的分享内容进行总结,希望通过本篇文章介绍近些年来支付宝在移动端架构的上演进和思考,期冀能给读者们带来些许帮助。支付宝作为国民级应用,当前全球用户已经超过 10 亿,提供了超过 200 项以上的服务,而崩溃率始终维持在万分之五以下,而且每天支付宝都上线新的功能和改进。做到今天这样的成绩,并不容易,是经过长时间的实践经验积累下来的。支付宝的架构演进主要经历了三个阶段,如果用比喻的话,可以分为独木舟、战列舰和航空母舰三个阶段。独木舟时代支付宝刚推出移动端时,它的结构非常之简单,除了一些工具组件被划分为模块,业务代码都是糅合在一起。刚开始并没有太大问题,但是当我们的研发人员迅速增长时,问题开始变得棘手起来,仅仅举几个例子便可见一斑。研发同学晚上提交的可以运行的代码,到第二天早上来更新一下就完全不能用,原因是其他不相干团队提交代码覆盖或者污染了自己的代码。临近发布点的时候,通常是最忙的,但不是忙着赶功能,而是忙着解决合并代码产生的各种问题,不仅浪费时间,还耽误测试同学的宝贵时间。即使最后勉强发布了,稳定性和性能也是非常糟糕的,因为各个模块只管自己的,没有统一的规范,也缺乏统一的监控。最令 Android 开发头痛的是 65535 的问题,彼时 Google 还没有推出 multi-dex 的方案。这些严重的问题让我们的产品研发迭代变得无法持续下去,因此我们决定来一次彻底的重构,于是步入了战列舰时代。战列舰时代当设计新一代的客户端架构时,我们从三个方向进行思考:团队协作、研发效率、性能与稳定。团队协作方面,我们希望整个架构分层合理,基础层面,将通用能力下沉,为更多的上层业务服务,避免重复创造轮子;业务层面,各个业务团队能够独立开发管理,不会对不相关的业务造成影响。基于这个初衷,我们形成了下图这样的架构:整个客户端架构总共分成四层:业务层、服务层、组件层、框架层。业务层:只需专注于业务逻辑与界面的实现,当需要调用如支付这样的通用能力时,研发同学直接使用下层提供的服务能力,不需自己开发,如此能够保证核心能力有收口,方便监控。服务层:常用模块,如登录、支付、营销等,它们不仅自己是业务,也向其他业务提供自己的服务,我们将此类模块归类到服务层。组件层:这一层提供的是客户端通用能力,如安全、网络、多媒体、存储这些,它们提供稳定的接口给上层使用者,同时不断优化自身内部的性能和稳定性,作为客户端的基石,它们至关重要。框架层:最为关键的部分,包括容器、微应用、服务框架以及 Pipeline,客户端的微应用化、启动管理都依赖框架层的运作。我们将服务层、组件层和框架层合称为 mPaaS,即移动端上的 PaaS 服务。这些 PaaS 服务可以复用,我们不仅在支付宝里使用它们,也在其他集团应用,如蚂蚁财富、网商银行等中使用。业务分治要实现业务分治,最好的方式就在代码上能够进行隔离,大家不必在同一个 Codebase 中开发,避免代码合并冲突的现象,这个通常在 Android 上通常可以 aar 的方式来实现,但是可惜的是我们重构的时候 aar 还没出来,而且即使有 aar,也存在打包时间随代码体积增大线性增长的问题。我们的解决方案借鉴 OSGi 的概念,将整个客户端以 Bundle 为单位划分,每个 Bundle 可以包含自己的代码、页面和资源。读者可能会想,这究竟和 aar 有什么分别呢?其实区别很大!首先,Bundle 里的代码部分是已编译的 dex,当编译 apk 时,我们只需要合并 dex 即可,不需要像 aar 那样将 class 编译成 dex 再进行合并,这样大大节省了打包时间;其次,Bundle 是可以独立运行于自己的 ClassLoader 中的,并且我们可以通过壳代理的方式加载 Activity 等基础组件,使得动态下发业务成为可能;最后,Bundle 里还包含微应用、服务和 Pipeline 相关的配置信息,框架会根据这些信息启动相应的组件。mPaaS 的服务,即 Service 类似于 Spring 框架中的 Service,它对外提供接口服务,而使用者不需要知道如何初始化服务的实例以及生命周期管理,这些完全由框架来托管。使用者只需要知道目标服务接口类的方法参数即可,调用时通过框架提供的 API 来获取实例。对于服务的发布者来说,他在自己的 bundle 中声明接口类以及实现接口类派生的实例类,并注册相关信息到 bundle 的 manifest 文件中。这种做法的本质思想是 Inversion of Control,减少类之间的复杂依赖,避免繁琐的初始化工作。以依赖接口的方式进行开发,能够解除服务使用者对服务提供者的依赖,在服务提供者尚未完全开发完成时,使用者可以完全以 mock 的方式来模拟服务,而不需要修改自己的业务代码,当然,前提是双方协商好服务接口的协议。支付宝中的页面非常多,直接启动 Activity 或者 ViewController 对我们来说远远不够,我们选择在它们上面增加 MicroApp,即微应用的概念。微应用具备唯一的应用 ID,在框架中标识自己的存在。微应用具有统一的入口,根据使用方传入的字典参数来管理 Activity 或 ViewController。这样能够带来很多好处:只要应用 ID 和参数协议不变,使用方不需担心目标应用内部重构带来的影响,直接使用 Activity 或者 ViewController 类名造成的引用泛滥的问题不复存在。微应用的 ID 和字典参数特性,很容易生成 URL,从而实现外部应用使用 URL 跳转应用内页面。从数据的角度,我们可以按业务维度来统计用户行为数据。微应用的概念不仅适用于原生页面,同样也适用于 H5 和小程序。注册为 H5 或者小程序类型的应用 ID,框架会自动将启动过程 delegate 给 H5 或者小程序容器,而使用者完全不必关心应用 ID 对应的应用类型。综上所述,微应用化和服务接口所赋予的特性极大提高团队间协作效率,各研发小组之间的依赖更加简单,可以各行其道,更关注于自身服务的打造建设。性能优化我们一方面在架构上作出重大改变来提高研发效率,另一方面也在不断的进行性能优化,改善用户体验。我们主要从三个层面来着手:框架层面制定统一开发规范,业务方使用统一的线程池、存储、网络等组件,并按需进行加载,避免不必要的启动和耗时操作。引入 Pipeline 机制,业务模块如需在应用启动时进行初始化工作,必须使用 Pipeline。框架依据业务优先级确定业务初始化实际。利用 AOP 切面,对常用路径进行耗时统计,追踪性能瓶颈。基础指标对于常用指标,如闪退、ANR、内存、存储、电量、流量等,进行长期追踪。我们能够明确获悉每个版本之间这些指标上的差异,并进行采样分析,定位并解决问题。向下突破我们不仅仅在应用层面进行优化,同时也向下探索性能提升的可能性。在这方面,我们也收获颇丰,比如在 Android 上某些系统版本,通过在启动阶段禁用 GC 的方式获得 20%~30% 的启动时间缩减;在 iOS 上,利用系统本身的 Background Fetch 机制提高进程活跃时间,实现应用秒起。航母时代随着移动支付的不断普及,面对海量的用户和业务需求,高可用、弹性动态成为支付宝客户端更为艰巨的挑战。支付宝作为集支付、金融、生活为一体的服务平台,需要能够快速稳定的发布服务和引入第三方服务,同时对于用户的反馈和诉求必须能够积极迅速的响应。动态研发模式我们在研发模式上作出改变以业务快速迭代的要求,业务逐步由原生页面向 Web 混合页面迁移。原有的研发模式能够很好的满足团队协作的要求,但是随着业务规模的不断增大,代码量相应膨胀导致安装包太大,在iOS上一度超过代码段上限,无法通过 AppStore 审核,另外基于集中时间点的迭代发布,通常是一个月发布一个版本,远不能满足业务的更新速度要求。相较于原生应用开发,Web应用的优势非常明显:只需要一套代码,Web 应用可以在 iOS 和 Android 客户端中运行,能够相对减少人员的投入。每个用户日常使用的功能仅仅是支付宝庞大平台中的一小部分,H5 应用可以做到动态下发,因此可以消除冗余的存储,降低包大小。近些年来 React Native,Weex 等动态渲染引擎在社区非常活跃,但经过小范围的应用以及考虑到 Web 技术的不断发展以及其在业界公认的地位,我们最终还是选择 Web 技术作为动态研发模式的基础。Web 应用迭代摆脱了客户端集中时间点发布的束缚,各业务线迭代计划变得自主可控。打磨 Web 体验尽管 Web 应用优势明显,但在移动端上的短板也是显而易见的,它提供的用户体验、性能以及能力上的限制与原生应用有相当大的差距。为了弥补这些差距,我们做了大量的改进,主要在以下几个方面:前后端分离,我们将页面资源离线化,这样节省了资源请求消耗的时间,使得页面打开速度提升明显,解决了在网络环境较差下容易出现白屏的问题。同时,数据请求使用 native 网络通道,可优化的空间更大,安全性更高。差量更新,客户端更新某业务应用版本时,不需下载完整的新版本资源包,而是下载由发布平台根据客户端本地安装版本计算生成的体积更小的差量包,这样不仅能够节省带宽和流量,也提升了业务更新的速度。推拉结合,解决业务最新版本覆盖率的问题,每次发布新版本时,业务可主动触发消息到客户端,客户端收到通知后会更新该业务应用版本。同时,客户端会定时去检查服务端是否有版本发布,这样能够保证版本发布后大多数用户在短时间内获得最新的应用。容错补偿,客户端可能由于网络、安全或者存储权限等原因,不能使用或者及时获得离线包,这种情况我们也考虑进来了。我们在发布离线资源时,发布平台会自动生成对应的在线 URL 并配置到应用信息中,当客户端加载 Web 应用时发现离线包不可用,会立刻启用该url加载内容,能够最大程度保证业务可用性。Android 独立浏览器内核,Android 碎片化的问题自其诞生之初业已存在,而且目前看上去没有得以解决的迹象。不同系统、不同厂商中的浏览器内核同样存在差异,这导致层出不穷的兼容性问题令研发同学头疼不已,这也违背 Web 一统天下的愿景。为了彻底解决并掌控这些问题,我们引入了独立的 UC 浏览器内核并集成在应用中,这样所有的问题都集中到UC团队解决,变得非常可控,根据数据统计,使用 UC 浏览器内核后浏览器相关的闪退和 ANR 有明显的下降。同时,安全上出现的漏洞,我们可以在第一时间修复并发布,远比厂商升级更有效率。Web 应用全方位监控,资源加载异常、JS执行异常、白屏、加载耗时等性能数据会被收集上报至后台,可以及时发现异常。小程序我们不仅自身提供各种各样的服务,也需要引入第三方服务来服务更多的人群,以往我们只能引入简单的第三方 H5 页面,它们只能使用支付宝提供的少量功能,而且开发人员能力的差异导致用户体验不是很理想。小程序将支付宝的能力全面开放出来,从开发到测试皆有完整的 IDE 等工具链支持,同时 DSL 简单易用,对于第三方来说,能够快速开发上线一款体验和功能比以往更强大的小程序。本文作者:josephjin阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 15, 2019 · 1 min · jiezi

谈谈iOS获取调用链

本文由云+社区发表iOS开发过程中难免会遇到卡顿等性能问题或者死锁之类的问题,此时如果有调用堆栈将对解决问题很有帮助。那么在应用中如何来实时获取函数的调用堆栈呢?本文参考了网上的一些博文,讲述了使用mach thread的方式来获取调用栈的步骤,其中会同步讲述到栈帧的基本概念,并且通过对一个demo的汇编代码的讲解来方便理解获取调用链的原理。一、栈帧等几个概念先抛出一个栈帧的概念,解释下什么是栈帧。应用中新创建的每个线程都有专用的栈空间,栈可以在线程期间自由使用。而线程中有千千万万的函数调用,这些函数共享进程的这个栈空间,那么问题就来了,函数运行过程中会有非常多的入栈出栈的过程,当函数返回backtrace的时候怎样能精确定位到返回地址呢?还有子函数所保存的一些寄存器的内容?这样就有了栈帧的概念,即每个函数所使用的栈空间是一个栈帧,所有的栈帧就组成了这个线程完整的栈。栈帧下面再抛出几个概念:寄存器中的fp,sp,lr,pc。寄存器是和CPU联系非常紧密的一小块内存,经常用于存储一些正在使用的数据。对于32位架构armv7指令集的ARM处理器有16个寄存器,从r0到r15,每一个都是32位比特。调用约定指定他们其中的一些寄存器有特殊的用途,例如:r0-r3:用于存放传递给函数的参数;r4-r11:用于存放函数的本地参数;r11:通常用作桢指针fp(frame pointer寄存器),栈帧基址寄存器,指向当前函数栈帧的栈底,它提供了一种追溯程序的方式,来反向跟踪调用的函数。r12:是内部程序调用暂时寄存器。这个寄存器很特别是因为可以通过函数调用来改变它;r13:栈指针sp(stack pointer)。在计算机科学内栈是非常重要的术语。寄存器存放了一个指向栈顶的指针。看这里了解更多关于栈的信息;r14:是链接寄存器lr(link register)。它保存了当目前函数返回时下一个函数的地址;r15:是程序计数器pc(program counter)。它存放了当前执行指令的地址。在每个指令执行完成后会自动增加;不同指令集的寄存器数量可能会不同,pc、lr、sp、fp也可能使用其中不同的寄存器。后面我们先忽略r11等寄存器编号,直接用fp,sp,lr来讲述 如下图所示,不管是较早的帧,还是调用者的帧,还是当前帧,它们的结构是完全一样的,因为每个帧都是基于一个函数,帧伴随着函数的生命周期一起产生、发展和消亡。在这个过程中用到了上面说的寄存器,fp帧指针,它总是指向当前帧的底部;sp栈指针,它总是指向当前帧的顶部。这两个寄存器用来定位当前帧中的所有空间。编译器需要根据指令集的规则小心翼翼地调整这两个寄存器的值,一旦出错,参数传递、函数返回都可能出现问题。其实这里这几个寄存器会满足一定规则,比如:fp指向的是当面栈帧的底部,该地址存的值是调用当前栈帧的上一个栈帧的fp的地址。lr总是在上一个栈帧(也就是调用当前栈帧的栈帧)的顶部,而栈帧之间是连续存储的,所以lr也就是当前栈帧底部的上一个地址,以此类推就可以推出所有函数的调用顺序。这里注意,栈底在高地址,栈向下增长而由此我们可以进一步想到,通过sp和fp所指出的栈帧可以恢复出母函数的栈帧,不断递归恢复便恢复除了调用堆栈。向下面代码一样,每次递归pc存储的*(fp + 1)其实就是返回的地址,它在调用者的函数内,利用这个地址我们可以通过符号表还原出对应的方法名称。while(fp) { pc = *(fp + 1); fp = fp;}二、汇编解释下如果你非要问为什么会这样,我们可以从汇编角度看下函数是怎么调用的,从而更深刻理解为什么fp总是存储了上一个栈帧的fp的地址,而fp向前一个地址为什么总是lr?写如下一个demo程序,由于我是在mac上做实验,所以直接使用clang来编译出可执行程序,然后再用hopper工具反汇编查看汇编代码,当然也可直接使用clang的-S参数指定生产汇编代码。demo源码#import <Foundation/Foundation.h>int func(int a);int main (void){ int a = 1; func(a); return 0;}int func (int a){ int b = 2; return a + b;}汇编语言 ; ================ B E G I N N I N G O F P R O C E D U R E ================ ; Variables: ; var_4: -4 ; var_8: -8 ; var_C: -12 _main:0000000100000f70 push rbp0000000100000f71 mov rbp, rsp0000000100000f74 sub rsp, 0x100000000100000f78 mov dword [rbp+var_4], 0x00000000100000f7f mov dword [rbp+var_8], 0x10000000100000f86 mov edi, dword [rbp+var_8] ; argument #1 for method _func0000000100000f89 call _func0000000100000f8e xor edi, edi0000000100000f90 mov dword [rbp+var_C], eax0000000100000f93 mov eax, edi0000000100000f95 add rsp, 0x100000000100000f99 pop rbp0000000100000f9a ret ; endp0000000100000f9b nop dword [rax+rax] ; ================ B E G I N N I N G O F P R O C E D U R E ================ ; Variables: ; var_4: -4 ; var_8: -8 _func:0000000100000fa0 push rbp ; CODE XREF=_main+250000000100000fa1 mov rbp, rsp0000000100000fa4 mov dword [rbp+var_4], edi0000000100000fa7 mov dword [rbp+var_8], 0x20000000100000fae mov edi, dword [rbp+var_4]0000000100000fb1 add edi, dword [rbp+var_8]0000000100000fb4 mov eax, edi0000000100000fb6 pop rbp0000000100000fb7 ret需要注意,由于是在mac上编译出可执行程序,指令集已经是x86-64,所以上文的fp、sp、lr、pc名称和使用的寄存器发生了变化,但含义基本一致,对应关系如下:fp—-rbpsp—-rsppc—-rip接下来我们看下具体的汇编代码,可以看到在main函数中在经过预处理和参数初始化后,通过call _func来调用了func函数,这里call _func其实等价于两个汇编命令:Pushl %rip //保存下一条指令(第41行的代码地址)的地址,用于函数返回继续执行Jmp _func //跳转到函数foo于是,当main函数调用了func函数后,会将下一行地址push进栈,至此,main函数的栈帧已经结束,然后跳转到func的代码处开始继续执行。可以看出,rip指向的函数下一条地址,即上文中所说的lr已经入栈,在栈帧的顶部。而从func的代码可以看到,首先使用push rbp将帧指针保存起来,而由于刚跳转到func函数,此时rbp其实是上一个栈帧的帧指针,即它的值其实还是上一个栈帧的底部地址,所以此步骤其实是将上一个帧底部地址保存了下来。下一句汇编语句mov rbp, rsp将栈顶部地址rsp更新给了rbp,于是此时rbp的值就成了栈的顶部地址,也是当前栈帧的开始,即fp。而栈顶部又正好是刚刚push进去的存储上一个帧指针地址的地址,所以rbp指向的时当前栈帧的底部,但其中保存的值是上一个栈帧底部的地址。至此,也就解释了为什么fp指向的地址存储的内容是上一个栈帧的fp的地址,也解释了为什么fp向前一个地址就正好是lr。另外一个比较重要的东西就是出入栈的顺序,在ARM指令系统中是地址递减栈,入栈操作的参数入栈顺序是从右到左依次入栈,而参数的出栈顺序则是从左到右的你操作。包括push/pop和LDMFD/STMFD等。三、获取调用栈步骤其实上面的几个fp、lr、sp在mach内核提供的api中都有定义,我们可以使用对应的api拿到对应的值。如下便是64位和32位的定义_STRUCT_ARM_THREAD_STATE64{ __uint64_t __x[29]; / General purpose registers x0-x28 / __uint64_t __fp; / Frame pointer x29 / __uint64_t __lr; / Link register x30 / __uint64_t __sp; / Stack pointer x31 / __uint64_t __pc; / Program counter / __uint32_t __cpsr; / Current program status register / __uint32_t __pad; / Same size for 32-bit or 64-bit clients /};_STRUCT_ARM_THREAD_STATE{ __uint32_t r[13]; / General purpose register r0-r12 / __uint32_t sp; / Stack pointer r13 / __uint32_t lr; / Link register r14 / __uint32_t pc; / Program counter r15 / __uint32_t cpsr; / Current program status register */};于是,我们只要拿到对应的fp和lr,然后递归去查找母函数的地址,最后将其符号化,即可还原出调用栈。总结归纳了下,获取调用栈需要下面几步:1、挂起线程thread_suspend(main_thread);2、获取当前线程状态上下文thread_get_state_STRUCT_MCONTEXT ctx;#if defined(x86_64) mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&ctx.__ss, &count);#elif defined(arm64) _STRUCT_MCONTEXT ctx; mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&ctx.__ss, &count);#endif3、获取当前帧的帧指针fp#if defined(x86_64) uint64_t pc = ctx.__ss.__rip; uint64_t sp = ctx.__ss.__rsp; uint64_t fp = ctx.__ss.__rbp;#elif defined(arm64) uint64_t pc = ctx.__ss.__pc; uint64_t sp = ctx.__ss.__sp; uint64_t fp = ctx.__ss.__fp;#endif4、递归遍历fp和lr,依次记录lr的地址while(fp) { pc = *(fp + 1); fp = fp;}这一步我们其实就是使用上面的方法来依次迭代出调用链上的函数地址,代码如下void t_fp[2];vm_size_t len = sizeof(record);vm_read_overwrite(mach_task_self(), (vm_address_t)(fp),len, (vm_address_t)t_fp, &len);do { pc = (long)t_fp[1] // lr总是在fp的上一个地址 // 依次记录pc的值,这里先只是打印出来 printf(pc) vm_read_overwrite(mach_task_self(),(vm_address_t)m_cursor.fp[0], len, (vm_address_t)m_cursor.fp,&len);} while (fp);上面代码便会从下到上依次打印出调用栈函数中的地址,这个地址总是在函数调用地方的下一个地址,我们就需要拿这个地址还原出对应的符号名称。5、恢复线程thread_resumethread_resume(main_thread);6、还原符号表这一步主要是将已经获得的调用链上的地址分别解析出对应的符号。主要是参考了运行时获取函数调用栈 的方法,其中用到的dyld链接mach-o文件的基础知识,后续会专门针对这里总结一篇文章。enumerateSegment(header, [&](struct load_command *command) { if (command->cmd == LC_SYMTAB) { struct symtab_command *symCmd = (struct symtab_command *)command; uint64_t baseaddr = 0; enumerateSegment(header, [&](struct load_command *command) { if (command->cmd == LC_SEGMENT_64) { struct segment_command_64 *segCmd = (struct segment_command_64 *)command; if (strcmp(segCmd->segname, SEG_LINKEDIT) == 0) { baseaddr = segCmd->vmaddr - segCmd->fileoff; return true; } } return false; }); if (baseaddr == 0) return false; nlist_64 *nlist = (nlist_64 *)(baseaddr + slide + symCmd->symoff); uint64_t strTable = baseaddr + slide + symCmd->stroff; uint64_t offset = UINT64_MAX; int best = -1; for (int k = 0; k < symCmd->nsyms; k++) { nlist_64 &sym = nlist[k]; uint64_t d = pcSlide - sym.n_value; if (offset >= d) { offset = d; best = k; } } if (best >= 0) { nlist_64 &sym = nlist[best]; std::cout << “SYMBOL: " << (char *)(strTable + sym.n_un.n_strx) << std::endl; } return true; } return false;});参考函数调用栈空间以及fp寄存器函数调用栈也谈栈和栈帧 运行时获取函数调用栈 深入解析Mac OS X & iOS 操作系统 学习笔记此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 14, 2019 · 3 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

这样做动画交互,一点都不费力!

本文由云+社区发表作者:paulzeng导语:Lottie是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,可实现非常复杂的动画,使用也及其简单,极大释放人力,值得一试。一、简介Lottie 是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,能分析 Adobe After Effects 导出的动画,并且能让原生 App 像使用静态素材一样使用这些动画,完美实现动画效果。现在使用各平台的 native 代码实现一套复杂的动画是一件很困难并且耗时的事,我们需要为不同尺寸的屏幕加载不同的素材资源,还需要写大量难维护的代码,而Lottie可以做到同一个动画文件在不同平台上实现相同的效果,极大减少开发时间,实现不同的动画,只需要设置不同的动画文件即可,极大减少开发和维护成本。官方效果图:二、如何使用Lottie支持多平台,使用同一个JSON动画文件,可在不同平台实现相同的效果。Android 通过Airbnb的开源项目lottie-android实现,最低支持 API 16;IOS 通过Airbnb的开源项目lottie-ios实现,最低支持 IOS 7;React Native,通过Airbnb的开源项目lottie-react-native实现;这是React logo的动画,以下以Android平台为例如何使用Lottie1.下载Lottie在项目的 build.gradle 文件添加依赖dependencies { compile ‘com.airbnb.android:lottie:2.1.0’}2.添加 Adobe After Effects 导出的动画文件Lottie默认读取Assets中的文件,我们需要把动画文件react.json 保存在app/src/main/assets文件里。(文件比较大,只展示了部分内容,文件链接){ “v”: “4.6.0”, “fr”: 29.9700012207031, “ip”: 0, “op”: 141.000005743048, “w”: 800, “h”: 800, “ddd”: 0, “assets”: [ ], “layers”: [ { “ddd”: 0, “ind”: 0, “ty”: 4, “nm”: “center_circle”, “ks”: {…}, “ao”: 0, “shapes”: […], “ip”: 0, “op”: 900.000036657751, “st”: 0, “bm”: 0, “sr”: 1 }, {…}, {…}, {…} ]}3.使用Lottie在布局文件中直接添加Lottie的LottieAnimationView控件,即可在界面显示React logo动画效果<com.airbnb.lottie.LottieAnimationView android:id="@+id/animation_view" android:layout_width=“wrap_content” android:layout_height=“wrap_content” app:lottie_fileName=“react.json” app:lottie_loop=“true” app:lottie_autoPlay=“true” />4.引入Lottie影响(1)兼容性Lottie 最低支持版本API 16,低版本系统需要做降级动画或者不展示动画(2)安装包影响项使用前使用后结论方法数144807145891增加1084个方法安装包大小41969KB42037KB增大68KB这是用全民K歌release包的测试数据,lottie本身方法数不小,有方法数超标和安装包过大的风险,业务可自行评估注:LottieAnimationView继承于V7的AppCompatImageView,需要引入V7兼容包,根据业务需要,可以源码引入Lottie,让LottieAnimationView继承与ImageView,就不用引入V7兼容包,可减小安装大小。三、使用小技巧1.加载SDCard动画文件StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(JSON_PATH + “react.json”)));String content = null;while ((content = bufferedReader.readLine()) != null){ stringBuilder.append(content);}JSONObject jsonObject = new JSONObject(stringBuilder.toString());animationView.setAnimation(jsonObject);animationView.loop(true);animationView.playAnimation();2.加载SDCard图片animationView.setImageAssetDelegate(new ImageAssetDelegate() { @Override public Bitmap fetchBitmap(LottieImageAsset asset) { try { FileInputStream fileInputStream = new FileInputStream(IMAGE_PATH + asset.getFileName()); return BitmapFactory.decodeStream(fileInputStream); ///把流转化为Bitmap图片 } catch (Exception e) { Log.e(TAG, “”, e); } return null; }});3.加载SDCard字体animationView.setFontAssetDelegate(new FontAssetDelegate(){ public Typeface fetchFont(String fontFamily) { Typeface customFont = Typeface.createFromFile(FONT_PATH + fontFamily); return customFont; }});4.缓存动画/** Lottie内部有两个缓存map(强引用缓存,弱引用缓存),在动画文件加载完成后会根据设置的缓存策略缓存动画,方便下次使用。*/animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Strong); //强缓存animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Weak); //弱缓存四、Lottie实现原理设计师把一张复杂的图片使用多个图层来表示,每个图层展示一部分内容,图层中的内容也可以拆分为多个元素。拆分元素之后,根据动画需求,可以单独对图层或者图层中的元素做平移、旋转、收缩等动画。Lottie的使用的资源是需要先通过bodymovin( bodymovin 插件本身是用于网页上呈现各种AE效果的一个开源库)将 Adobe After Effects (AE)生成的aep动画工程文件转换为通用的json格式描述文件。Lottie则负责解析动画的数据,计算每个动画在某个时间点的状态,准确地绘制到屏幕上。导出的json动画描述文件:{ “v”: “4.6.0”, “fr”: 29.9700012207031, “ip”: 0, “op”: 141.000005743048, “w”: 800, “h”: 800, “ddd”: 0, “assets”: [ ], “layers”: [ {…}, ]}Lottie主要类图:图:lottie_classLottie对外通过控件LottieAnimationView暴露接口,控制动画。LottieAnimationView继承自ImageView,通过当前时间绘制canvas显示到界面上。这里有两个关键类:LottieComposition 负责解析json描述文件,把json内容转成Java数据对象;LottieDrawable负责绘制,把LottieComposition转成的数据对象绘制成drawable显示到View上。顺序如下:1.json文件解析LottieComposition负责解析json文件,建立数据到java对象的映射关系。(1)解析json外部结构LottieComposition封装整个动画的信息,包括动画大小,动画时长,帧率,用到的图片,字体,图层等等。json外部结构{ “v”: “4.6.0”, //bodymovin的版本 “fr”: 29.9700012207031, //帧率 “ip”: 0, //起始关键帧 “op”: 141.000005743048, //结束关键帧 “w”: 800, //动画宽度 “h”: 800, //动画高度 “ddd”: 0, “assets”: […] //资源信息 “layers”: […] //图层信息}//解析json的源码static LottieComposition fromJsonSync(Resources res, JSONObject json) { Rect bounds = null; float scale = res.getDisplayMetrics().density; int width = json.optInt(“w”, -1); int height = json.optInt(“h”, -1); if (width != -1 && height != -1) { int scaledWidth = (int) (width * scale); int scaledHeight = (int) (height * scale); bounds = new Rect(0, 0, scaledWidth, scaledHeight); } long startFrame = json.optLong(“ip”, 0); long endFrame = json.optLong(“op”, 0); float frameRate = (float) json.optDouble(“fr”, 0); String version = json.optString(“v”); String[] versions = version.split("[.]"); int major = Integer.parseInt(versions[0]); int minor = Integer.parseInt(versions[1]); int patch = Integer.parseInt(versions[2]); LottieComposition composition = new LottieComposition( bounds, startFrame, endFrame, frameRate, scale, major, minor, patch); JSONArray assetsJson = json.optJSONArray(“assets”); parseImages(assetsJson, composition); //解析图片 parsePrecomps(assetsJson, composition); parseFonts(json.optJSONObject(“fonts”), composition); //解析字体 parseChars(json.optJSONArray(“chars”), composition); //解析字符 parseLayers(json, composition); //解析图层 return composition; }(2)解析图片资源LottieImageAsset类封装图片信息"assets": [ //资源信息 { //第一张图片 “id”: “image_0”, //图片id “w”: 58, //图片宽度 “h”: 31, //图片高度 “u”: “images/”, //图片路径 “p”: “img_0.png” //图片名称 }, {…} //第n张图片]static LottieImageAsset newInstance(JSONObject imageJson) { return new LottieImageAsset(imageJson.optInt(“w”), imageJson.optInt(“h”), imageJson.optString(“id”), imageJson.optString(“p”));}(3)解析图层Layer封装图层信息,现在lottie只支持PreComp,Solid,Image,Null,Shape,Text这6中图层。“layers”: [ //图层信息 { //第一层动画 “ddd”: 0, “ind”: 0, //layer id 图层 id “ty”: 4, //图层类型 “nm”: “center_circle”, “ks”: {…}, //动画 “ao”: 0, “shapes”: […], “ip”: 0, //inFrame 该图层起始关键帧 “op”: 90, //outFrame 该图层结束关键帧 “st”: 0, //startFrame 开始 “bm”: 0, “sr”: 1 }, {…} //第n层动画]2.如何动起来Lottie时序图:利用属性动画控制进度,每次进度改变通知到每一层,触发LottieAnimationView重绘。(1)利用属性动画计算进度这里用到了属性动画来产生一个0~1的插值,根据不同的插值来设置当前动画进度。代码如下:public LottieDrawable() { animator.setRepeatCount(0); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (systemAnimationsAreDisabled) { animator.cancel(); setProgress(1f); } else { setProgress((float) animation.getAnimatedValue()); } } });}(2)通过CompositionLayer把进度传递到各个图层@Overridepublic void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { super.setProgress(progress); if (timeRemapping != null) { long duration = lottieDrawable.getComposition().getDuration(); long remappedTime = (long) (timeRemapping.getValue() * 1000); progress = remappedTime / (float) duration; } if (layerModel.getTimeStretch() != 0) { progress /= layerModel.getTimeStretch(); } progress -= layerModel.getStartProgress(); for (int i = layers.size() - 1; i >= 0; i–) { layers.get(i).setProgress(progress); }}(3)通知进度改变 void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { if (progress < getStartDelayProgress()) { progress = 0f; } else if (progress > getEndProgress()) { progress = 1f; } if (progress == this.progress) { return; } this.progress = progress; for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onValueChanged(); } }(4)最终回调到LottieAnimationView的invalidateDrawable@Overridepublic void invalidateDrawable(@NonNull Drawable dr) { if (getDrawable() == lottieDrawable) { // We always want to invalidate the root drawable so it redraws the whole drawable. // Eventually it would be great to be able to invalidate just the changed region. super.invalidateDrawable(lottieDrawable); } else { // Otherwise work as regular ImageView super.invalidateDrawable(dr); }}(5)最后触发LottieDrawable重绘@Overridepublic void draw(@NonNull Canvas canvas) { … matrix.reset(); matrix.preScale(scale, scale); compositionLayer.draw(canvas, matrix, alpha); //这里会调用所有layer的绘制方法 if (hasExtraScale) { canvas.restore(); }}五、性能1.官方说明如果没有mask和mattes,那么性能和内存非常好,没有bitmap创建,大部分操作都是简单的cavas绘制。如果存在mattes,将会创建2~3个bitmap。bitmap在动画加载到window时被创建,被window删除时回收。所以不宜在RecyclerView中使用包涵mattes或者mask的动画,否则会引起bitmap抖动。除了内存抖动,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也会降低动画性能。对于简单的动画,在实际使用时性能不太明显。如果在列表中使用动画,推荐使用缓存LottieAnimationView.setAnimation(String, CacheStrategy) 。2.属性动画和Lottie动画对比以下性能对比是以K歌内单个礼物动画效果 属性动画lottie使用硬件加速lottie未使用硬件加速帧率内容CPULottie动画在未开启硬件加速的情况下,帧率、内存,CPU都比属性动画差,开启硬件加速后,性能差不多。3、未开启硬件加速,Lottie动画大小帧率对比0.12倍1倍主要耗时在draw方法,绘制区域越小,耗时越小六、K歌可用的场景1.特性引导视频全民K歌每个大版本的首次启动都会有视频引导动画,每次都会在清晰度和文件大小平衡,最终导出一个大概有3-4M的引导视频,使用lottie可提高动画清晰度和减小安装包大小2.loading动画3.礼物动画这是全民K歌的礼物面板,内部有大量礼物动画,每个礼物动画都不相同,动画过程中有大量的旋转,透明度,大小的变化,需要用属性动画实现,非常麻烦,代码可维护性也比较差。对比使用lottie后,有几大优势:1、100%实现设计效果2、客户端代码量极少,易维护3、每个动画可动态配置动画样式(加载不同的json)4、所有动画都可动态配置,动画配置文件,素材都可从网上加载4.说唱K歌的说唱功能需要歌词按照特定的动画展示出来,这里就涉及歌词放大,缩小,旋转等等特效。实现时,根据当前时间,在canvas上歌词绘制出来,最终再和声音融合在一起生成一个MV视频,这里就导致动画不能特别复杂,并且有一定的规律。如果使用lottie后,可以把效果导出到json动画文件里,客户端加载动画文件,循环设置进度,读取每帧画面,再和声音融合生成MV。优势:1.动画丰富2.代码量少3.可使用设计导出的字体代码animationView.setProgress(progress); //设置当前进度animationView.buildDrawingCache(); //强制缓存绘制数据Bitmap image = animationView.getDrawingCache(); //获取当前绘制数据七、总结1.劣势(1)性能不够好—某些动画特效,内存和性能不够好;相对于属性动画,在展示大动画时,帧率较低2.优势(1)开发效率高—代码实现简单,更换动画方便,易于调试和维护。(2)数据源多样性—可从assets,sdcard,网络加载动画资源,能做到不发版本,动态更新(3)跨平台—设计稿导出一份动画描述文件,android,ios,react native通用Lottie使用简单,易于上手,非常值得尝试。八、参考资料1.GitHub - airbnb/lottie-android: Render After Effects animations natively on Android and iOS2.Lottie的使用及原理浅析 - 彩笔学长 - CSDN博客3.从json文件到炫酷动画-Lottie实现思路和源码分析 - 简书4.Most Popular - LottieFiles此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 9, 2019 · 4 min · jiezi

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

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 8, 2019 · 1 min · jiezi

自己动手写事件总线(EventBus)

本文由云+社区发表事件总线核心逻辑的实现。<!–more–>EventBus的作用Android中存在各种通信场景,如Activity之间的跳转,Activity与Fragment以及其他组件之间的交互,以及在某个耗时操作(如请求网络)之后的callback回调等,互相之之间往往需要持有对方的引用,每个场景的写法也有差异,导致耦合性较高且不便维护。以Activity和Fragment的通信为例,官方做法是实现一个接口,然后持有对方的引用,再强行转成接口类型,导致耦合度偏高。再以Activity的返回为例,一方需要设置setResult,而另一方需要在onActivityResult做对应处理,如果有多个返回路径,代码就会十分臃肿。而SimpleEventBus(本文最终实现的简化版事件总线)的写法如下:public class MainActivity extends AppCompatActivity { TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.tv_demo); mTextView.setText(“MainActivity”); mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); } }); EventBus.getDefault().register(this); } @Subscribe(threadMode = ThreadMode.MAIN) public void onReturn(Message message) { mTextView.setText(message.mContent); } @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); }}来源Activity:public class SecondActivity extends AppCompatActivity { TextView mTextView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.tv_demo); mTextView.setText(“SecondActivity,点击返回”); mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Message message = new Message(); message.mContent = “从SecondActivity返回”; EventBus.getDefault().post(message); finish(); } }); }}效果如下:似乎只是换了一种写法,但在场景愈加复杂后,EventBus能够体现出更好的解耦能力。背景知识主要涉及三方面的知识:观察者模式(or 发布-订阅模式)Android消息机制Java并发编程本文可以认为是greenrobot/EventBus这个开源库的源码阅读指南,笔者在看设计模式相关书籍的时候了解到这个库,觉得有必要实现一下核心功能以加深理解。实现过程EventBus的使用分三个步骤:注册监听、发送事件和取消监听,相应本文也将分这三步来实现。注册监听定义一个注解:@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Subscribe { ThreadMode threadMode() default ThreadMode.POST;}greenrobot/EventBus还支持优先级和粘性事件,这里只支持最基本的能力:区分线程,因为如更新UI的操作必须放在主线程。ThreadMode如下:public enum ThreadMode { MAIN, // 主线程 POST, // 发送消息的线程 ASYNC // 新开一个线程发送}在对象初始化的时候,使用register方法注册,该方法会解析被注册对象的所有方法,并解析声明了注解的方法(即观察者),核心代码如下:public class EventBus { … public void register(Object subscriber) { if (subscriber == null) { return; } synchronized (this) { subscribe(subscriber); } } … private void subscribe(Object subscriber) { if (subscriber == null) { return; } // TODO 巨踏马难看的缩进 Class<?> clazz = subscriber.getClass(); while (clazz != null && !isSystemClass(clazz.getName())) { final Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { Subscribe annotation = method.getAnnotation(Subscribe.class); if (annotation != null) { Class<?>[] paramClassArray = method.getParameterTypes(); if (paramClassArray != null && paramClassArray.length == 1) { Class<?> paramType = convertType(paramClassArray[0]); EventType eventType = new EventType(paramType); SubscriberMethod subscriberMethod = new SubscriberMethod(method, annotation.threadMode(), paramType); realSubscribe(subscriber, subscriberMethod, eventType); } } } clazz = clazz.getSuperclass(); } } … private void realSubscribe(Object subscriber, SubscriberMethod method, EventType eventType) { CopyOnWriteArrayList<Subscription> subscriptions = mSubscriptionsByEventtype.get(subscriber); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); } Subscription subscription = new Subscription(subscriber, method); if (subscriptions.contains(subscription)) { return; } subscriptions.add(subscription); mSubscriptionsByEventtype.put(eventType, subscriptions); } …}执行过这些逻辑后,该对象所有的观察者方法都会被存在一个Map中,其Key是EventType,即观察事件的类型,Value是订阅了该类型事件的所有方法(即观察者)的一个列表,每个方法和对象一起封装成了一个Subscription类:public class Subscription { public final Reference<Object> subscriber; public final SubscriberMethod subscriberMethod; public Subscription(Object subscriber, SubscriberMethod subscriberMethod) { this.subscriber = new WeakReference<>(subscriber);// EventBus3 没用弱引用? this.subscriberMethod = subscriberMethod; } @Override public int hashCode() { return subscriber.hashCode() + subscriberMethod.methodString.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Subscription) { Subscription other = (Subscription) obj; return subscriber == other.subscribe && subscriberMethod.equals(other.subscriberMethod); } else { return false; } }}如此,便是注册监听方法的核心逻辑了。消息发送消息的发送代码很简单:public class EventBus { … private EventDispatcher mEventDispatcher = new EventDispatcher(); private ThreadLocal<Queue<EventType>> mThreadLocalEvents = new ThreadLocal<Queue<EventType>>() { @Override protected Queue<EventType> initialValue() { return new ConcurrentLinkedQueue<>(); } }; … public void post(Object message) { if (message == null) { return; } mThreadLocalEvents.get().offer(new EventType(message.getClass())); mEventDispatcher.dispatchEvents(message); } …}比较复杂一点的是需要根据注解声明的线程模式在对应的线程进行发布:public class EventBus { … private class EventDispatcher { private IEventHandler mMainEventHandler = new MainEventHandler(); private IEventHandler mPostEventHandler = new DefaultEventHandler(); private IEventHandler mAsyncEventHandler = new AsyncEventHandler(); void dispatchEvents(Object message) { Queue<EventType> eventQueue = mThreadLocalEvents.get(); while (eventQueue.size() > 0) { handleEvent(eventQueue.poll(), message); } } private void handleEvent(EventType eventType, Object message) { List<Subscription> subscriptions = mSubscriptionsByEventtype.get(eventType); if (subscriptions == null) { return; } for (Subscription subscription : subscriptions) { IEventHandler eventHandler = getEventHandler(subscription.subscriberMethod.threadMode); eventHandler.handleEvent(subscription, message); } } private IEventHandler getEventHandler(ThreadMode mode) { if (mode == ThreadMode.ASYNC) { return mAsyncEventHandler; } if (mode == ThreadMode.POST) { return mPostEventHandler; } return mMainEventHandler; } }// end of the class …}三种线程模式分别如下,DefaultEventHandler(在发布线程执行观察者放方法):public class DefaultEventHandler implements IEventHandler { @Override public void handleEvent(Subscription subscription, Object message) { if (subscription == null || subscription.subscriber.get() == null) { return; } try { subscription.subscriberMethod.method.invoke(subscription.subscriber.get(), message); } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } }}MainEventHandler(在主线程执行):public class MainEventHandler implements IEventHandler { private Handler mMainHandler = new Handler(Looper.getMainLooper()); DefaultEventHandler hanlder = new DefaultEventHandler(); @Override public void handleEvent(final Subscription subscription, final Object message) { mMainHandler.post(new Runnable() { @Override public void run() { hanlder.handleEvent(subscription, message); } }); }}AsyncEventHandler(新开一个线程执行):public class AsyncEventHandler implements IEventHandler { private DispatcherThread mDispatcherThread; private IEventHandler mEventHandler = new DefaultEventHandler(); public AsyncEventHandler() { mDispatcherThread = new DispatcherThread(AsyncEventHandler.class.getSimpleName()); mDispatcherThread.start(); } @Override public void handleEvent(final Subscription subscription, final Object message) { mDispatcherThread.post(new Runnable() { @Override public void run() { mEventHandler.handleEvent(subscription, message); } }); } private class DispatcherThread extends HandlerThread { // 关联了AsyncExecutor消息队列的Handle Handler mAsyncHandler; DispatcherThread(String name) { super(name); } public void post(Runnable runnable) { if (mAsyncHandler == null) { throw new NullPointerException(“mAsyncHandler == null, please call start() first.”); } mAsyncHandler.post(runnable); } @Override public synchronized void start() { super.start(); mAsyncHandler = new Handler(this.getLooper()); } }}以上便是发布消息的代码。注销监听最后一个对象被销毁还要注销监听,否则容易导致内存泄露,目前SimpleEventBus用的是WeakReference,能够通过GC自动回收,但不知道greenrobot/EventBus为什么没这样实现,待研究。注销监听其实就是遍历Map,拿掉该对象的订阅即可:public class EventBus { … public void unregister(Object subscriber) { if (subscriber == null) { return; } Iterator<CopyOnWriteArrayList<Subscription>> iterator = mSubscriptionsByEventtype.values().iterator(); while (iterator.hasNext()) { CopyOnWriteArrayList<Subscription> subscriptions = iterator.next(); if (subscriptions != null) { List<Subscription> foundSubscriptions = new LinkedList<>(); for (Subscription subscription : subscriptions) { Object cacheObject = subscription.subscriber.get(); if (cacheObject == null || cacheObject.equals(subscriber)) { foundSubscriptions.add(subscription); } } subscriptions.removeAll(foundSubscriptions); } // 如果针对某个Event的订阅者数量为空了,那么需要从map中清除 if (subscriptions == null || subscriptions.size() == 0) { iterator.remove(); } } } …}以上便是事件总线最核心部分的代码实现,完整代码见vimerzhao/SimpleEventBus,后面发现问题更新或者进行升级也只会改动仓库的代码。局限性由于时间关系,目前只研究了EventBus的核心部分,还有几个值得深入研究的点,在此记录一下,也欢迎路过的大牛指点一二。性能问题实际使用时,注解和反射会导致性能问题,但EventBus3已经通过Subscriber Index基本解决了这一问题,实现也非常有意思,是通过注解处理器(Annotation Processor)把耗时的逻辑从运行期提前到了编译期,通过编译期生成的索引来给运行期提速,这也是这个名字的由来。可用性问题如果订阅者很多会不会影响体验,毕竟原始的方法是点对点的消息传递,不会有这种问题,如果部分订阅者尚未初始化怎么办。等等。目前EventBus3提供了优先级和粘性事件的属性来进一步满足开发需求。但是否彻底解决问题了还有待验证。跨进程问题EventBus是进程内的消息管理机制,并且从开源社区的反馈来看这个项目是非常成功的,但稍微有点体量的APP都做了进程拆分,所以是否有必要支持多进程,能否在保证性能的情况下提供同等的代码解耦能力,也值得继续挖掘。目前有lsxiao/Apollo和Xiaofei-it/HermesEventBus等可供参考。参考greenrobot/EventBushehonghui/AndroidEventBus《Android开发艺术探索》第十章此文已由作者授权腾讯云+社区发布 ...

December 29, 2018 · 4 min · jiezi

iOS 覆盖率检测原理与增量代码测试覆盖率工具实现

背景对苹果开发者而言,由于平台审核周期较长,客户端代码导致的线上问题影响时间往往比较久。如果在开发、测试阶段能够提前暴露问题,就有助于避免线上事故的发生。代码覆盖率检测正是帮助开发、测试同学提前发现问题,保证代码质量的好帮手。对于开发者而言,代码覆盖率可以反馈两方面信息:自测的充分程度。代码设计的冗余程度。尽管代码覆盖率对代码质量有着上述好处,但在 iOS 开发中却使用的不多。我们调研了市场上常用的 iOS 覆盖率检测工具,这些工具主要存在以下四个问题:第三方工具有时生成的检测报告文件会出错甚至会失败,开发者对覆盖率生成原理不了解,遇到这类问题容易弃用工具。第三方工具每次展示全量的覆盖率报告,会分散开发者的很多精力在未修改部分。而在绝大多数情况下,开发者的关注重点在本次新增和修改的部分。Xcode 自带的覆盖率检测只适用于单元测试场景,由于需求变更频繁,业务团队开发单元测试的成本很高。已有工具很难和现有开发流程结合起来,需要额外进行测试,运行覆盖率脚本才能获取报告文件。为了解决上述问题,我们深入调研了覆盖率报告的生成逻辑,并结合团队的开发流程,开发了一套嵌入在代码提交流程中、基于单次代码提交(git commit)生成报告、对开发者透明的增量代码测试覆盖率工具。开发者只需要正常开发,通过模拟器测试开发代码,commit 本次代码(commit 和测试顺序可交换),推送(git push)到远端,就可以在本地看到这次提交代码的详细覆盖率报告了。本文分为两部分,先从介绍通用覆盖率检测的原理出发,让读者对覆盖率的收集、解析有直观的认识。之后介绍我们增量代码测试覆盖率工具的实现。覆盖率检测原理生成覆盖率报告,首先需要在 Xcode 中配置编译选项,编译后会为每个可执行文件生成对应的 .gcno 文件;之后在代码中调用覆盖率分发函数,会生成对应的 .gcda 文件。其中,.gcno 包含了代码计数器和源码的映射关系, .gcda 记录了每段代码具体的执行次数。覆盖率解析工具需要结合这两个文件给出最后的检测报表。接下来先看看 .gcno 的生成逻辑。.gcno利用 Clang 分别生成源文件的 AST 和 IR 文件,对比发现,AST 中不存在计数指令,而 IR 中存在用来记录执行次数的代码。搜索 LLVM 源码可以找到覆盖率映射关系生成源码。覆盖率映射关系生成源码是 LLVM 的一个 Pass,(下文简称 GCOVPass)用来向 IR 中插入计数代码并生成 .gcno 文件(关联计数指令和源文件)。下面分别介绍IR插桩逻辑和 .gcno 文件结构。IR 插桩逻辑代码行是否执行到,需要在运行中统计,这就需要对代码本身做一些修改,LLVM 通过修改 IR 插入了计数代码,因此我们不需要改动任何源文件,仅需在编译阶段增加编译器选项,就能实现覆盖率检测了。从编译器角度看,基本块(Basic Block,下文简称 BB)是代码执行的基本单元,LLVM 基于 BB 进行覆盖率计数指令的插入,BB 的特点是:只有一个入口。只有一个出口。只要基本块中第一条指令被执行,那么基本块内所有指令都会顺序执行一次。覆盖率计数指令的插入会进行两次循环,外层循环遍历编译单元中的函数,内层循环遍历函数的基本块。函数遍历仅用来向 .gcno 中写入函数位置信息,这里不再赘述。一个函数中基本块的插桩方法如下:统计所有 BB 的后继数 n,创建和后继数大小相同的数组 ctr[n]。以后继数编号为序号将执行次数依次记录在 ctr[i] 位置,对于多后继情况根据条件判断插入。举个例子,下面是一段猜数字的游戏代码,当玩家猜中了我们预设的数字10的时候会输出Bingo,否则输出You guessed wrong!。这段代码的控制流程图如图1所示。- (void)guessNumberGame:(NSInteger)guessNumber{ NSLog(@“Welcome to the game”); if (guessNumber == 10) { NSLog(@“Bingo!”); } else { NSLog(@“You guess is wrong!”); }}例1 猜数字游戏 这段代码如果开启了覆盖率检测,会生成一个长度为 6 的 64 位数组,对照插桩位置,方括号中标记了桩点序号,图 1 中代码前数字为所在行数。图 1 桩点位置.gcno计数符号和文件位置关联.gcno 是用来保存计数插桩位置和源文件之间关系的文件。GCOVPass 在通过两层循环插入计数指令的同时,会将文件及 BB 的信息写入 .gcno 文件。写入步骤如下:创建 .gcno 文件,写入 Magic number(oncg+version)。随着函数遍历写入文件地址、函数名和函数在源文件中的起止行数(标记文件名,函数在源文件对应行数)。随着 BB 遍历,写入 BB 编号、BB 起止范围、BB 的后继节点编号(标记基本块跳转关系)。写入函数中BB对应行号信息(标注基本块与源码行数关系)。从上面的写入步骤可以看出,.gcno 文件结构由四部分组成:文件结构函数结构BB 结构BB 行结构通过这四部分结构可以完全还原插桩代码和源码的关联,我们以 BB 结构 / BB 行结构为例,给出结构图 2 (a) BB 结构,(b) BB 行信息结构,在本章末尾覆盖率解析部分,我们利用这个结构图还原代码执行次数(每行等高格代表 64bit):图2 BB 结构和 BB 行信息结构.gcda入口函数关于 .gcda 的生成逻辑,可参考覆盖率数据分发源码。这个文件中包含了 __gcov_flush() 函数,这个函数正是分发逻辑的入口。接下来看看 __gcov_flush() 如何生成 .gcda 文件。通过阅读代码和调试,我们发现在二进制代码加载时,调用了llvm_gcov_init(writeout_fn wfn, flush_fn ffn)函数,传入了_llvm_gcov_writeout(写 gcov 文件),_llvm_gcov_flush(gcov 节点分发)两个函数,并且根据调用顺序,分别建立了以文件为节点的链表结构。(flush_fn_node * ,writeout_fn_node * )__gcov_flush() 代码如下所示,当我们手动调用__gcov_flush() 进行覆盖率分发时,会遍历flush_fn_node *这个链表(即遍历所有文件节点),并调用分发函数_llvm_gcov_flush(curr->fn 正是__llvm_gcov_flush函数类型)。void __gcov_flush() { struct flush_fn_node *curr = flush_fn_head; while (curr) { curr->fn(); curr = curr->next; }}具体的分发逻辑观察__llvm_gcov_flush 的 IR 代码,可以看到:图3 __llvm_gcov_flush 代码示例__llvm_gcov_flush 先调用了__llvm_gcov_writeout,来向 .gcda 写入覆盖率信息。最后将计数数组清零__llvm_gcov_ctr.xx。而__llvm_gcov_writeout逻辑为:生成对应源文件的 .gcda 文件,写入 Magic number。循环执行llvm_gcda_emit_function: 向 .gcda 文件写入函数信息。llvm_gcda_emit_arcs: 向 .gcda 文件写入BB执行信息,如果已经存在 .gcda 文件,会和之前的执行次数进行合并。调用llvm_gcda_summary_info,写入校验信息。调用llvm_gcda_end_file,写结束符。感兴趣的同学可以自己生成 IR 文件查看更多细节,这里不再赘述。.gcda 的文件/函数结构和 .gcno 基本一致,这里不再赘述,统计插桩信息结构如图 4 所示。定制化的输出也可以通过修改上述函数完成。我们的增量代码测试覆盖率工具解决代码 BB 结构变动后合并到已有 .gcda 文件不兼容的问题,也是修改上述函数实现的。图4 计数桩输出结构覆盖率解析在了解了如上所述 .gcno ,.gcda 生成逻辑与文件结构之后,我们以例 1 中的代码为例,来阐述解析算法的实现。例 1 中基本块 B0,B1 对应的 .gcno 文件结构如下图所示,从图中可以看出,BB 的主结构完全记录了基本块之间的跳转关系。图5 B0,B1 对应跳转信息B0,B1 的行信息在 .gcno 中表示如下图所示,B0 块因为是入口块,只有一行,对应行号可以从 B1 结构中获取,而 B1 有两行代码,会依次把行号写入 .gcno 文件。图6 B0,B1 对应行信息在输入数字 100 的情况下,生成的 .gcda 文件如下:图7 输入 100 得到的 .gcda 文件通过控制流程图中节点出边的执行次数可以计算出 BB 的执行次数,核心算法为计算这个 BB 的所有出边的执行次数,不存在出边的情况下计算所有入边的执行次数(具体实现可以参考 gcov 工具源码),对于 B0 来说,即看 index=0 的执行次数。而 B1 的执行次数即 index=1,2 的执行次数的和,对照上图中 .gcda 文件可以推断出,B0 的执行次数为 ctr[0]=1,B1 的执行次数是 ctr[1]+ctr[2]=1, B2 的执行次数是 ctr[3]=0,B4 的执行次数为 ctr[4]=1,B5 的执行次数为 ctr[5]=1。经过上述解析,最终生成的 HTML 如下图所示(利用 lcov):图8 覆盖率检测报告以上是 Clang 生成覆盖率信息和解析的过程,下面介绍美团到店餐饮 iOS 团队基于以上原理做的增量代码测试覆盖率工具。增量代码覆盖率检测原理方案权衡由于 gcov 工具(和前面的 .gcov 文件区分,gcov 是覆盖率报告生成工具)生成的覆盖率检测报告可读性不佳,如图 9 所示。我们做的增量代码测试覆盖率工具是基于 lcov 的扩展,报告展示如上节末尾图 8 所示。图9 gcov 输出,行前数字代表执行次数,#### 代表没执行比 gcov 直接生成报告多了一步,lcov 的处理流程是将 .gcno 和 .gcda 文件解析成一个以 .info 结尾的中间文件(这个文件已经包含全部覆盖率信息了),之后通过覆盖率报告生成工具生成可读性比较好的 HTML 报告。结合前两章内容和覆盖率报告生成步骤,覆盖率生成流程如下图所示。考虑到增量代码覆盖率检测中代码增量部分需要通过 Git 获取,比较自然的想法是用 git diff 的信息去过滤覆盖率的内容。根据过滤点的不同,存在以下两套方案:通过 GCOVPass 过滤,只对修改的代码进行插桩,每次修改后需重新插桩。通过 .info 过滤,一次性为所有代码插桩,获取全部覆盖率信息,过滤覆盖率信息。图10 覆盖率生成流程分析这两个方案,第一个方案需要自定义 LLVM 的 Pass,进而会引入以下两个问题:只能使用开源 Clang 进行编译,不利于接入正常的开发流程。每次重新插桩会丢失之前的覆盖率信息,多次运行只能得到最后一次的结果。而第二个方案相对更加轻量,只需要过滤中间格式文件,不仅可以解决我们在文章开头提到的问题,也可以避免上述问题:可以很方便地加入到平常代码的开发流程中,甚至对开发者透明。未修改文件的覆盖率可以叠加(有修改的那些控制流程图结构可能变化,无法叠加)。因此我们实际开发选定的过滤点是在 .info 。在选定了方案 2 之后,我们对中间文件 .info 进行了一系列调研,确定了文件基本格式(函数/代码行覆盖率对应的文件的表示),这里不再赘述,具体可以参考 .info 生成文档。增量代码测试覆盖率工具的实现前一节是实现增量代码覆盖率检测的基本方案选择,为了更好地接入现有开发流程,我们做了以下几方面的优化。降低使用成本在接入方面,接入增量代码测试覆盖率工具只需一次接入配置,同步到代码仓库后,团队中成员无需配置即可使用,降低了接入成本。在使用方面,考虑到插桩在编译时进行,对全部代码进行插桩会很大程度降低编译速度,我们通过解析 Podfile(iOS 开发中较为常用的包管理工具 CocoaPods 的依赖描述文件),只对 Podfile 中使用本地代码的仓库进行插桩(可配置指定仓库),降低了团队的开发成本。对开发者透明接入增量代码测试覆盖率工具后,开发者无需特殊操作,也不需要对工程做任何其他修改,正常的 git commit 代码,git push 到远端就会自动生成并上传这次 commit 的覆盖率信息了。为了做到这一点,我们在接入 Pod 的过程中,自动部署了 Git 的 pre-push 脚本。熟悉 Git 的同学知道,Git 的 hooks 是开发者的本地脚本,不会被纳入版本控制,如何通过一次配置就让这个仓库的所有使用成员都能开启,是做好这件事的一个难点。我们考虑到 Pod 本身会被纳入版本控制,因此利用了 CocoaPods 的一个属性 script_phase,增加了 Pod 编译后脚本,来帮助我们把 pre-push 插入到本地仓库。利用 script_phase 插入还带来了另外一个好处,我们可以直接获取到工程的缓存文件,也避免了 .gcno / .gcda 文件获取的不确定性。整个流程如下:图11 pre-push 分发流程覆盖率累计在实现了覆盖率的过滤后,我们在实际开发中遇到了另外一个问题:修改分支/循环结构后生成的 .gcda 文件无法和之前的合并。 在这种情况下,__gcov_flush会直接返回,不再写入 .gcda 文件了导致覆盖率检测失败,这也是市面上已有工具的通用问题。而这个问题在开发过程中很常见,比如我们给例 1 中的游戏增加一些提示,当输入比预设数字大时,我们就提示出来,反之亦然。- (void)guessNumberGame:(NSInteger)guessNumber{ NSInteger targetNumber = 10; NSLog(@“Welcome to the game”); if (guessNumber == targetNumber) { NSLog(@“Bingo!”); } else if (guessNumber > targetNumber) { NSLog(@“Input number is larger than the given target!”); } else { NSLog(@“Input number is smaller than the given target!”); }}这个问题困扰了我们很久,也推动了对覆盖率检测原理的调研。结合前面覆盖率检测的原理可以知道,不能合并的原因是生成的控制流程图比原来多了两条边( .gcno 和旧的 .gcda 也不能匹配了),反映在 .gcda 上就是数组多了两个数据。考虑到代码变动后,原有的覆盖率信息已经没有意义了,当发生边数不一致的时候,我们会删除掉旧的 .gcda 文件,只保留最新 .gcda 文件(有变动情况下 .gcno 会重新生成)。如下图所示:图12 覆盖率冲突解决算法整体流程图结合上述流程,我们的增量代码测试覆盖率工具的整体流程如图 13 所示。开发者只需进行接入配置,再次运行时,工程中那些作为本地仓库进行开发的代码库会被自动插桩,并在 .git 目录插入 hooks 信息;当开发者使用模拟器进行需求自测时,插桩统计结果会被自动分发出去;在代码被推到远端前,会根据插桩统计结果,生成仅包含本次代码修改的详细增量代码测试覆盖率报告,以及向远端推送覆盖率信息;同时如果测试覆盖率小于 80% 会强制拒绝提交(可配置关闭,百分比可自定义),保证只有经过充分自测的代码才能提交到远端。图13 增量代码测试覆盖率生成流程图总结以上是我们在代码开发质量方面做的一些积累和探索。通过对覆盖率生成、解析逻辑的探究,我们揭开了覆盖率检测的神秘面纱。开发阶段的增量代码覆盖率检测,可以帮助开发者聚焦变动代码的逻辑缺陷,从而更好地避免线上问题。作者介绍丁京,iOS 高级开发工程师。2015 年 2 月校招加入美团到店餐饮事业群,目前负责大众点评 App 美食频道的开发维护。王颖,iOS 开发工程师。2017 年 3 月校招加入美团到店餐饮事业群,目前参与大众点评 App 美食频道的开发维护。招聘信息到店餐饮技术部交易与信息技术中心,负责点评美食用户端业务,服务于数以亿计用户,通过更好的榜单、真实的评价和完善的信息为用户提供更好的决策支持,致力于提升用户体验;同时承载所有餐饮商户端线上流量,为餐饮商户提供多种营销工具,提升餐饮商户营销效率,最终达到让用户“Eat Better、Live Better”的美好愿景!我们的团队包含且不限于 Android、iOS、FE、Java、PHP 等技术方向,已完备覆盖前后端技术栈。只要你来,就能点亮全栈开发技能树。诚挚欢迎投递简历至 wangkang@meituan.com。参考资料覆盖率数据分发源码覆盖率映射关系生成源码基本块介绍gcov 工具源码覆盖率报告生成工具 .info 生成文档 ...

December 28, 2018 · 3 min · jiezi

如何做好SQLite 使用质量检测,让事故消灭在摇篮里

本文由云+社区发表SQLite 在移动端开发中广泛使用,其使用质量直接影响到产品的体验。常见的 SQLite 质量监控一般都是依赖上线后反馈的机制,比如耗时监控或者用户反馈。这种方式问题是:事后发现,负面影响已经发生。关注的只是没这么差。eg. 监控阈值为 500ms ,那么一条可优化为 20ms 而平均耗时只有 490ms 的 sql 就被忽略了。能否在上线前就进行SQLite使用质量的监控?于是我们尝试开发了一个工具: SQLiteLint 。虽然名带 “lint ” ,但并不是代码的静态检查,而是在 APP 运行时对 sql 语句、执行序列、表信息等进行分析检测。而和 “lint” 有点类似的是:在开发阶段就介入,并运用一些最佳实践的规则来检测,从而发现潜在的、可疑的 SQLite 使用问题。本文会介绍 SQLiteLint 的思路,也算是 SQLite 使用经验的分享,希望对大家有所帮助。简述SQLiteLint 在 APP 运行时进行检测,而且大部分检测算法与数据量无关即不依赖线上的数据状态。只要你触发了某条 sql 语句的执行,SQLiteLint 就会帮助你 review 这条语句是否写得有问题。而这在开发、测试或者灰度阶段就可以进行。检测流程十分简单:1. 收集 APP 运行时的 sql 执行信息 包括执行语句、创建的表信息等。其中表相关信息可以通过 pragma 命令得到。对于执行语句,有两种情况: a)DB 框架提供了回调接口。比如微信使用的是 WCDB ,很容易就可以通过MMDataBase.setSQLiteTrace 注册回调拿到这些信息。 b) 若使用 Android 默认的 DB 框架,SQLiteLint 提供了一种无侵入的获取到执行的sql语句及耗时等信息的方式。通过hook的技巧,向 SQLite3 C 层的 api sqlite3_profile 方法注册回调,也能拿到分析所需的信息,从而无需开发者额外的打点统计代码。2. 预处理 包括生成对应的 sql 语法树,生成不带实参的 sql ,判断是否 select* 语句等,为后面的分析做准备。预处理和后面的算法调度都在一个单独的处理线程。3. 调度具体检测算法执行 checker 就是各种检测算法,也支持扩展。并且检测算法都是以 C++ 实现,方便支持多平台。而调度的时机包括:最近未分析 sql 语句调度,抽样调度,初始化调度,每条 sql 语句调度。4. 发布问题 上报问题或者弹框提示。可以看到重点在第 3 步,下面具体讨论下 SQLiteLint 目前所关注的质量问题检测。检测问题简介一、检测索引使用问题索引的使用问题是数据库最常见的问题,也是最直接影响性能的问题。SQLiteLint 的分析主要基于 SQLite3 的 “explain query plan” ,即 sql 的查询计划。先简单说下查询计划的最常见的几个关键字:SCAN TABLE: 全表扫描,遍历数据表查找结果集,复杂度 O(n) SEARCH TABLE: 利用索引查找,一般除了 without rowid 表或覆盖索引等,会对索引树先一次 Binary Search 找到 rowid ,然后根据得到 rowid 去数据表做一次 Binary Search 得到目标结果集,复杂度为 O(logn) USE TEMP B-TREE: 对结果集临时建树排序,额外需要空间和时间。比如有 Order By 关键字,就有可能出现这样查询计划通过分析查询计划,SQLiteLint 目前主要检查以下几个索引问题:1. 未建索引导致的全表扫描(对应查询计划的 SCAN TABLE… )虽然建立索引是最基本优化技巧,但实际开发中,很多同学因为意识不够或者需求太紧急,而疏漏了建立合适的索引,SQLiteLint 帮助提醒这种疏漏。问题虽小,解决也简单,但最普遍存在。 这里也顺带讨论下一般不适合建立索引的情况:写多读少以及表行数很小。但对于客户端而言,写多读少的表应该不常见。而表行数很小的情况,建索引是有可能导致查询更慢的(因为索引的载入需要的时间可能大过全表扫描了),但是这个差别是微乎其微的。所以这里认为一般情况下,客户端的查询还是尽量使用索引优化,如果确定预估表数量很小或者写多读少,也可以将这个表加到不检测的白名单。解决这类问题,当然是建立对应的索引。2. 索引未生效导致的全表扫描(对应查询计划的 SCAN TABLE… )有些情况即便建立了索引,但依然可能不生效,而这种情况有时候是可以通过优化 sql 语句去用上索引的。举个例子:以上看到,即便已建立了索引,但实际没有使用索引来查询。 如对于这个 case ,可以把 like 变成不等式的比较:这里看到已经是使用索引来 SEARCH TABLE ,避免了全表扫描。但值得注意的是并不是所有 like 的情况都可以这样优化,如 like ‘%lo’ 或 like ‘%lo%’ ,不等式就做不到了。再看个位操作导致索引不生效的例子:位操作是最常见的导致索引不生效的语句之一。但有些时候也是有些技巧的利用上索引的,假如这个 case 里 flag 的业务取值只有 0x1,0x2,0x4,0x8 ,那么这条语句就可以通过穷举值的方式等效:以上看到,把位操作转成 in 穷举就能利用索引了。解决这类索引未生效导致的全表扫描 的问题,需要结合实际业务好好优化sql语句,甚至使用一些比较trick的技巧。也有可能没办法优化,这时需要添加到白名单。3. 不必要的临时建树排序(对应查询计划的 USE TEMP B-TREE… )。比如sql语句中 order by 、distinct 、group by 等就有可能引起对结果集临时额外建树排序,当然很多情况都是可以通过建立恰当的索引去优化的。举个例子:以上看到,即便id和mark都分别建立了索引,即便只需要一行结果,依然会引起重新建树排序( USE TEMP B-TREE FOR ORDER BY )。当然这个case非常简单,不过如果对 SQLite 的索引不熟悉或者开发时松懈了,确实很容易发生这样的问题。同样这个问题也很容易优化:这样就避免了重新建树排序,这对于数据量大的表查询,优化效果是立竿见影的好。解决这类问题,一般就是建立合适的索引。4. 不足够的索引组合这个主要指已经建立了索引,但索引组合的列并没有覆盖足够 where 子句的条件式中的列。SQLiteLint 检测出这种问题,建议先关注该 sql 语句是否有性能问题,再决定是否建立一个更长的索引。举个例子:以上看到,确实是利用了索引 genderIndex 来查询,但看到where子句里还有一个 mark=60 的条件,所以还有一次遍历判断操作才能得到最终需要的结果集。尤其对于这个 case,gender 也就是性别,那么最多 3 种情况,这个时候单独的 gender 索引的优化效果的已经不明显了。而同样,优化也是很容易的:解决这类问题,一般就是建立一个更大的组合索引。5. 怎么降低误报现在看到 SQLiteLint 主要根据查询计划的某些关键字去发现这些问题,但SQLite支持的查询语法是非常复杂的,而对应的查询计划也是无穷变化的。所以对查询计划自动且正确的分析,不是一件容易的事。SQLiteLint 很大的功夫也在这件事情上所以对查询计划自动且正确的分析,不是一件容易的事。SQLiteLint 很大的功夫也在这件事情上。SQLiteLint 这里主要对输出的查询计划重新构建了一棵有一定的特点的分析树,并结合sql语句的语法树,依据一定的算法及规则进行分析检测。建分析树的过程会使用到每条查询计划前面如 “0|1|0” 的数字,这里不具体展开了。 举个例子:是不是所有带有 “SCAN TABLE” 前缀的查询计划,都认为是需要优化的呢?明显不是。具体看个 case :这是一个联表查询,在 SQLite 的实现里一般就是嵌套循环。在这个语句中里, t3.id 列建了索引,并且在第二层循环中用上了,但第一层循环的 SCAN TABLE是无法优化的。比如尝试给t4的id列也建立索引:可以看出,依然无法避免 SCAN TABLE 。对于这种 SCAN TABLE 无法优化的情况,SQLiteLint 不应该误报。前面提到,会对查询计划组织成树的结构。比如对于这个 case ,最后构建的查询计划分析树为:分析树,有个主要的特点:叶子节点有兄弟节点的是联表查询,其循环顺序对应从左往右,而无兄弟节点是单表查询。而最后的分析会落地到叶子节点的分析。遍历叶子节点时,有一条规则(不完整描述)是:叶子节点有兄弟节点的,且是最左节点即第一层循环,且 where 子句中不含有相关常量条件表达式时,SCAN TABLE 不认为是质量问题。这里有两个条件必须同时满足,SCAN TABLE 才不报问题:第一层循环 & 无相关常量表达式。第一层循环前面已经描述,这里再解释下后面一个条件。由上看到,当select子句中出现常量条件表达式 “t4.id=666” , 若 t3.id,t4.id 都建了索引,是可以优化成没有 SCAN TABLE 。而把 t4.id 的索引删除后,又出现了 SCAN TABLE 。而这种 SCAN TABLE 的情况,不满足规则里的的第二个条件,SQLiteLint 就会报出可以使用索引优化了。这里介绍了一个较简单语句的查询计划的分析,当然还有更复杂的语句,还有子查询、组合等等,这里不展开讨论了。巨大的复杂性,无疑对准确率有很大的挑战,需要对分析规则不断地迭代完善。当前 SQLiteLint 的分析算法依然不足够严谨,还有很大的优化空间。 这里还有另一个思路去应对准确性的问题:对所有上报的问题,结合耗时、是否主线程、问题等级等信息,进行优先级排序。这个“曲线救国”来降低误报的策略也适用本文介绍的所有检测问题。二、检测冗余索引问题SQLiteLint 会在应用启动后对所有的表检测一次是否存在冗余索引,并建议保留最大那个索引组合。先定义什么是冗余索引:如对于某个表,如果索引组合 index1,index2 是另一个索引组合 index3 的前缀,那么一般情况下 index3 可以替代掉 index1 和 index2 的作用,所以 index1,index2 就冗余了。而多余的索引就会有多余的插入消耗和空间消耗,一般就建议只保留索引 index3 。 看个例子:以上看到,如果已经有一个 length 和 type 的组合索引,就已经满足了单 length 列条件式的查询,没必要再为 length 再建一个索引。三、检测 select * 问题SQLiteLint这里通过扫描 sql 语法树,若发现 select 子句,就会报问题,建议尽量避免使用 select ,而是按需 select 对应的列。select * 是SQLite最常用的语句之一,也非常方便,为什么还认为是问题的呢?这里有必要辩驳一下:对于 select ,SQLite 底层依然存在一步把 展开成表的全部列。select * 也减少了可以使用覆盖索引的机会。覆盖索引指索引包含的列已经覆盖了 select 所需要的列,而使用上覆盖索引就可以减少一次数据表的查询。对于 Android 平台而言,select * 就会投射所有的列,那么每行结果占据的内存就会相对更大,那么 CursorWindow(缓冲区)的容纳条数就变少,那么 SQLiteQuery.fillWindow 的次数就可能变多,这也有一定的性能影响。基于以上原因,出于 SQLiteLint 目标最佳实践的原则,这里依然报问题。四、检测 Autoincrement 问题SQLiteLint 在应用启动后会检测一次所有表的创建语句,发现 AUTOINCREMENT 关键字,就会报问题,建议避免使用 Autoincrement 。这里看下为什么要检测这个问题,下面引用 SQLite 的官方文档:The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed.可以看出 Auto Increment 确实不是个好东西。 ps. 我这里补充说明一下 strictly needed 是什么是意思,也就是为什么它不必要。通常 AUTOINCREMENT 用于修饰 INTEGER PRIMARY KEY 列,后简称IPK 列。而 IPK 列等同于 rowid 别名,本身也具有自增属性,但会复用删除的 rowid 号。比如当前有 4 行,最大的rowid是 4,这时把第 4 行删掉,再插入一行,新插入行的 rowid 取值是比当前最大的 rowid 加 1,也就 3+1=4 ,所以复用了 rowid 号 4 。而如果加以 AUTOINCREMENT 修饰就是阻止了复用,在这个情况,rowid 号是 5 。也就是说,AUTOINCREMENT 可以保证了历史自增的唯一性,但对于客户端应用有多少这样的场景呢?五、检测建议使用 prepared statementSQLiteLint 会以抽样的时机去检测这个问题,比如每 50 条执行语句,分析一次执行序列,如果发现连续执行次数超过一定阈值的相同的(当然实参可以不同)而未使用 prepared statement 的 sql 语句,就报问题,建议使用 prepared statement 优化。 如阈值是 3 ,那么连续执行下面的语句,就会报问题:使用 prepared statement 优化的好处有两个:对于相同(实参不同)的 sql 语句多次执行,会有性能提升如果参数是不可信或不可控输入,还防止了注入问题六、检测建议使用 without rowid 特性SQLiteLint 会在应用启动后检测一次所有表的创建语句,发现未使用 without rowid 技巧且根据表信息判断适合使用 without rowid 优化的表,就报问题,建议使用 without rowid 优化。 这是 SQLiteLint 的另一个思路,就是发现是否可以应用上一些 SQLite 的高级特性。without rowid 在某些情况下可以同时带来空间以及时间上将近一半的优化。简单说下原理,如:对于这个含有 rowid 的表( rowid 是自动生成的),这时这里涉及到两次查询,一次在 name 的索引树上找到对应的 rowid ,一次是用这个 rowid 在数据树上查询到 mark 列。 而使用 without rowid 来建表:数据树构建是以 name 为 key ,mark 为 data 的,并且是以普通 B-tree 的方式存储。这样对于刚刚同样的查询,就需要只有一次数据树的查询就得到了 mark 列,所以算法复杂度上已经省了一个 O(logn)。另外又少维护了一个 name 的索引树,插入消耗和空间上也有了节省。当然 withou rowid 不是处处适用的,不然肯定是默认属性了。SQLiteLint 判断如果同时满足以下两个条件,就建议使用 without rowid :表含有 non-integer or composite (multi-column) PRIMARY KEY表每行数据大小不大,一个比较好的标准是行数据大小小于二十分之一的page size 。ps.默认 page size SQLite 版本3.12.0以后(对应 Android O 以上)是 4096 bytes ,以前是 1024 。而由于行数据大小业务相关,为了降低误报,SQLiteLint 使用更严格的判定标准:表不含有 BLOB 列且不含有非 PRIMARY KEY TEXT 列。简单说下原因: 对于1,假如没有 PRIMARY KEY ,无法使用 without rowid 特性;假如有 INTEGER PRIMARY KEY ,前面也说过,这时也已经等同于 rowid 。 对于 2,小于 20 分之一 pagesize 是官方给出的建议。 这里说下我理解的原因。page 是 SQLite 一般的读写单位(实际上磁盘的读写 block 更关键,而磁盘的消耗更多在定位上,更多的page就有可能需要更多的定位)。without rowid 的表是以普通 B-Tree 存储的,而这时数据也存储在所有树结点上,那么假如数据比较大,一个 page 存储的结点变少,那么查找的过程就需要读更多的 page ,从而查找的消耗更大。当然这是相对 rowid 表 B*-Tree 的存储来说的,因为这时数据都在叶子结点,搜索路径上的结点只有 KEY ,那么一个page能存的结点就多了很多,查找磁盘消耗变小。这里注意的是,不要以纯内存的算法复杂度去考量这个问题。以上是推论不一定正确,欢迎指教。引申一下,这也就是为什么 SQLite 的索引树以 B-Tree 组织,而 rowid 表树以 B-Tree 组织,因为索引树每个结点的存主要是索引列和 rowid ,往往没这么大,相对 B-Tree 优势就在于不用一直查找到叶子结点就能结束查找。与 without rowid 同样的限制,不建议用大 String 作为索引列,这当然也可以加入到 SQLiteLint 的检测。小结这里介绍了一个在开发、测试或者灰度阶段进行 SQLite 使用质量检测的工具,这个思路的好处是:上线前发现问题关注最佳实践本文的较大篇幅其实是对 SQLite 最佳实践的讨论,因为 SQLiteLint 的思路就是对最佳实践的自动化检测。当然检查可以覆盖更广的范围,准确性也是挑战,这里还有很大的空间。 此文已由作者授权腾讯云+社区发布搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包! ...

December 18, 2018 · 3 min · jiezi

《Flutter实战》中文原创书籍开源

《Flutter实战》 为Flutter中文网开源电子书项目,本书系统介绍了Flutter各个方面,是第一本中文原创Flutter技术书籍:在线阅读地址:https://book.flutterchina.club《Flutter实战》部分目录缘起起步移动开发技术简介Flutter简介搭建Flutter开发环境Dart语言简介第一个Flutter应用计数器示例路由管理包管理资源管理调试Flutter APP基础WidgetsWidget简介文本、字体样式按钮图片和Icon单选框和复选框输入框和表单布局类Widgets布局类Widgets简介线性布局Row、Column弹性布局Flex流式布局Wrap、Flow层叠布局Stack、Positioned容器类WidgetsPadding布局限制类容器ConstrainedBox、SizeBox装饰容器DecoratedBox变换TransformContainer容器可滚动Widgets可滚动Widgets简介SingleChildScrollViewListViewGridViewCustomScrollView滚动监听及控制ScrollController功能型Widgets导航返回拦截-WillPopScope数据共享-InheritedWidget主题-Theme事件处理与通知原始指针事件处理手势识别全局事件总线通知Notification动画Flutter动画简介动画结构自定义路由过渡动画Hero动画交错动画自定义Widget自定义Widget方法简介通过组合现有Widget实现实例:TurnBoxCustomPaint与Canvas实例:圆形渐变进度条(自绘)文件操作与网络请求文件操作Http请求-HttpClientHttp请求-Dio packageWebSocket使用Socket APIJson转Model包与插件开发package插件开发:平台通道简介插件开发:实现Android端API插件开发:实现IOS端API系统能力调用国际化让App支持多语言实现Localizations使用Intl包更多内容,请移步《Flutter实战》

December 18, 2018 · 1 min · jiezi

如果你的公司还没有企业级班车应用,把这篇文章转给你老板

本文由云+社区发表越来越多的大型企业都开始为员工提供班车,解决员工上下班出行的问题,甚至有些高新技术园区也组织了园区车辆共享的一些新的出行方案。期间,企业从早期员工提供优质的班车服务,到提升班车的管理水平、优化车辆使用成本逐渐成为了主线。那么如何做到这些呢?企业级班车应用到底怎么做呢?首先,我们需要数据,而数据的采集离不开设备,比如刷卡设备,GPS设备总结来说,企业级班车应用的目标也还是在解决痛点员工痛点:出行便利问题、晚点率高等车难、站点设置不合理通勤时间长、线路超载管理痛点:业务规模大黑盒运营、不断增加的运营成本、难以管理的行车安全示意图从设备谈起可以这么说,在设备方面执行最好的还是各大城市公交系统的刷卡机,现在的刷卡机也带了GPS的功能。对于员工,我们通过GPS设备实时展示车辆所在位置,能够在等车这件事上给出预期,对于管理,我们在收集了GPS、刷卡数据后,对人、站、车三者的关系做一系列大数据分析,建立运营模型整体架构图我们可以通过在腾讯云上搭建GPS服务器来获取GPS信息image.png还有一些安全设备,比如车内的视频、车辆行驶记录、车辆前车过近监控等等设备有的时候也会出一些问题,也需要一系列的工具来监控他们,出现问题快速解决设备监控一般设备出问题,我们会认为是一下3种情况时间没变化,GPS也没变化,这种就说明设备关机或者坏掉了,没有数据上报时间有变化,GPS没变化,终端未定位时,会一直上传最后一条有效位置信息,这说明定位有问题如果都没有数据,那么就是这趟车或者这趟线没有设备被安装安全是第一安全也是运营中最重要的一环,目前班车通过智能硬件设备,对班车的超速、驾驶行为习惯进行监控和取证。超速,可以通过轨迹获取到班车的实时轨迹,对超速路段和速度都能够做到快速的取证超速情况危险驾驶:对于前车过近、压线行驶、疲劳驾驶进行监控后上报给班车管理员进行人工干预前车过近站点的合理性班车是会有很多线路、很多站点的,一条线路的确定要考虑多个因素:通勤时间、上座率、时间与空间的互补,一般来说,上班的点是比较固定的,那么以方向和区域来优化线路是可行的首先,我们得明确优化的方向和方法,拿深圳来说,我们的站点分布就像织网,大多数的站点分布在四个区域,东(市区、龙岗)、南(南山)、西(宝安)、北(龙华),如下图分布情况:站点分布图在优化的时候,可以合并、拆分、搭线三种方式来解决运载问题,科学合理的方式是以本区范围来做优化,同时还要考虑行车距离和行车时间,考虑的因素很多,所以要做到智能的推荐我们事先需要总结一些规律,我们发现一个比较有意思的问题,从区域的角度来看,每个区域的距离都可以用以下结论来套线路:方围2公里经过的线路(跨域带人)区域站点:方围1公里的站点(5-15分钟内可达)横向与纵向员工的体验其实员工需要的很简单,快速的查、实时的看,目前市面上很多的公交类或者地图类app都具备了这些,列如车来了,通过文本形式来展示状态车来了也列如地图类应用,通过地图显示来展示目前的状态腾讯班车此文已由作者授权腾讯云+社区发布搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!

December 6, 2018 · 1 min · jiezi

深入理解苹果系统(Unicode)字符串的排序方法

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由iminder发表于云+社区专栏Unicode编码我们知道计算机是不能直接处理文本的,而是和数字打交道。因此,为了表示文本,就建立了一个字符到数字的映射表,叫做编码。最著名的字符编码就是ASCII了,它使用7-bit来表示应用字母表以及数字和其他字符。这对于英语来说是够用了,但是对于其他语言,这个7-bit就不能满足条件了,因为字符远远超过了7-bit所能表示的最大个数。因此1987年,来自几个大的科技公司的工程师开始合作开发一种致力于能在全世界的所有书写系统中都能通用的字符编码系统,并与1991年10发布了Unicode的1.0.0标准。2018年6月发布了Unicode的11.0版本。这里就不再对Unicode做过多的介绍,值得注意的是,在iOS开发中,常使用的的NSString是基于Unicode-16来开发的,这是因为当时开发这个的时候Unicode标准还是以16bit固定长度来编码,这就导致使用上的一些坑,建议大家阅读下这篇文章:NSString and Unicode。#UCA和CLDR:最常用到的排序标准介绍完Unicode编码之后,我们就可以来介绍UCA(Unicode Collation Algorithm)和CLDR(Common Locale Data Repository)了,因为苹果的NSString的介绍文档里有这么一句话:Localized string comparisons are based on the Unicode Collation Algorithm, as tailored for different languages by CLDR (Common Locale Data Repository). Both are projects of the Unicode Consortium. Unicode is a registered trademark of Unicode, Inc.说白了,苹果系统的NSString字符串排序是基于UCA的,并且在不同语言下,经过CLDR来裁剪的。UCA(Unicode Collation Algorithm)UCA的介绍官方文档介绍在这里:UCA介绍。其中第一句话就写的很清楚,Collation is the general term for the process and function of determining the sorting order of strings of characters.对字符串排序的过程就是Collation,UCA就是Unicode表示的字符串进行排序的规则,制定这个规则的原因是不同语种对字符串的排序规则要求是不一样的,比如,德国、法国和瑞士对相同的字符排序的规则是不一样的,甚至在同一个语言下比如中文,多音字这种在不同组合里,排序的先后顺序也是不一样的。差异化举例因此可以想象,UCA指定的规则比较复杂。感兴趣的可以读下前面贴的UCA介绍,里面有具体的排序规则介绍。CLDR(Common Locale Data Repository)CLDR的官方文档在这里:CLDR介绍。CLDR是一堆语言数据仓库,为软件提供各种世界语言版本提供了基础,目前在使用CLDR的公司有:Apple (macOS, iOS, watchOS, tvOS, and several applications; Apple Mobile Device Support and iTunes for Windows; …) Google (Web Search, Chrome, Android, Adwords, Google+, Google Maps, Blogger, Google Analytics, …) IBM (DB2, Lotus, Websphere, Tivoli, Rational, AIX, i/OS, z/OS,…) Microsoft (Windows, Office, Visual Studio, …)其他公司:ABAS Software, Adobe, Amazon (Kindle), Amdocs, Apache, Appian, Argonne National Laboratory, Avaya, Babel (Pocoo library), BAE Systems Geospatial eXploitation Products, BEA, BluePhoenix Solutions, BMC Software, Boost, BroadJump, Business Objects, caris, CERN, Debian Linux, Dell, Eclipse, eBay, EMC Corporation, ESRI, Firebird RDBMS, FreeBSD, Gentoo Linux, GroundWork Open Source, GTK+, Harman/Becker Automotive Systems GmbH, HP, Hyperion, Inktomi, Innodata Isogen, Informatica, Intel, Interlogics, IONA, IXOS, Jikes, jQuery, Library of Congress, Mathworks, Mozilla, Netezza, OpenOffice, Oracle (Solaris, Java), Lawson Software, Leica Geosystems GIS & Mapping LLC, Mandrake Linux, OCLC, Perl, Progress Software, Python, QNX, Rogue Wave, SAP, Shutterstock, SIL, SPSS, Software AG, SuSE, Symantec, Teradata (NCR), ToolAware, Trend Micro, Twitter, Virage, webMethods, Wikimedia Foundation (Wikipedia), Wine, WMS Gaming, XyEnterprise, Yahoo!, Yelp对于不同区域(local),可以找到不同的数据CLDR,结合UCA对字符串进行排序,就做到了不同语言下的本地化排序。可以去 http://cldr.unicode.org/ 下载最新的CLDR库,后面将会用到里面的一些内容。字符分类与排序规则字符分类与Unicode码点值排序Unicode把所有的字符分为两类:common charaters 包括空格,标点,通用符号,货币符号,数字等。script charaters 包括拉丁字母,希腊字母,汉字等。 这样经过分类,便于把一类字符统一集中在一起。通常情况下,我们是通过unicode 的UTF-16码点值逐个进行比较大小的来进行排序的。NSArray *rawArray = @[@“爱你”, @“一生一世”,@“㊀”, @“上”,@"㊤",@"",@"",@"..",@“123”,@"@",@“AA”,@“abc”,@“abb”];//1. 默认排序方式NSArray defaultedSortedArray = [rawArray sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { return [obj1 compare:obj2 options:NSCaseInsensitiveSearch];}];__block NSMutableArray codeUnits = [NSMutableArray array];[defaultedSortedArray enumerateObjectsUsingBlock:^(NSString _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [codeUnits addObject:@([obj characterAtIndex:0])];}];NSLog(@“默认Unicode码点值排序 %@ 对应的各个字符串的首字符码点值是 %@”, [defaultedSortedArray descriptionWithLocale:cnLocal], codeUnits);输出结果是排序结果 .. 123 @ AA abb abc ㊀ ㊤ 一生一世 上 爱你 对应的各个字符串的首字符码点值是 46 49 64 65 97 97 956 1103 12928 12964 19968 19978 29233 我们常用的各种字符的码点值范围是:0-9 U+0030 - U0039a-z U+0061 - U+007AA-Z U+0041 - U+005A 具体可通过:unicode-table查询。UCA 默认排序在我们前面下载的文件CLDR库有个/common/uca/allkeys_CLDR.txt文件,它表示我们指定locale为“en”或者说是默认的排序规则。它的格式是0000 ; [.0000.0000.0000] # <NULL>0001 ; [.0000.0000.0000] # <START OF HEADING>0002 ; [.0000.0000.0000] # <START OF TEXT>0003 ; [.0000.0000.0000] # <END OF TEXT>分号前的值表示码点,分号后中括号里面的值表示UCA算法权重,用.号来区分,Unicode字符就是按照这个规则从上到下排序。NSLocale enLocale = [[NSLocale alloc] initWithLocaleIdentifier:@“en”];defaultedSortedArray = [rawArray sortedArrayUsingComparator:^NSComparisonResult(NSString _Nonnull obj1, NSString Nonnull obj2) { return [obj1 compare:obj2 options:0 range:NSMakeRange(0, obj1.length) locale:enLocale];}];NSLog(@“默认排序规则或者指定地区为locale后的排序结果是 %@”, [defaultedSortedArray descriptionWithLocale:cnLocal]);排序结果是默认排序规则或者指定地区为en后的排序结果是 .. (ch (en @ 123 AA abb abc ㊀ 一生一世 上 ㊤ 爱你 这种排序依次为符号,数字,英文/汉字等script charaters。CLDR调整后的排序在下载的CLDR文件中,有个common/bcp47/collation.xml文件,列出了可选的排序方式,有standard,pinyin, stroke(笔画排序)等。排序可选方式那如何确定各个区域语言下,该使用哪种排序规则呢,我们可以看到common/collation/文件夹下,有很多标记语言LDML文件,这些文件就是表示在不同区域语言下,采用的排序规则。我们打开zh.xml,这个就是我们简体中文的排序规则,可以看到,里面默认采用的排序是pinyin排序,并且在开头还写了各个声调字母的排序先后顺序。首先按照pinyin声调的先后顺序进行排序,即zh.xml底下列出的先后顺序进行排序。如果是在同一行的汉字,则按照笔画由少到多的顺序进行排序。如果还不能区分大小,就按照kRSUnicode (偏旁索引的方式,按照康熙字典的定义)的先后顺序进行排序。假如我们指定区域为zh_CN,则对于字符串中出现的中文则排在其他语言字符串前面。其他script charater则按照allkeys_CLDR.txt的顺序进行进行排序。值得注意的是,中文由于多音字,在这里不一定能够完全按照我们的习惯排序正确,比如“重逢(chong feng)”就没有第一个拼音chong去排,而是按照zhong来排列的。默认排序规则或者指定地区为zh_CN后的排序结果是 .. (ch (en @ 0124 123 艾你 爱你 産 上 ㊤ ㊀ 一生一世 重逢 重要 aa AA abb 默认排序规则或者指定地区为ru_CN后的排序结果是 .. (ch (en @ 0124 123 aa AA abb ㊀ 一生一世 上 ㊤ 爱你 産 艾你 重要 重逢 至此,我们大致讲清楚了几种排序规则。苹果系统的排序前面我们已经说了,苹果系统的NSString排序是UCA和CLDR规则的。NSString提供了很多的排序方法,但最终,所有的都是调用了compare:options:range:locale:来进行处理,只是传入的参数不同。可以在NSString.swift 中查看具体的实现。这么多排序方法中,其中之一是localizedStandardCompare:, 这个方法是苹果系统推荐的,在给用户展示的列表数据的名字或者其他字符串进行排序时所使用的方法。我们看到,它的内部实现是 public func localizedStandardCompare( string: String) -> ComparisonResult { return compare(string, options: [.caseInsensitive, .numeric, .widthInsensitive, .forcedOrdering], range: NSRange(location: 0, length: length), locale: Locale.current._bridgeToObjectiveC()) }其中用到的四个Options参数是NSCaseInsensitiveSearch //大小写不敏感NSNumericSearch //对字符串中出现的数字字符进行数字化的大小比较,比如Foo2.txt < Foo7.txt < Foo25.txtNSWidthInsensitiveSearch //忽略宽度,按照实际表示的意思来对比,如’a’ = UFF41NSForcedOrderingSearch //强制返回Ascending或者Descending,和NSCaseInsensitiveSearch结合起来就是例如"aaa" > “AAA"并且指定了当前的区域locale作为参数,这就相当于指定使用CLDR进行排序,如果是在手机上,这个方法的调用和系统当前的区域设置是有很大关系的,这和我们代码中设置locale是一个道理。我们可以这样理解,调用这个方法得到的结果和在iOS Files中文件名选择按照名称排序得到的结果是一样的。在iOS中,当我们的区域设置为中国时,排序顺序就是 标点符号等特殊符号>数字>中文>英文等其他。区域设置成中文后的排序自此,对localizedStandardCompare:的使用,大家应该比较清楚了。数字的比较这里单独把数字字符串的比较列出来,是因为一些人对这里比较迷惑。由于localizedStandardCompare:中有使用NSNumericSearch选项,这里简单来说,就是假如目前两个字符串是相等的,两者都出现了数字,则分别从两者种取出这段数字进行数字化来比较大小,按照数字大小排序。为了验证这里的逻辑,我看了下CFString.c中CFStringCompareWithOptionsAndLocale这个方法的实现,这个就是compare实际调用的的比较方法。其中关于数字大小比较的代码如下:if (numerically && ((0 == strBuf1Len) && (str1Char <= ‘9’) && (str1Char >= ‘0’)) && ((0 == strBuf2Len) && (str2Char <= ‘9’) && (str2Char >= ‘0’))) { // If both are not ASCII digits, then don’t do numerical comparison here uint64_t intValue1 = 0, intValue2 = 0; // !!! Doesn’t work if numbers are > max uint64_t CFIndex str1NumRangeIndex = str1Index; CFIndex str2NumRangeIndex = str2Index; do { intValue1 = (intValue1 * 10) + (str1Char - ‘0’); str1Char = CFStringGetCharacterFromInlineBuffer(&inlineBuf1, ++str1Index); } while ((str1Char <= ‘9’) && (str1Char >= ‘0’)); do { intValue2 = intValue2 * 10 + (str2Char - ‘0’); str2Char = CFStringGetCharacterFromInlineBuffer(&inlineBuf2, ++str2Index); } while ((str2Char <= ‘9’) && (str2Char >= ‘0’)); if (intValue1 == intValue2) { if (forceOrdering && (kCFCompareEqualTo == compareResult) && ((str1Index - str1NumRangeIndex) != (str2Index - str2NumRangeIndex))) { compareResult = (((str1Index - str1NumRangeIndex) < (str2Index - str2NumRangeIndex)) ? kCFCompareLessThan : kCFCompareGreaterThan); numericEquivalence = true; forcedIndex1 = str1NumRangeIndex; forcedIndex2 = str2NumRangeIndex; } continue; } else if (intValue1 < intValue2) { if (freeLocale && locale) { CFRelease(locale); } return kCFCompareLessThan; } else { if (freeLocale && locale) { CFRelease(locale); } return kCFCompareGreaterThan; } }这段代码的含义就是,如果两个字符串都是以数字开始(也可能是字符串前面都相等,当前从数字部分开始比较),则取出两个字符串的数字,按照数字大小进行对比。如果数字能够比较出大小,则直接返回两个字符串的大小关系,不再对后面的字符串进行对比。比如“0123aaa” 和“1bbbbbbbbb”,就直接返回“0123aaa”大于“1bbbbbbbbb”。当然,这里取出的数字可能超出了uint64_t表示的最大值,但是这种概率很低,在我们的名称排序中,很难遇到这么长的数字进行比较的。明白这个规则后,大家对字符串中出现的数字在进行排序时应该比较理解了。下面的名字排序是对着的。综述本文主要讲述由localizedStandardCompare:这个苹果系统方法所引发的对排序规则的深入研究,简单来说,设置中选择区域为中国时,排序顺序为 标点符号等特殊符号>数字>中文>英文等其他。中文本身是按照pinyin排序的,只是由于多音字的关系,不能够做到100%按照中文习惯来排序,会有些无法正确排序的问题,但大体已经符合我们的习惯了。参考https://zh.wikipedia.org/wiki…https://developer.apple.com/l...https://www.objc.io/issues/9-...http://unicode.org/reports/tr10/https://www.cnblogs.com/huahu...https://raw.githubusercontent...http://cldr.unicode.org/相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 20, 2018 · 4 min · jiezi

iOS开发必会的坐标系探究

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由落影发表于云+社区专栏前言app在渲染视图时,需要在坐标系中指定绘制区域。 这个概念看似乎简单,事实并非如此。When an app draws something in iOS, it has to locate the drawn content in a two-dimensional space defined by a coordinate system. This notion might seem straightforward at first glance, but it isn’t.正文我们先从一段最简单的代码入手,在drawRect中显示一个普通的UILabel; 为了方便判断,我把整个view的背景设置成黑色:- (void)drawRect:(CGRect)rect { [super drawRect:rect]; CGContextRef context = UIGraphicsGetCurrentContext(); NSLog(@“CGContext default CTM matrix %@”, NSStringFromCGAffineTransform(CGContextGetCTM(context))); UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 28)]; testLabel.text = @“测试文本”; testLabel.font = [UIFont systemFontOfSize:14]; testLabel.textColor = [UIColor whiteColor]; [testLabel.layer renderInContext:context];}这段代码首先创建一个UILabel,然后设置文本,显示到屏幕上,没有修改坐标。 所以按照UILabel.layer默认的坐标(0, 0),在左上角进行了绘制。UILabel绘制接着,我们尝试使用CoreText来渲染一段文本。- (void)drawRect:(CGRect)rect { [super drawRect:rect]; CGContextRef context = UIGraphicsGetCurrentContext(); NSLog(@“CGContext default matrix %@”, NSStringFromCGAffineTransform(CGContextGetCTM(context))); NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@“测试文本” attributes:@{ NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont systemFontOfSize:14], }]; CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根据富文本创建排版类CTFramesetterRef UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)]; CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 创建排版数据 CTFrameDraw(frameRef, context);}首先用NSString创建一个富文本,然后根据富文本创建CTFramesetterRef,结合CGRect生成的UIBezierPath,我们得到CTFrameRef,最终渲染到屏幕上。 但是结果与上文不一致:文字是上下颠倒。CoreText的文本绘制从这个不同的现象开始,我们来理解iOS的坐标系。坐标系概念在iOS中绘制图形必须在一个二维的坐标系中进行,但在iOS系统中存在多个坐标系,常需要处理一些坐标系的转换。 先介绍一个图形上下文(graphics context)的概念,比如说我们常用的CGContext就是Quartz 2D的上下文。图形上下文包含绘制所需的信息,比如颜色、线宽、字体等。用我们在Windows常用的画图来参考,当我们使用画笔????在白板中写字时,图形上下文就是画笔的属性设置、白板大小、画笔位置等等。iOS中,每个图形上下文都会有三种坐标: 1、绘制坐标系(也叫用户坐标系),我们平时绘制所用的坐标系; 2、视图(view)坐标系,固定左上角为原点(0,0)的view坐标系; 3、物理坐标系,物理屏幕中的坐标系,同样是固定左上角为原点;根据我们绘制的目标不同(屏幕、位图、PDF等),会有多个context;Quartz常见的绘制目标不同context的绘制坐标系各不相同,比如说UIKit的坐标系为左上角原点的坐标系,CoreGraphics的坐标系为左下角为原点的坐标系;CoreGraphics坐标系和UIKit坐标系的转换CoreText基于CoreGraphics,所以坐标系也是CoreGraphics的坐标系。 我们回顾下上文提到的两个渲染结果,我们产生如下疑问: UIGraphicsGetCurrentContext返回的是CGContext,代表着是左下角为原点的坐标系,用UILabel(UIKit坐标系)可以直接renderInContext,并且“测”字对应为UILabel的(0,0)位置,是在左上角? 当用CoreText渲染时,坐标是(0,0),但是渲染的结果是在左上角,并不是在左下角;并且文字是上下颠倒的。 为了探究这个问题,我在代码中加入了一行log: NSLog(@“CGContext default matrix %@”, NSStringFromCGAffineTransform(CGContextGetCTM(context))); 其结果是CGContext default matrix [2, 0, 0, -2, 0, 200]; CGContextGetCTM返回是CGAffineTransform仿射变换矩阵: 一个二维坐标系上的点p,可以表达为(x, y, 1),乘以变换的矩阵,如下:把结果相乘,得到下面的关系此时,我们再来看看打印的结果[2, 0, 0, -2, 0, 200],可以化简为 x’ = 2x, y’ = 200 - 2y 因为渲染的view高度为100,所以这个坐标转换相当于把原点在左下角(0,100)的坐标系,转换为原点在左上角(0,0)的坐标系!通常我们都会使用UIKit进行渲染,所以iOS系统在drawRect返回CGContext的时候,默认帮我们进行了一次变换,以方便开发者直接用UIKit坐标系进行渲染。我们尝试对系统添加的坐标变换进行还原: 先进行CGContextTranslateCTM(context, 0, self.bounds.size.height); 对于x’ = 2x, y’ = 200 - 2y,我们使得x=x,y=y+100;(self.bounds.size.height=100) 于是有x’ = 2x, y’ = 200-2(y+100) = -2y; 再进行CGContextScaleCTM(context, 1.0, -1.0); 对于x’ = 2x, y’ = -2y,我们使得x=x, y=-y; 于是有 x’=2x, y’ = -2(-y) = 2y;- (void)drawRect:(CGRect)rect { [super drawRect:rect]; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); NSLog(@“CGContext default matrix %@”, NSStringFromCGAffineTransform(CGContextGetCTM(context))); NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@“测试文本” attributes:@{ NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont systemFontOfSize:14], }]; CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根据富文本创建排版类CTFramesetterRef UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)]; CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 创建排版数据 CTFrameDraw(frameRef, context);}通过log也可以看出来CGContext default matrix [2, 0, -0, 2, 0, 0]; 最终结果如下,文本从左下角开始渲染,并且没有出现上下颠倒的情况。 这时我们产生新的困扰: 用CoreText渲染文字的上下颠倒现象解决,但是修改后的坐标系UIKit无法正常使用,如何兼容两种坐标系? iOS可以使用CGContextSaveGState()方法暂存context状态,然后在CoreText绘制完后通过CGContextRestoreGState ()可以恢复context的变换。- (void)drawRect:(CGRect)rect { [super drawRect:rect]; CGContextRef context = UIGraphicsGetCurrentContext(); NSLog(@“CGContext default matrix %@”, NSStringFromCGAffineTransform(CGContextGetCTM(context))); CGContextSaveGState(context); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@“测试文本” attributes:@{ NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont systemFontOfSize:14], }]; CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); // 根据富文本创建排版类CTFramesetterRef UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)]; CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); // 创建排版数据 CTFrameDraw(frameRef, context); CGContextRestoreGState(context); NSLog(@“CGContext default CTM matrix %@”, NSStringFromCGAffineTransform(CGContextGetCTM(context))); UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 20)]; testLabel.text = @“测试文本”; testLabel.font = [UIFont systemFontOfSize:14]; testLabel.textColor = [UIColor whiteColor]; [testLabel.layer renderInContext:context];}渲染结果如下,控制台输出的两个matrix都是[2, 0, 0, -2, 0, 200]; 遇到的问题1、UILabel.layer在drawContext的时候frame失效初始化UILabel时设定了frame,但是没有生效。 UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 100, 28)]; 这是因为frame是在上一层view中坐标的偏移,在renderInContext中坐标起点与frame无关,所以需要修改的是bounds属性: testLabel.layer.bounds = CGRectMake(50, 50, 100, 28);2、renderInContext和drawInContext的选择在把UILabel.layer渲染到context的时候,应该采用drawInContext还是renderInContext?虽然这两个方法都可以生效,但是根据画线部分的内容来判断,还是采用了renderInContext,并且问题1就是由这里的一句Renders in the coordinate space of the layer,定位到问题所在。3、如何理解CoreGraphics坐标系不一致后,会出现绘制结果异常?我的理解方法是,我们可以先不考虑坐标系变换的情况。 如下图,上半部分是普通的渲染结果,可以很容易的想象; 接下来是增加坐标变换后,坐标系变成原点在左上角的顶点,相当于按照下图的虚线进行了一次垂直的翻转。也可以按照坐标系变换的方式去理解,将左下角原点的坐标系相对y轴做一次垂直翻转,然后向上平移height的高度,这样得到左上角原点的坐标系。附录Drawing and Printing Guide for iOS Quartz 2D Programming Guide相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 13, 2018 · 2 min · jiezi

当我们按下电源键,Android 究竟做了些什么?

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦本文由goo发表于云+社区专栏相信我们对Android系统都不陌生,而Android系统博大精深,被各种各样的智能设备承载的同时,我们会否好奇过,如此复杂的Android究竟是怎么运作起来的呢?借本文给大家分享,笔者对Android 系统启动流程的整体理解hi, I’m Android现在,按下电源键下面是Android启动的核心步骤流程图,看文字的时候,记得回来对照图来理解喔,希望阅读全文后,回观流程图,会有恍然大悟的感觉,那么文章的目的就达到啦~整体流程一、启动电源及系统启动系统从 ROM 中开始启动,加载引导程序到 RAM ,然后执行。二、引导程序引导程序是 Android 操作系统开始运行前的一个小程序,因此它需要针对特定主板与芯片,并不是 Android 操作系统的一部分。引导程序是 OEM 厂商或运行商进行加锁、限制的地方。1 两个阶段检测外部 RAM 以及为第二阶段加载程序设置网络、内存等,搭建内核运行环境(为了达到特殊目的时,引导程序可以根据配置参数或者输入数据来设置内核)2 引导程序的加载器Android引导程序可以在bootablebootloaderlegacyusbloader找到,传统的加载器包含的两个文件:init.s 初始化堆栈,清零BSS段,会调用 main.c 中的 _main()函数 (bss segment:通常是指用来存放程序中未初始化的全局变量的一块内存区域;BSS - Block Started by Symbol。BSS段属于静态内存分配)main.c 初始化硬件,创建 linux 标签三、内核启动Android 内核启动方式类似桌面 linux,主要步骤:1. 设置缓存2. 被保护存储器3. 计划列表4. 加载驱动当内核完成系统设置,接下来即将启动系统的第一个进程 – init 进程四、init 进程作为 Android 系统的第一个进程,其PID为0,通过解析 init.rc 脚本来构建出系统初始运行形态,这一阶段中,“Android” logo 会显示出来(系统中,大多数系统服务程序都是在该脚本中描述并被相继启动的)init.rc 由4种类型声明组成:Actions、Commands、Services、OptionsActions:响应某事件的过程。当“trigger”所描述的触发事件产生时,则依次执行各种“command” 源码角度:系统会对 init.rc 中各“trigger”进行匹配,当发现符合条件的 Action,就将它加入“命令执行队列”尾部(除非 Action 已存在队列中),然后系统再对这些命令按顺序进行。on <trigger> ##触发条件 <command1> ##执行命令 <command2> ##可执行多个命令 …Commands:命令将在所属事件发生时被一个个执行Services:可执行程序,它们在特定选项的约束下会被 init 程序运行或者重启(Service 可以在配置中指定是否需要退出重启,那么,当 Service 出现异常 crash 时,可有机会复原)service <name><pathname> [<argument>]* <option> <option>Options:对 service 的约束选项五和六、 ServiceManager、Zygote、SystemServer科普:Daemons - 守护进程init进程通过解析 init.rc 来陆续启动其他关键的系统服务进程,其中最重要的是 ServiceManager、Zygote 和 SystemServer 三者,下面我们逐一解析:1 ServiceManager – Binder 机制支撑者概述:ServiceManager 是 Binder 机制中的支撑者,负责某 Binder 服务注册信息到底层 Binder 驱动分配的值解析。ServiceManager 由 init 进程解析 rc 脚本时启动,属于 core 类,其他同类进程包括:uenetd、console、adbd等。根据 core 组的特性,这些进程会同时启动或停止。另外,ServiceManager 配置含有 critical 属性,这意味着它是系统关键进程(如果进程不幸在4分钟内异常退出超过4次,设备将重启并进入还原模式)。当 ServiceManager 每次重启时,其他关键进程:zygote、media、surfaceflinger 等也会被 restart。2. Zygote – “孕育”新线程与进程Android 中大多数应用进程与系统进程都是通过 Zygote 来生成的。Zygote 同样由 init 解析 rc 脚本时启动,属于 main 类,同属 main 类的系统进程有:netd、debuggerd、rild等。Zygote并不是处于独立的程序中的,它所在程序名为“app_process”,观察 app_process 主函数实现知道,如果 init.rc 中指定了 –zygote选项,app_process 接下来将启动“ZygoteInit”,并传入“start-system-server”,这样,ZygoteInit 就会运行在虚拟机上(Dalvik VM)上了。ZygoteInit 函数有两项重要工作预装载各种系统类搭建 SystemServer 环境,并启动 SystemServer(大部分的 Android 系统服务都在其中,由 Java 编写)ZygoteInit 流程总结(摘自:Gityuan – Android 系统启动-Zygote 篇)解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法;调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数;通过JNI方式调用ZygoteInit.main(),第一次进入Java世界;registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求;preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高ap启动效率;zygote完毕大部分工作,接下来再通过startSystemServer(),fork得力帮手system_server进程,也是上层framework的运行载体。zygote功成身退,调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作。ZygoteInit 结束后,开机Logo就出来了。(注意:这里并不包括开机动画,而是开机前 “Android” Logo 出现的那个画面,开机动画出现之前还需要进行各种加载,开机动画是在“Android” Logo 出现之后才播放的)3. SystemServer – 大部分 Android 系统服务所在地SystemServer 是 Android 进入 Launcher 前的最后准备,它提供了众多的由“Java”语言编写的系统服务。如果 init.rc 中为 zygote 指定启动参数 –start-system-server,那么 ZygotyeInit 就会调用 startSystemServer 来进入 SystemServer。startSystemServer函数解析:首先 ZygoteInit 通过 Zygote.forkSystemServer 来生成一个新的线程(fork),用于承载各种系统服务。(源码角度:Zygote 内部由 Native 函数 Dalvik_dalvik_system_Zygote_forkSystemServer 来进一步实现,最终调用底层接口的 fork 接口来实际产生进程)根据fork特性,子进程与父进程将获得相同的代码环境。pid为0为子进程,否则为父进程;如果是前者,则进一步调用 handleSystemServerProcess(parseArgs) 函数来完成最核心的工作 – “启动各系统服务”(源码角度:handleSystemServerProcess 方法将 startSystemServer 中的 parsedArgs.remainingArgs 参数传给 RuntimeInit.zygoteInit,后者又调用 nativeZygoteInit 函数)nativeZygoteInit 调用后,接着,三个重要的 static 函数就要被执行了:init1 - 完成本地Service(SurfaceFlinger、AudioFlinger等)启动,完成后调用 init2、init2 - 新建一个新的带 Looper 的线程 ServerThread来启动 Java层各 Service后语上面对 Android 系统启动做了一个简述,意在给大家展现一个整体流程,其中每个环节涉及的知识点只是浅浅掠过,笔者也尚在学习与探索中,希望在后续再作详细分析。资源推荐Gityuan - Android 开篇老罗的Android之旅《深入理解 Android 内核设计思想》 由浅入深,落实到源码层面上进行探索,知识很有深度相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 13, 2018 · 2 min · jiezi

快速探索,音视频技术不再神秘

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由goo发表于云+社区专栏与生活紧密相连的音视频,为何有那么多格式?直播、点播以及即时视频其中又有怎样的机制支撑?面对纷繁复杂的音视频知识,应该如何学起?快速探索,音视频技术不再神秘。前言面对一门技术,我们熟悉而陌生,我们能够熟练的基于平台的API完成各种各样的需求,掌握平台特性、框架与原理。但随着技术点不断深入,却发现自己存在基础性与深度性的知识盲区。局限于平台API开发,并不能使我们走的很远。突破技术成长必经的瓶颈期,关键在于技术沉淀与对业务方向相结合,需要我们对知识积累与深入。本文分享了笔者对音视频技术知识网络的探索路径,希望能给大家带来帮助。一、采集 - 数据从哪里来?1.1 采样原理定义:对连续变化图像在空间坐标上做离散化处理,将模拟信号转变成数字信号的过程,即为图像进行采样。通俗来说:采集就是将看到的东西转成二进制流的过程。1.2 基础概念1.2.1 图像「图像」是个集合的概念,帧、顶场、底场都可以称为图像。帧 一帧通常是一幅完整图像,当采用逐行扫描方式扫描,每次扫描得到的信号就是一帧。顶场与底场 采集视频信号时,扫描方式分为逐行扫描与隔行扫描。如果采用逐行扫描,得到的则是一幅完整的图像;而采用隔行扫描(奇、偶数行),则扫描下来的一帧图像就被分为了两个部分,这每一部分就称为「场」,根据次序分为:「顶场」和「底场」隔行扫描 每一帧被分割为两场画面交替显示。每一帧被分割为顶场与底场,通常是先扫描奇数行得到第一场,然后扫描偶数行得到第二场。由于视觉暂留效应,人眼将会看到平滑的运动而不是闪动的半帧半帧的图像。但是这时会有闪烁出现,尽管不容易被察觉,但会使得人眼容易疲劳。当屏幕的内容是横条纹时,这种闪烁特别容易被注意到,并且会有锯齿瑕疵。逐行扫描 则是将每帧的所有画面同时显示。每次都显示整个扫描帧,如果逐行扫描的帧率和隔行扫描的场率相同,人眼将看到比隔行扫描更平滑的图像,相对于隔行扫描来说闪烁较小。每一帧图像均是由电子束顺序地一行接着一行连续扫描而成,这种扫描方式称为逐行扫描。两者区别 举个栗子,25fps 100行帧图像,那么隔行扫描需要一秒扫描50次,但每次只需要扫描50行。而逐行扫描则只需要扫描25次,但每次需要扫描100行。 结论:隔行扫描扫描频率为逐行扫描双倍,信道带宽为逐行扫描的一半。在图像体验降低不多的情况下,信道带宽减少了一半,使得设备成本减少,因此,早期大多数显示器都采用隔行扫描。传送门:逐行扫描、隔行扫描详细讲解逐行扫描与隔行扫描顶场与底场,隔行扫描锯齿瑕疵1.2.2 颜色模型RGB颜色模型RGB模型RGB分别代表红绿蓝,每种颜色需要用3个数字表示,一个数字占用1字节,一种颜色则需要3字节,24位。更高效的颜色模型?YUVYCbCr颜色模型YCbCr颜色模型是YUV家族的一员,关键特点在于它亮度信号Y与色度信号U、V相互分离。当缺失U、V,仅有Y信号时,也能够表示出黑白图像。Y = kr*R + kg*G + kb*BY 即「亮度」,kr、kg、kb 即 R、G、B 的权重值。Cr = R – Y; Cg = G – Y; Cb = B – Y;疑问:对比RGB模型,YCbCr模型每个像素也需要3个信号表示,为什么说该模型更高效?优化思路人眼对亮度分辨率敏感度高于色彩敏感度。视觉特性基于人眼视觉特性,很明显,我们需要从颜色方面入手,于是提出“色度取样”,使颜色存储减半或者更多。容易实现,编码压力较小,收益较高。色度取样优化实现我们知道显示器扫描原理分为逐行扫描与隔行扫描,每条扫描线被扫描时,色度数值传送频率会比亮度低,颜色取样方式有多种,取样方式通常基于亮度值,以4:X:Y的形式描述,X和Y是每两个色度通道中的数值的相对数量:显示器扫描显示原理继续举个栗子:YCbCr像素点我们有这样一幅图片,上面有像素阵列:原始像素阵列YCbCr 4:4:4会有以下几种采样优化方式:4:2:2优化后像素阵列4:2:2取样方式4:2:0优化后像素阵列4:2:0取样方式上图可以很直观的看出:采用YCbCr颜色模型后,并不需要每个像素都存有3个分量,颜色分量通过“色度取样”后,有效的减少了颜色分量的存储。1.3 图像感知与获取<center><img src=“https://ask.qcloudimg.com/draft/2557878/yjqoq9qlhg.png"width="70" />成像传感器</center>通过电功率和对特殊类型检测能源敏感的传感器材料组合。将输入的光照能量变为特殊的电压波形。波形的幅度和空间特性都与感知的物理现象有关。为了产生数字图像,接下来需要进行取样与量化处理。1.4 取样与量化举个栗子,对于黑白图像图(a)为连续图像,如果需要转换成数字形式,需要几步主要操作:取样与量化取样:(a)图上沿AB线段等间隔对该图像取样,得到灰度级曲线(b)量化:(c)图右侧将灰度分为8个灰度级,再横向每一取样的连续灰度值,量化为8个灰度之一,最终得到(d)图,感知器输出的量化完成流产生数字图像的过程。 a. 图像投影至传感器阵列 b. 图像取样与量化结果二、渲染 - 数据如何展现?2.1 播放器原理播放器播放从互联网上播放视频,需要经过:解协议、解封装、解码、音视频同步这几个核心步骤。<center>互联网播放视频流程解协议:将流媒体协议数据,解析为标准封装格式数据。流媒体协议传输音视频数据同时,也会传输一些信令数据,其中包括:播放控制、网络状态描述等。常见流媒体协议如HTTP、RTMP或MMS等。解封装:将解协议得到的标准封装格式数据,分离为音频流压缩编码数据与视频流压缩编码数据。封装格式也称为容器,即是将已经编码压缩好的视频轨与音频轨按照一定格式放到一个文件中。 需要注意的是:就算是同一个封装格式,其编码方式并不一定一样,我们可以从后缀名中直观的看到视频文件到封装格式。常见封装格式:avi,rmvb,mp4,flv,mkv等。解码:就是将音视频压缩编码数据,解码成为非压缩的音视频原始数据。音频编码标准有AAC,MP3,AC-3等;视频编码标准包含H.264,MPEG2,VC-1等。编解码是整个流程最核心与最复杂的环节。音视频同步:根据解封装过程获取的参数信息,将解码出来的音视频数据进行同步对其,最终将数据传送到系统,由系统调用硬件进行播放。2.2 视频编码方式视频编解码过程是数字视频压缩与解压缩的过程。选取音视频编码方案时,需要考虑:视频的质量、码率、编码算法和解码算法的复杂度、针对数据丢失和错误的鲁棒性(Robustness)、编辑的方便性、随机访问、编码算法设计的完美性、端到端的延时以及其它一些因素。2.2.1 H.26X系列概述H.26X 系列,由国际电传视讯联盟远程通信标准化组织(ITU-T)主导,包括 H.261、H.262、H.263、H.264、H.265。H.261,主要用于老的视频会议和视频电话系统。是第一个使用的数字视频压缩标准。实质上说,之后的所有的标准视频编解码器都是基于它设计的。H.262,等同于 MPEG-2 第二部分,使用在 DVD、SVCD 和大多数数字视频广播系统和有线分布系统中。H.263,主要用于视频会议、视频电话和网络视频相关产品。在对逐行扫描的视频源进行压缩的方面,H.263 比它之前的视频编码标准在性能上有了较大的提升。尤其是在低码率端,它可以在保证一定质量的前提下大大的节约码率。H.264,等同于 MPEG-4 第十部分,也被称为高级视频编码(Advanced Video Coding,简称 AVC),是一种视频压缩标准,一种被广泛使用的高精度视频的录制、压缩和发布格式。该标准引入了一系列新的能够大大提高压缩性能的技术,并能够同时在高码率端和低码率端大大超越以前的诸标准。H.265,被称为高效率视频编码(High Efficiency Video Coding,简称 HEVC)是一种视频压缩标准,是 H.264 的继任者。HEVC 被认为不仅提升图像质量,同时也能达到 H.264 两倍的压缩率(等同于同样画面质量下比特率减少了 50%),可支持 4K 分辨率甚至到超高画质电视,最高分辨率可达到 8192×4320(8K 分辨率),这是目前发展的趋势。详解待整理另外文章2.2.2 MPEG系列概述MPEG 系列,由国际标准组织机构(ISO)下属的运动图象专家组(MPEG)开发。MPEG-1 第二部分,主要使用在 VCD 上,有些在线视频也使用这种格式。该编解码器的质量大致上和原有的 VHS 录像带相当。MPEG-2 第二部分,等同于 H.262,使用在 DVD、SVCD 和大多数数字视频广播系统和有线分布系统中。MPEG-4 第二部分,可以使用在网络传输、广播和媒体存储上。比起 MPEG-2 第二部分和第一版的 H.263,它的压缩性能有所提高。MPEG-4 第十部分,等同于 H.264,是这两个编码组织合作诞生的标准。详解待整理另外文章2.3 音频编解码方式除了视频,音频当然也需要编码,而音频常用编码格式:AAC,英文全称 Advanced Audio Coding,是由 Fraunhofer IIS、杜比实验室、AT&T、Sony等公司共同开发,在 1997 年推出的基于 MPEG-2 的音频编码技术。2000 年,MPEG-4 标准出现后,AAC 重新集成了其特性,加入了 SBR 技术和 PS 技术,为了区别于传统的 MPEG-2 AAC 又称为 MPEG-4 AAC。(AAC详解待整理另外文章)MP3,英文全称 MPEG-1 or MPEG-2 Audio Layer III,是当曾经非常流行的一种数字音频编码和有损压缩格式,它被设计来大幅降低音频数据量。它是在 1991 年,由位于德国埃尔朗根的研究组织 Fraunhofer-Gesellschaft 的一组工程师发明和标准化的。MP3 的普及,曾对音乐产业造成极大的冲击与影响。WMA,英文全称 Windows Media Audio,由微软公司开发的一种数字音频压缩格式,本身包括有损和无损压缩格式。三、处理 - 数据怎么加工?音视频加工处理,是业务的核心需求,对开发者自由度最大的一个环节,通过音视频处理,可以实现各种各样炫酷的特效。图像、视频常见处理方式:美化、裁剪、缩放、旋转、叠加、编解码等。音频常见处理方式:重采样、去噪,回声消除,混音、编解码等常见框架:图像处理:OpenGL,OpenCV,libyuv,ffmpeg 等;视频编解码:x264,OpenH264,ffmpeg 等;音频处理:speexdsp,ffmpeg 等;音频编解码:libfaac,opus,speex,ffmpeg 等。(传送门:音视频开发开源码工程汇总)四、传输 - 数据如何传输?4.1 流媒体协议流媒体,指通过互联网以流式传输方式的媒体。流媒体协议,则是服务器与客户端之间通信遵循但规定。说到音视频传输,我们不得不提流媒体协议,常见流媒体协议有:协议概述特点应用场景RTP(Real-time Transport Protocol)一种网络传输协议,RTP协议详细说明了在互联网上传递音频和视频的标准数据包格式。基于UDP 协议实现RTP协议常用于流媒体系统(配合 RTSP 协议)RTCP(Real-time Transport Control Protoco)实时传输协议(RTP)的一个姐妹协议。RTCP为RTP媒体流提供信道外(out-of-band)控制。RTCP 本身并不传输数据,但和 RTP 一起协作将多媒体数据打包和发送。RTCP 定期在流多媒体会话参加者之间传输控制数据。为 RTP 所提供的服务质量(Quality of Service)提供反馈。RTSP(Real Time Streaming Protocol)定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。RTSP 在体系结构上位于 RTP 和 RTCP 之上,使用 TCP 或 UDP 完成数据传输使用 RTSP 时,客户机和服务器都可以发出请求,即 RTSP 可以是双向的。RTMP(Real Time Messaging Protocol)Adobe Systems 公司为 Flash 播放器和服务器之间音频、视频和数据传输开发的开放协议。协议基于 TCP,是一个协议族,包括 RTMP 基本协议及 RTMPT/RTMPS/RTMPE 等多种变种。一种设计用来进行实时数据通信的网络协议,主要用来在 Flash/AIR 平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。RTMFP(Real Time Media Flow0 Protoco)Adobe 公司开发的一套新的通信协议,全称 Real Time Media Flow Protocol协议基于 UDP,支持 C/S 模式和 P2P 模式,即该协议可以让使用 Adobe Flash Player 的终端用户之间进行直接通信Adobe Flash Player 的终端用户之间进行直接通信HTTP(HyperText Transfer Protoco)运行在 TCP 之上 这个协议是大家非常熟悉的,它也可以用到视频业务中来。HLS(HTTP Live Streaming)是苹果公司实现的基于 HTTP 的流媒体传输协议,全称 ,可支持流媒体的直播和点播短时长的媒体文件(MPEG-TS 格式),客户端不断的下载并播放这些小文件。由于数据通过 HTTP 协议传输,所以完全不用考虑防火墙或者代理的问题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,以适应不同带宽条件下的播放 HLS 的这种技术特点,决定了它的延迟一般总是会高于普通的流媒体直播协议主要应用在 iOS 系统,为 iOS 设备(如 iPhone、iPad)提供音视频直播和点播方案。4.2 网络视频点播业务公司协议封装视频编码音频编码播放器CNTVHTTPMP4H.264AACFlashCNTV(部分)RTMPFLVH.264AACFlash华数 TVHTTPMP4H.264AACFlash优酷网HTTPFLVH.264AACFlash土豆网HTTPF4VH.264AACFlash56网HTTPFLVH.264AACFlash音悦台HTTPMP4H.264AACFlash乐视网HTTPFLVH.264AACFlash新浪视频HTTPFLVH.264AACFlash网络视频点播业务采用 HTTP 有两方面优势:HTTP 是基于 TCP 协议的应用层协议,媒体传输过程中不会出现丢包等现象,从而保证了视频的质量。HTTP 是绝大部分的 Web 服务器支持的协议,因而流媒体服务机构不必投资购买额外的流媒体服务器,从而节约了开支。对于封装格式:MP4,FLV,F4V 几者只是容器,带来的差异不大,而关键的是音视频解码方式:H.264与AAC,这两种编码标准目前仍被最广泛的应用。4.3 网络视频直播业务公司协议封装视频编码音频编码播放器华数TVRTMPFLVH.264AACFlash六间房RTMPFLVH.264AACFlash中国教育电视台RTMPFLVH.264AACFlash北广传媒移动电视RTMPFLVH.264AACFlash上海IPTVRTSP+RTPTSH.264MP2机顶盒网络视频直播服务采用 RTMP 作为直播协议的好处是可以直接被 Flash 播放器支持,而 Flash 播放器在 PC 时代有着极高的普及率,并且与浏览器结合的很好。因此这种流媒体直播平台基本上可以实现了「无插件直播」,极大降低了用户使用成本。封装格式、视频编码、音频编码、播放器方面几乎全部采用了 FLV、H.264、AAC、Flash。FLV、RTMP、Flash 都是 Adobe 公司的产品,天生有着良好的结合性。4.4 总结以上为PC时代旧数据,现移动互联网已爆发,H5 以及客户端应用的普及,行业中对视频业务技术方案的选择也逐渐在发生着变化,而我们则需要结合眼下的实际情况和技术发展的趋势去做出合适的技术选型。结语音视频技术道路很长,本文旨在搭建音视频知识知识网,许多知识未能深入,后续仍需要我们不断学习与实践,抱着追求极致的精神去探索发现,加油,我们共同快速成长!相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 11, 2018 · 2 min · jiezi

Android如何实现超级棒的沉浸式体验

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由brzhang发表于云+社区专栏做APP开发的过程中,有很多时候,我们需要实现类似于下面这种沉浸式的体验。沉浸式体验一开始接触的时候,似乎大家都会觉这种体验实现起来,会比较困难。难点在于:头部的背景图在推上去的过程中,慢慢的变得不可见了,整个区域的颜色变成的暗黑色,然后标题出现了。StatusBar变的透明,且空间可以被利用起来,看我们的图片就顶到了顶 了。我们的viewpager推到actionbar的下方的时候,就固定在了actionbar的下方,不能在往上面推了。底部有一个控件,随着列表的向上滑动,它退出视角范围,以便于给出更多的空间来展示列表,其实整个沉浸式体验都是为了给列表留出更多的空间来展示。好,总结起来以上就是我们的问题,也是需要解决的,一个一个解决了,这种需求也就实现了,那么,我们如何去一步一步来解决以上的问题呢?1、头部背景和标题的渐隐渐现首先,我们来分析第一个问题,头部的背景图在推上去的过程中,慢慢的变得不可见了,这种听起来好像是某种collapse,因此,很容易让人想到CollapsingToolbarLayout,如果你想要比较容易的了解CollapsingToolbarLayout应用,建议看这位兄台的文章,他给也给了一个动画,比较详细的介绍了这个的应用,例如:CollapsingToolbarLayout对于里面的用法,我这里不作讲解了,但是如果你不了解这个布局的应用,我强烈建议你好好了解一下,才能继续下面走,只是想说明一下,走到这里,你有一个坑需要去填,那就是我们的标题动画可以不是这样的,而且,还是标题还是居中的,注意,这里的实现,标题不是居中的,是靠左的,这本来是Android设计规范,但是设计师偏偏不买Android规范的账,因此,我们必须躺过这个坑,然后,从Stack Overflow上了解到一个issue:<android.support.v7.widget.Toolbar android:id="@+id/toolbar_top" android:layout_height=“wrap_content” android:layout_width=“match_parent” android:minHeight="?attr/actionBarSize" android:background="@color/action_bar_bkgnd" app:theme="@style/ToolBarTheme" > <TextView android:layout_width=“wrap_content” android:layout_height=“wrap_content” android:text=“Toolbar Title” android:layout_gravity=“center” android:id="@+id/toolbar_title" /></android.support.v7.widget.Toolbar>假设,这个方式是可行的,那么要解决居中的问题后,把返回按钮改为我们的按钮样式,然后,在耍点小诡计,让title开始是透明的,并且改变返回按钮的图片:collapsingToolbarLayout.setCollapsedTitleTextColor(Color.WHITE);//collapsingToolbarLayout.setExpandedTitleColor(Color.WHITE);collapsingToolbarLayout.setExpandedTitleColor(Color.TRANSPARENT);然而,假设,始终只是一个假设,实际上,这个假设不成立,我在尝试的时候,发现Toolbar中的TextView根本就不能使用android:layout_gravity=“center"这种属性好吧,即使强行加上,效果也是靠左的。那么,如何做,我的解决方式是这样的<android.support.design.widget.AppBarLayout android:id=”@+id/appbarlayout" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:elevation=“0dp”> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:contentScrim="@color/b_G6" app:expandedTitleMarginEnd=“10dp” app:expandedTitleMarginStart=“10dp” app:layout_scrollFlags=“scroll|exitUntilCollapsed|snap”> <android.support.constraint.ConstraintLayout android:layout_width=“match_parent” android:layout_height=“match_parent”> <ImageView android:id="@+id/igame_arena_rank_class_header_bg" android:layout_width=“match_parent” android:layout_height=“0dp” android:scaleType=“centerCrop” android:src="@drawable/bg_arena_rank_class" app:layout_constraintDimensionRatio=“375:156” /> ……… </android.support.constraint.ConstraintLayout> <android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:minHeight="?android:attr/actionBarSize" android:visibility=“visible” app:contentInsetLeft=“0dp” app:contentInsetStart=“0dp” app:layout_collapseMode=“pin”> <include layout="@layout/igame_common_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:layout_gravity=“center” /> </android.support.v7.widget.Toolbar> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout>然后,include里面的布局是这样的<?xml version=“1.0” encoding=“utf-8”?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:orientation=“vertical”>//请注意这个View**/// <View android:id=”@+id/common_index_activity_view_status_bar” android:layout_width=“match_parent” android:layout_height=“0dp” /> <RelativeLayout android:layout_width=“match_parent” android:layout_height=“50dp”> <TextView android:id="@+id/tv_toolbar_bg" android:layout_width=“match_parent” android:layout_height=“50dp” android:layout_centerInParent=“true” tools:background="@color/b_G6" /> <TextView android:id="@+id/common_index_header_tv_title" android:layout_width=“wrap_content” android:layout_height=“wrap_content” android:layout_centerInParent=“true” android:gravity=“center” android:textColor="@color/b_G99" android:textSize="@dimen/igame_textsize_xl" tools:text=“这里是标题” /> <RelativeLayout android:id="@+id/common_index_header_rl_back" android:layout_width=“48dp” android:layout_height=“48dp” android:layout_centerVertical=“true” android:layout_gravity=“center_vertical” android:visibility=“visible”> <ImageView android:layout_width=“match_parent” android:layout_height=“match_parent” android:layout_centerInParent=“true” android:contentDescription="@string/image_desc" android:scaleType=“centerInside” android:src="@drawable/igame_actionbar_arrow_left" /> </RelativeLayout> </RelativeLayout></LinearLayout>效果就是这样当然,这时候,标题是需要你自己设置渐隐渐现的。那么,我们依据什么呢?appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { mTitle.setAlpha(-verticalOffset * 1.0f / appBarLayout.getTotalScrollRange()); } });依据的就是对appBarLayout的监听。2、将statusBar变为透明,且利用他的空间来放我们的布局内容。 /** * 使状态栏透明,并覆盖状态栏,对API大于19的显示正常,但小于的界面扩充到状态栏,但状态栏不为透明 / @TargetApi(Build.VERSION_CODES.KITKAT) public static void transparentAndCoverStatusBar(Activity activity) { //FLAG_LAYOUT_NO_LIMITS这个千万别用,带虚拟按键的机型会有特别多问题// //FLAG_TRANSLUCENT_STATUS要求API大于19// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);// //FLAG_LAYOUT_NO_LIMITS对API没有要求// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = activity.getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); window.setNavigationBarColor(Resources.getSystem().getColor(android.R.color.background_dark)); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Window window = activity.getWindow(); window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } }这里是在网上找的一个方法,直接调用即可,但是API需要大于19,相信目前基本上都满足吧。请注意,我的AppBarLayout中并没有这个属性android:fitsSystemWindows=“true"如果你加了这个属性,嘿嘿,statusbar虽然空间可以利用,但是有一个你挥之不去的颜色覆盖在上面,然后,你还记得上面那个布局中//请注意这个View*/// <View android:id=”@+id/common_index_activity_view_status_bar" android:layout_width=“match_parent” android:layout_height=“0dp” />这个作用可大了,就是为了对status_bar原始空间做偏移的,在代码中,需要动态的改变这个View的高度为statusBar的高度,怎么获取:/** * 获取状态栏高度 * * @param context context * @return 状态栏高度 */ public static int getStatusBarHeight(Context context) { // 获得状态栏高度 int resourceId = context.getResources().getIdentifier(“status_bar_height”, “dimen”, “android”); return context.getResources().getDimensionPixelSize(resourceId); }完了之后,还需要设置我们自己塞进去的那个toolbar的高度为toolbar的高度加上StatusBar的高度。3、ViewPager推到actionbar下面就不让在推了这个其实需要你CollapsingToolbarLayout里面有一个子view是要使用pin模式的,那么这个子view是谁,显然就是那个toolbar了<android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:minHeight="?android:attr/actionBarSize" android:visibility=“visible” app:contentInsetLeft=“0dp” app:contentInsetStart=“0dp” app:layout_collapseMode=“pin”> <include layout="@layout/igame_common_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:layout_gravity=“center” /> </android.support.v7.widget.Toolbar>4、底部控件随着列表的滑动渐渐隐藏可以看到,底部的控件是覆盖在列表上的,列表向上滑动的时候,把他隐藏,就可以空出更多的控件看列表。那么,如何做呢?既然,我们是包裹在CoordinatorLayout中,那么,显然,最好的方式是使用layout_behavior了,我这里实现了一个BottomBehavior:public class BottomBehavior extends CoordinatorLayout.Behavior { private int id; private float bottomPadding; private int screenWidth; private float designWidth = 375.0f;//设计视图的宽度,通常是375dp, public BottomBehavior() { super(); } public BottomBehavior(Context context, AttributeSet attrs) { super(context, attrs); screenWidth = getScreenWidth(context); TypedArray typedArray = context.getResources().obtainAttributes(attrs, R.styleable.BottomBehavior); id = typedArray.getResourceId(R.styleable.BottomBehavior_anchor_id, -1); bottomPadding = typedArray.getFloat(R.styleable.BottomBehavior_bottom_padding, 0f); typedArray.recycle(); } @Override public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) { params.dodgeInsetEdges = Gravity.BOTTOM; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { return dependency.getId() == id; } @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { child.setTranslationY(-(dependency.getTop() - (screenWidth * bottomPadding / designWidth))); Log.e(“BottomBehavior”, “layoutDependsOn() called with: parent = [” + dependency.getTop()); return true; } public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = null; if (wm != null) { display = wm.getDefaultDisplay(); Point size = new Point(); display.getSize(size); int width = size.x;// int height = size.y; return width; } return 0; }}这个里面有两个自定义属性,id,bottomPadding,id表示基于哪个控件的相对位置改变,我这打算基于viewpager这个控件,看源码可以知道,只有当onDependentViewChanged返回ture时,layoutDependsOn才会被回调。bottomPadding是表示一个初始的偏移,因为viewpager本身不是顶在屏幕顶端的(开始被图片占据了一部分控件),因此,需要扣除这部分占有。同理,加入让你实现一个悬浮在左侧,右侧,滑动隐藏,停止显示的,也都可以参考类似Behavior的方式,减少代码耦合。总结最后整个布局是这样子的<?xml version=“1.0” encoding=“utf-8”?><com.tencent.igame.view.common.widget.IGameRefreshLayout xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:app=“http://schemas.android.com/apk/res-auto” android:id="@+id/igame_competition_detail_fragment_refresh" android:layout_width=“match_parent” android:layout_height=“match_parent”> <android.support.design.widget.CoordinatorLayout android:layout_width=“match_parent” android:layout_height=“match_parent”> <android.support.design.widget.AppBarLayout android:id="@+id/appbarlayout" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:elevation=“0dp”> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:contentScrim="@color/b_G6" app:expandedTitleMarginEnd=“10dp” app:expandedTitleMarginStart=“10dp” app:layout_scrollFlags=“scroll|exitUntilCollapsed|snap”> <android.support.constraint.ConstraintLayout android:layout_width=“match_parent” android:layout_height=“match_parent”> <ImageView android:id="@+id/igame_arena_rank_class_header_bg" android:layout_width=“match_parent” android:layout_height=“0dp” android:scaleType=“centerCrop” android:src="@drawable/bg_arena_rank_class" app:layout_constraintDimensionRatio=“375:156” /> ………… </android.support.constraint.ConstraintLayout> <android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:minHeight="?android:attr/actionBarSize" android:visibility=“visible” app:contentInsetLeft=“0dp” app:contentInsetStart=“0dp” app:layout_collapseMode=“pin”> <include layout="@layout/igame_common_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:layout_gravity=“center” /> </android.support.v7.widget.Toolbar> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <com.tencent.igame.widget.viewpager.IgameViewPager android:id="@+id/igame_arena_rank_class_vp_content" android:layout_width=“match_parent” android:layout_height=“match_parent” app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <android.support.constraint.ConstraintLayout android:layout_width=“match_parent” android:layout_height=“60dp” android:layout_gravity=“bottom” android:background="@color/b_G6" android:paddingLeft=“12dp” android:paddingRight=“12dp” app:anchor_id="@+id/igame_arena_rank_class_vp_content" app:bottom_padding=“156.0” app:layout_behavior=“com.tencent.igame.common.widget.BottomBehavior”>……….底部布局 </android.support.constraint.ConstraintLayout> </android.support.design.widget.CoordinatorLayout></com.tencent.igame.view.common.widget.IGameRefreshLayout>注:IGameRefreshLayout实际上就是封装的PullToRefreshView,IgameViewPager是我们封装的Viewpager,减少每次写Viewpager的套路代码。按照这个框架来,相信你很容易写出这个样子的布局。相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 8, 2018 · 3 min · jiezi

Android P的APP适配总结,让你快人一步

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由QQ音乐技术团队发表于云+社区专栏上篇:Android P 行为变更适配Android P 这次有很多行为变更,其中不乏一些需要亟需适配的变更。一、全面屏检测在 Android 8.0 时代各个手机厂商就开始发布自己的全面屏手机,但是此时 Android 官方并未支持到该功能,所以各个厂商都各自实现了一套全面屏判断逻辑,对于开发者来说甚是麻烦。终于在 Android P 里官方收归了该功能的判断逻辑,Android P 和之后的版本完全可以使用官方 API 来判断全面屏,当然前提是第三方厂商按照 google 官方接口去实现。Android P 版本判断全面屏代码很简单,但是在适配过程中你可能会在网上发现如下判断代码:if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { decorView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @RequiresApi(api = 28) @Override public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) { if (windowInsets != null) { DisplayCutout cutout = windowInsets.getDisplayCutout(); if (cutout != null) { List<Rect> rects = cutout.getBoundingRects(); //通过判断是否存在rects来确定是否全面屏手机 if (rects != null && rects.size() > 0) { isNotchScreen = true; } } } return windowInsets; } });}这段代码确实可以判断出全面屏与否,但是会造成一个很严重的后果,就是在某些手机(pixel 和 vivo x21 均出现该情况)上底部导航栏会透明,导致应用内容会透到导航栏从而被遮挡,大大影响内容展示。最后经过仔细排查发现仅仅因为在上面那段代码中调用了 setOnApplyWindowInsetsListener 函数,该函数在 Android 官网有详细介绍,是用来在 Android 21 版本之后代替 fitSystemWindows 函数,目的是让 View 根据 Window 的缩进进行相应处理,调用后会影响系统状态栏和导航栏对应用内容的展示,对此的介绍资料网上有很多,就不赘述了。真正完美判断全面屏的代码如下:if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowInsets windowInsets = decorView.getRootWindowInsets(); if (windowInsets != null) { DisplayCutout displayCutout = windowInsets.getDisplayCutout(); if (displayCutout != null) { List<Rect> rects = displayCutout.getBoundingRects(); //通过判断是否存在rects来确定是否刘海屏手机 if (rects != null && rects.size() > 0) { isNotchScreen = true; } } }}二、非 SDK API 适配详解2.1 非 SDK API 名单介绍Android P 版本最大最严格的特性变更应该非 SDK 接口限制莫属了。对于非 SDK API 里面的部分名单来说,就算在不修改 targetSdkVersion 的前提下,不管是直接、反射还是通过 JNI 调用都会造成调用失败、抛出 NoSuchFieldException或 NoSuchMethodException 等严重后果,该行为影响范围波及所有调用此接口的应用。非 SDK API 名单总共分为三类:light grey list (浅灰名单)、dark grey list (深灰名单)、dark list(黑名单),详情:2.2 非 SDK API 名单扫描所以对于我们应用开发者来说,当前首要任务是适配深灰名单和黑名单。目前 google 官方提供了一个可以实时查询三个名单里面 API 列表的网站:https://android.googlesource….。在之前 DP 版本时开发者如果遇到了不得不使用的黑名单或者深灰名单 API,需要向 google 官方及时提出反馈(反馈url:https://issuetracker.google.c…),申请将其移动到浅灰名单中,但是目前正式版本已经发布,未得知该申请通道是否仍有效。 详细了解了非 SDK API 之后,下一步当然是将应用代码里面的深灰名单和黑名单 API 调用找出来一一修改。目前官方提供了一个非常实用的扫描工具,该工具可以把应用里面三个类型名单的 API 调用都扫描出来(但是可能会有遗漏),使用方法也很简单:打包一个应用 APK,建议使用 release 包,排除一些未使用到的单元测试类或者其他因素的影响,将 APK 放到工具指定目录下;执行命令 ./appcompat.sh –dex-file=test.apk,在终端上会输出三个名单每个 API 的详细调用处: #1: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;)Ljava/lang/String; use(s): Ltmsdkobf/gv;->a(Ljava/lang/String;)Ljava/lang/String; #2: Linking dark greylist Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; use(s): Ltmsdkobf/gp;->b(Landroid/content/Context;)Ljava/lang/String; ….2.3 非 SDK API 适配经过上一步扫描出应用内非 SDK API 调用之后,接下来就可以直接开始适配。适配的原则是优先黑名单和深灰名单,浅灰名单在官方未有替代 API 之前可以暂时不适配,在 Android P 上运行也不会有任何问题。扫描完成之后,不出意外大家应该会有三类需要适配的 API 调用:应用代码本身调用到了非 SDK API 接口; 针对应用代码本身调用到了非 SDK API 接口,用的比较频繁的例如 SystemProperties.get,就需要去寻找另外一个可以替代的合法 API,如果找不到就只能认为该 API 调用失败从而走失败逻辑,如果实在必须要用到该 API 就尽早去向 google 申请移动到浅灰名单中。第三方库调用到了非 SDK API 接口; 针对第三方库调用到了非 SDK API 接口,解决办法当然是直接查询相关资料或者联系库提供方,确认是否有适配 Android P 新版本的 SDK。还有需要提到的一点,就算更换适配完成的第三方 SDK 后,仍然可能会在同一地方扫描出非 SDK API 的调用,这是因为适配工程师只是在调用处加了一个 try-catch 保护逻辑,虽然这样也勉强叫做适配完成,但是还是强烈建议大家使用如下的适配方式: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Android P or above } else { // below Android P } 严格按照上面的适配方案,扫描工具就不会再扫描出此处的非 SDK API 调用,我们也无需每次都去确认所有非 SDK API 调用处都加了保护逻辑。 当然如果第三方库没有适配也没有近期适配的意向,目前有两种方法:第一种是屏蔽入口;第二种是反编译 SDK,在关键地方加上适配代码;Android 官方库调用到了非 SDK API 接口; 没错!Android 官方库也会被扫描出非 SDK API 调用,针对这种情况,需要分情况讨论:该 API 调用查看 v7 support 包源码可以发现已经被 try-catch 住了,测试了相关类也可以正常运行,而且在适配过程中升级 rc 版本的 support-v7 包会导致应用编译不过,所以目前 QQ 音乐暂时认定无需升级到最新版本的 support-v7。除上面介绍的特殊情况之外还是建议更换最新版本的官方 SDK。三、电源管理改进3.1 应用待机群组Android P 上对电源管理又做了一系列的改进措施,不管应用 targetApi 版本是否已经升级到 P,系统都会依据应用最近的使用时间和频率来给应用进行待机分组,然后根据应用所属群组限制应用可以访问的资源,目前总共有五类分组:活跃: 一般为正在使用或者在前台运行的应用,例如:应用启动一个 Activity;应用正在运行前台 Service;应用的同步适配器关联上了一个前台应用;用户点击了应用的一个通知; 系统不会对该类应用有任何的限制;工作集: 应用经常运行,但是当前未属于活跃状态就会被归属于工作集,该群组的应用在运行作业和触发闹钟方面会被施加轻度的限制;常用: 应用如果被定期使用,但不是每天的话就会被归到该工作群组。该群组的应用在运行作业和触发闹钟方面会被施加较强的限制,FCM 消息数量也会有相关限制;极少使用: 应用如果不经常使用就会被归到该工作群组,系统会对该群组应用运行作业、触发闹钟和接收高优先级别 FCM 的消息能力方面有严格的限制;从未使用: 安装但从未被使用过的应用会被归到该工作群组,该工作群组的应用会被施加极其严格的限制;更加详细的表述可以参考官网:App Standby Buckets(https://developer.android.com…,不同群组的限制的详细表现见:Power management restrictions(链接:https://developer.android.com…)。系统会动态的将手机里面的应用分配到这五类群组里面,也会根据需要变化应用群组,同时借助了机器学习来将一个应用放到更合适的群组里。目前应用可以通过 UsageStatsManager.getAppStandbyBucket() 函数来获取当前所属的应用群组,借助这个结果来更好的提升自己的打开频率,同时可以借助此来模拟处于不同群组能否正常工作。另外,位于低电耗模式白名单中的应用不适用基于应用待机群组的限制。3.2 省电模式改进Android 9 对省电模式又做了很多改进,开启省电模式之后会有如下限制:系统会更加积极的将应用置于待机模式,不管应用是否空闲;后台执行限制将适用于所有应用,无论他们的 targetApi 是多少;屏幕关闭时,位置服务可能被停用;后台应用没有网络访问权限;这里需要重点介绍一下后台执行限制,该限制于 Android O 版本引入,主要是为了优化 Android 在多应用多服务运行时,系统负载过大会杀死后台音乐播放等服务导致用户体验下降的问题,它默认只对 targetApi 大于等于 26 的应用生效。目前用户可以通过设置页面对任意应用施加后台执行限制,后台执行限制会对应用有两方面的影响:后台服务限制: 处于前台(可见、具有前台服务或者关联到前台应用)或临时白名单(处理高优先级 FCM、接收短信等广播或者执行通知的 PendingIntent)时,应用可以自由创建和运行前台与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务,但是超过该时间之后再通过 startService 去启动一个服务就会抛出 java.lang.IllegalStateException: Not allowed to start service Intent 的错误,解决办法是使用 startForegroundService 或者 JobIntentService;广播限制: 针对 Android O 和之上的应用无法继续在其清单中为隐式广播注册广播接收器。四、Apache HTTP client 相关类找不到将 compileSdkVersion 升级到 28 之后,如果在项目中用到了 Apache HTTP client 的相关类,就会抛出找不到这些类的错误。这是因为官方已经在 Android P 的启动类加载器中将其移除,如果仍然需要使用 Apache HTTP client,可以在 Manifest 文件中加入:<uses-library android:name=“org.apache.http.legacy” android:required=“false”/>或者也可以直接将 Apache HTTP client 的相关类打包进 APK 中。除上面两种适配方式外,QQ 音乐目前采用了另外一种方式。在音乐项目中,我们已经将使用 Apache HTTP client 的模块单独抽离到了一个 module 中,所以暂时只需要保持 module 中的 compileSdkVersion 在 28 以下即可正常编译运行。五、其余适配4.1 前台 Service在 Android P 中,如果 targeSdkVersion 升级到 28,使用前台 Service 必须要申请 FOREGROUND_SERVICE 权限,如果没有申请该权限,系统会抛出 SecurityException,该权限为普通权限,申请自动授予应用。4.2 隐私安全保护Build.SERIAL 标识修改:在 Android P 中,对隐私保护又做了更加严格的要求。在某些应用中为了识别手机的唯一性可能会用到 Build.SERIAL 这个标识,但这个标识在 Android P 中已经被设置成了 UNKNOWN,所以会直接导致该功能出现异常。多进程 webview 信息访问限制:在 Android P 中为了提升系统的安全性,用户无法在多进程的 webview 中共享数据目录,该目录下存储的是一些 cookies、Http 缓存和其他一些永久、临时的缓存。当下不少应用会把 webview 放在另一个进程中打开以避免内存泄漏,但是他们 cookies 的设置往往还是在主进程中,所以开发者需要仔细排查自己的应用是否有这么使用,webview 相关运行是否正常等。4.3 com.android.internal 包下某些类找不到升级到 28 之后,应用编译后抛出 com.android.internal 包下面有些类找不到的异常,经过查找发现这些类已经从 SDK 中移除。针对这种情况目前有两种处理办法:移除该类的调用逻辑;在应用中新建一个同名类,将被移除类的所有代码逻辑复制到新建类中(必要时可能需要将被移除类相关类同时拷贝一份到应用中),然后将应用中所有相关 import 引用直接修改成新建类的包名引用即可;下篇:Android P 实用新特性Android P 这次当然也有很多丰富的特性,总结了两个对于第三方应用开发者比较实用的特性。一、HEIF 图片格式支持HEIF(High Efficiency Image Format),高帧率图片格式,采用的是 HEVC 编码格式。苹果于 iOS11 版本开始支持该图片格式,而 Android 则是在 Android O MR1 版本开始支持 HEIF 静态图的软解码,在 P 版本上完全支持该格式的软编解码。HEIF 格式的压缩率是 JPEG 的 2.39 倍,同等大小质量的图片可节省 50% 的空间和网络传输流量,而且支持动图。HEIF 格式比起 GIF 格式来说有着更好的图片展示效果,所以 HEIF 格式图片的目标是用来代替 JPEG 成为主流的图片压缩格式。HEIF 格式图片的扩展名为 .heif 或者 .heic: HEIFWebPJPEG最大尺寸无上限16383x1638365535x65535编码HEVCVP8JPEG是否支持其他编码YESNONO支持音频/文字YESNONO支持多图片YESYESNO支持裁剪YESNONO支持透明YESYESNO支持缩略图YESNOYES分块加载YESNONO看上去很美好,但是目前还不是所有的 Android P 机型都会支持 HEIF 格式硬编解码,因为这需要特殊的硬件支持同时还需要缴纳一定的专利费,所以在编解码效率上就会有机型差异,同时 Android P 软编解码也只能支持静态 HEIF 格式图片。目前开发者可以通过版本来判断是否支持 HEIF 编解码,判断逻辑如下:fun supportHEIF() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P解码代码也很简单,支持将 HEIF 格式图片解码成 Bitmap 和 Drawable:@TargetApi(28)fun decodeHEIFDrawable(filePath: String): Drawable? { if (!supportHEIF()) { return null } var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath)) return ImageDecoder.decodeDrawable(source)}@RequiresApi(28)fun decodeHEIFBitmap(filePath: String): Bitmap? { if (!supportHEIF()) { return null } var source: ImageDecoder.Source = ImageDecoder.createSource(File(filePath)) return ImageDecoder.decodeBitmap(source)}另外扫描本地图片则继续使用 ContentResolver 即可,如果设备支持 HEIF 格式,系统会自动扫描上 HEIF 格式的图片:var cursor : Cursor = getContext().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null)但是这样还远远没有适配完成,第三方应用适配 HEIF 格式图片有一个很困难的地方是本地虽然可以识别解码 HEIF 格式的图片,但是如果某个用户将其设置为头像上传到后台,后台将其下发给其他不支持 HEIF 图片格式解码的手机,这些手机就肯定有展示问题。解决这个问题目前有两种思路:终端在上传之前将其转码成 JPEG 格式的图片,但是这样就根本没有充分利用到 HEIF 图片的高压缩率的优势;在到达后端之后,后端将其转码成 JPEG 图片,同时保存一份 HEIF 和 JEPG,到时候根据用户是否可以解码 HEIF 下发不同格式图片。该方案可以充分利用 HEIF 的优点,但是大大增加了后端存储空间和开发工作量。二、ImageDecoder上面已经介绍到了 ImageDecoder 在解码 HEIF 图片中的应用,但是实际它的功能完全不仅于此,在 Android P 中它可以完全替代 BitmapFactory 和 BitmapFactory.Options 相关类。ImageDocoder 类可以通过字节数据、文件和 URI 来解码一张图片。用法和之前一样,首先通过 createSource 方法创建一个图片文件的 ImageDecoder.Source 对象,然后调用 decodeDrawable 或者 decodeBitmap 方法传入之前的 ImageDecoder.Source 对象就能生成图片的 Drawable 或者 Bitmap 对象引用。ImageDecoder 支持 PNG、JPEG、WEBP、GIF 和 HEIF 多种格式图片的解码,另外解码 GIF 或者 WEBP 格式图片得到的是一个 AnimatedImageDrawable 对象,AnimatedImageDrawable 类的工作原理和 AnimatedVectorDrawable 类似,都是使用一个工作线程来解码,所以解码线程和显示线程互不干扰。AnimatedImageDrawable 用法也很简单:var drawable: Drawable = ImageDecoder.decodeDrawable(source);if (drawable is AnimatedImageDrawable){ image.setImageDrawable(drawable) drawable.start()}ImageDecoder 除了基础的解码功能之外,还有很多非常实用的方法,比如通过设置 OnHeaderDecodedListener 就可以在解析图片之前获取到图片的宽高等信息,同时还可以根据需要设置采样率:val listener = object : OnHeaderDecodedListener { fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: Source) { decoder.setTargetSampleSize(2) }}val drawable = ImageDecoder.decodeDrawable(source, listener)另外还可以通过 setPostProcessor 方法来添加一些自定义的效果,比如最常用的切圆角:var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src -> decoder.setPostProcessor { canvas -> val path = Path() path.setFillType(Path.FillType.INVERSE_EVEN_ODD) val width = canvas.getWidth() val height = canvas.getHeight() path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW) val paint = Paint() paint.setAntiAlias(true) paint.setColor(Color.TRANSPARENT) paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC)) canvas.drawPath(path, paint) PixelFormat.TRANSLUCENT }}非常便捷。用法远不仅于此,有了 Canvas 对象,开发者完全可以发挥想象去实现自己想要的炫酷效果。另外如果解码的图片不完整或者包含错误,一般情况下会抛出 DecodeException,但是如果这个时候通过 setOnPartialImageListener 函数传递一个 OnPartialImageListener 对象,并且在 onPartialImage 函数中返回 true,则图片就会只展示解析成功的一部分而不会抛出 DecodeException:var drawable = ImageDecoder.decodeDrawable(source) { decoder, info, src -> decoder.setOnPartialImageListener { e: ImageDecoder.DecodeException -> true }}引用https://developer.android.goo… https://mp.weixin.qq.com/s/03ospQEdY5HLdYqxEiDX1g https://blog.csdn.net/GenlanF… https://developer.android.com/about/versions/pie/power https://segmentfault.com/a/11…问答Android - 如何修复权限异常?相关阅读Android音频系统Android 基本常识Android全局异常处理 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

October 10, 2018 · 4 min · jiezi