经过不懈的努力,最终完成了一整套App混编方案,让H5模块秒开加载,并且与纯原生部分,Flutter部分有机结合。
我们探索了一套混编开发方案,它既保证了纯原生的丝滑体验,又能让web业务模块无延迟更新迭代。它是以纯原生App作为web容器,web应用模块作为动态加载插件的方式来运作。用前者来保证体验,用后者来确保迭代。
主要解决3个关键问题:
webView加载一个url时,常规流程如下:
移动端webView加载H5页面的过程大概分为以下3个阶段:
第一个阶段,webView尚未创建,而webView初始化的过程其实是加载浏览器内核,尤其是在打开web首页时,以及打开web子页面时感受较为明显,前者加载内核会肉眼可见的慢。但是解决方式也很简单,在打开页面之前,提前初始化webView,让浏览器内核提前加载,可以一定程度上提升首次打开的速度。
第三个阶段,web加载状态。此时要想在视觉上让用户感觉加载很快,就要求H5的渲染顺序上做出优化,开始加载时先展示出页面框架以及loading动画,有多个接口时,让接口返回结果单独与组件对应渲染,而不是所有接口都完毕之后再渲染。
在本方案中,App本身是一个webView容器,容器上运行的是web应用,这两者的关系类似“插件化”的概念,webView容器作为宿主,web应用作为插件。而插件必须依托一个托管平台。宿主在特定时机与托管平台进行通信更新插件,再去运行插件代码。
解决即时更新,需要App端和托管平台合作完成。
web应用,打包成zip包,任何你想要离线的静态资源都可以通过打包的形式放到zip包内,我们称为“离线包”,每一个离线包都代表一个web应用。
托管平台就类似一个离线包的“池子”,在复杂的业务场景中,多个业务方上传各自的离线包进入“池子”,并附带上每个离线包的配置信息,就是上图中的manifest.json文件。
manifest.json是每个web应用的身份配置信息,内容展示如下:
上图中规定了一个web应用的一些关键元素。
entryUrl:web应用入口地址,在webView启动应用时将会去加载这个url。
version:应用版本号,提供web应用的版本管理。
updateStrategy:更新策略。一个web应用可以支持多个安卓或iOS应用,上面支持配置原生应用的包名,版本号(支持^2.1.0这种写法的模糊匹配)。
当App去通过网络请求拉取离线包时,需要传入平台标识(Android/iOS),AppId(App包名),AppVersion(App版本号),接口从离线包的“池子”中根据所有离线包的updateStrategy进行检索,找出当前App能够使用的最新离线包列表。
在App沙盒内,建立两个目录,一个temp目录,一个active目录,分别表示离线包暂存区,以及生效区。能够被匹配到的只有生效区的离线包,暂存区只做暂时存储,在合适的时机会被移动到生效区。(类似git的管理方式)
每次接口返回的离线包列表,我们会按照下面的流程进行更新替换:
上图中有一个多线程执行,每个线程的逻辑如下:
环节解释:
通过以上流程,保证在宿主app上打开web应用时,都能使用业务方发布的最新代码。
上文提到,要加快web页面的打开速度,其中一个重要环节是静态资源离线化。
资源的匹配,本质上是网络资源路径和本地资源路径的映射关系。基本的匹配规则如下:
它指向的就是离线包demo内的/1.0.1/index.html文件。
纯Web应用与纯原生应用之间的差距,除了启动速度之外,就是运行中的体验,差距明显。为了抹平这一差距,我们提供丰富的原生能力让H5去调用,使得app使用体验最大程度接近纯原生应用。
WebView的多媒体功能,比如视频播放,web前端的体验是完全和原生无法比拟的。像类似这种功能,使用我们App的提供的原生功能去实现,要比纯前端播放视频的开发要简单,兼容新更强,并且前端的工作量更低,甚至可以轻易做到前端无法做到的效果,比如京东App上能看到的可拖动视频悬浮窗,而web开发处理视频层级覆盖很麻烦。
类似的功能还有,音乐播放,视频剪辑,图片截取等。诸如此类纯web不好做的功能,都能通过原生能力来弥补,然后由web和原生之间的通信来进行数据传递。
原生能力主要分为以下几个方面:
必须说明的是,要扩展原生能力,或者修复原生能力的bug,我们都需要对Web容器进行版本更新,所以接入方有少量的维护成本。
此方案的核心目的是优化web应用的体验,除了以上3个核心特性之外,还有一些额外特性,给与更多拓展可能。
如果完整来看待一个完整的SDK项目,它的结构如下:
flutterBoost的核心作用:
它的架构图如下:
目前我们实现了web应用贴近原生的体验,但是仍有提升空间。
一个方案从诞生到完善,能做的还有很多,万里长征,始于足下。