我是一名iOS开发者,我是一名非计算机专业出生的程序员。
如果学习很幸苦,那就尝试无知的代价
上半年,部门同事对Harmony Next系统ALL IN进行App适配开发,产生了大量的开发工作量和人力支出,事后,也开始对工作量进行复盘分析和边界界定。针对这类新系统开发适配工作,框架也是全程参与,在这里,我自己产生了问题和思考,移动框架到底是什么?移动框架应该提供什么服务?
我是从V6版本(2017年)开始接触的移动框架,我接触到的移动框架给我的感觉,是一个融合了大量业务内容的套餐包。当然,我这并不是在批评,相反,我能感受到,移动框架从最初做App的几个人,扩展到一个几十人团队的过程。最初人少的时候,可能大家都把内容做在一起,方便测试,方便开发,快速上线,但团队扩大之后,项目变多,这样的架构带来了很多问题,例如:侵入式修改框架、无法改框架的逻辑需要框架介入、定制化困难、问题责任定位不清晰等,这些问题,即使放到框架架构优化后的现在,仍然存在。
尽管,后面的版本(V7、M8)在技术架构上对各个业务技术点进行了解耦和分离,但始终没有把业务内容向上剥离,这导致,后续的框架在维护过程中,都秉承着一个原则,业务通不通用?,通用就做到框架里!做个配置参数,开个接口,让上面进行配置和接入。这样的做法,虽然能快速解决项目的问题,但时间一长,框架的代码会越来越多,需要兼顾适配的场景也越来越多,学习成本也越来越高,在缺失大量测试场景的情况下,就会形成框架复杂度越大、稳定性越差的局面,版本升级推进困难。
因此,这迫使我们不得不思考,框架是否有责任承担那么多业务内容?各BG所需要的简单成品业务应该从哪里提供?框架到底应该提供什么服务?
在2022年,我意识到问题的严重性,想尝试新的架构,重点内容是让“框架保留核心部分,业务向上剥离”,同时,也参加了一些技术大会。我了解到Weex2.0技术,我对Weex有一点了解,但Weex2.0号称媲美Flutter的大饼给了我十足的信心做这件事,因此,我开始尝试“业务向上剥离”的架构,让Weex去承担业务侧的部分,并且在公司OA的首页场景上落地了门户内容,也在框架中实现了通讯录、登录页等模块的实现。这里我得夸一下Weex的稳定性,作为一个被抛弃了5年的项目,官方没人维护,两年运行下来,并没有大的问题,有的是因为Android系统升级、以及新手机带来的适配性问题,这简直比我们自己的框架都稳定。而且,运行的效果也符合预期,一次开发、多端渲染,成本一共投入1个业务侧的人力,App的运维内容动态化支持在线更新,体验和原生无差别。
至于为什么要使用Weex,而不是Flutter、RN这类技术,我之前写过一片篇文章详细讲过。和我司现状相结合的框架,最有可能铺开来使用的就是Weex,因为它的核心内容,就是将Vue开发的内容进行多端渲染,Vue的学习成本较低,配合公司的前端技术升级,有可行性方向。
就在最开心的时候,现实总会狠狠的给你一个耳光。团队内部人才流失以及Weex2.0(截止到今天,一直不发布)的长期不作为,给了我很大压力,如果要大量推广这样的架构,就需要对Weex核心技术有足够的了解,目前团队内部,对Weex 有所了解的人,可能就只有我了,而且,我自身也了解不够深,很明显,这是一次失败的尝试。
尽管这次失败,但我所坚持的“框架保留核心部分,业务向上剥离”的方向,不会改变,我仍然会继续探索这条技术路线。
在2023年末,我参与了新疆“新服办”的App项目建设,这个App的用户量很大,客户有一个硬性要求“我不希望App全疆更新,也能动态更新内容,而且体验要好”,也就是Weex的动态化能力。考虑到App的体量,我没敢在项目上直接使用Weex技术,在和技术团队沟通以及现场竞品分析之后,我们最终使用取巧的办法,“内置固定的多套原生模版、从小屏低代码上获取参数解析并渲染”,这样做确实实现了内容动态化,缺点是内置的模板为原生部分,更多模板需要原生单独开发才行。
我们推进了此模式的开发和落地,这过程中的重点工作,就是对接小屏低代码的参数以及数据渲染接口,最后通过一些技术手段让首页加载效果看起来还不错,目前App已经正式上线。
这个项目我认为成功的地方在于,App已经大规模上线的情况下,出现过几次需要调整接口参数的情况,我们并没有更新App,而是直接在小屏低代码上修改了入参,就解决了问题,这给我带来了震撼。因为在过去,我经常会遇到接口需要修改个参数,别人会问我能不能不更新App,我只能无奈地告诉对方必须更新。
通过这次经验,我分析得出,“新服办首页作为重要的业务部分”并没有在框架侧承担,而是交给了小屏低代码,而且交付人员能通过修改小屏低代码的参数来更新App上的Title文字,这个项目没有iOS,如果有IOS、Harmony,就更能体现出,小屏低代码一修改,三端同时更新的特性。那除了首页,登录、通讯录是否也可以?在此,我更坚定了“业务必须向上剥离”的想法。
我们先来看下框架中存在着的两个痛点:
涉及到的原生工作通常分为两点:
1、针对框架业务的定制化工作,有App启动流程、登录页、首页门户、通讯录、消息列表、个人中心等,在这些应用中,定制化工作通常分为“逻辑层定制化”和“表现层定制化”,在逻辑层中定制工作有,例如:业务需要在登录的时候加个参数,业务需要在启动的时候加个内容等,在表现层中定制,例如:业务需要增加微信/支付宝登录、业务需要更改门户列表展示和区域关联。上面所提到这些工作,都是非跨平台,全部都需要投入原生开发人员,并且工作量会直接*2或者更多(Android、iOS、Harmony)。
上面提到的应用,因为都是从框架中提供的,所以原生开发人员都是基于框架去做定制化开发,这样做工作量就巨大。
实际上,我观察到有小部分项目可能意识到了这一点问题,在个人中心的这块能力上,采用了H5开发模式,这样做节省人力,也不影响整体使用。通过这种形式,相当于把业务内容向上剥离,降低了和框架的耦合度,使业务部分更加独立。
2、三方SDK和API的投入开发,例如:业务需要集成分享组件,业务需要集成微信/支付宝登录,业务集成个音视频会议,这部分的开发工作量同样会*2或者更多。这部分工作是不可避免的,同时具有复用性,回到上文中的问题,框架到底应该提供什么服务?到这里,我认为框架的工作就应该持续的输出API和SDK为表现层服务,为什么会这样说,这衍生出了痛点2。
所以,在这个痛点中,我们需要实现的目标:
1、让原生人员不再需要持续投入业务内容的开发,让业务开发动态化,并沉淀为资产和编译可选项,支持一次开发、多端运行;
2、保留原生人员的API、SDK开发工作,并持续沉淀为资产和编译可选项;
3、表现层的实现方式优化,让“登录页”、“首页门户”、“通讯录”、“消息列表”、“个人中心”页面,变成可选资产,一次开发、多端渲染;
注入定制化的思想是有前团队Android同事提出,具体参考了Android AutoService 的实现逻辑,这一框架的核心思想是,通过定义接口,在框架业务逻辑中进行埋点拦截,在业务侧实现拦截的能力,完成定制化,Android团队基于AutoService研发了PluginService框架,将Android框架中的核心需要定制化的点都使用了PluginService,进行解耦,M8框架也是从这一刻开始诞生。当时,我还仅涉及iOS的内容开发,在我学习了AutoService框架后,也同时在iOS框架中提供 ElinkService框架,实现了和AutoService一样的能力。
可好景不长,人才流动,我承担了Android后半部分的开发责任,当时,业务侧的原生开发同事最多的问题,是框架model、view、adapter不能定制化,而我也是效仿前同事,把大部分的view、adapter通过pluginservice解耦,让外部可以定制化,我放大了这个可以定制化的口子,我为自己解决了Android的问题而沾沾自喜,殊不知,这才是灾难的开始。
通过注入思想,开放定制化接口后,后面的同事纷纷效仿这样的解决方案,随之迎面而来的问题:
1、业务开发不具备独立性,业务侧同事在做定制化需求的时候,都需要先阅读框架源码,还要确认框架能不能定制化?框架提供的口子满不满足需求?不能的话,得让框架解决下,能的话,得看下我做的逻辑和框架逻辑会不会冲突,这一来二去,变相拉高了开发成本。
2、问题难以快速界定边界,由于业务的定制化开发缺失了独立性,导致业务的逻辑上下文和框架逻辑上下文紧密关联,当出现问题后,很难快速界定是框架的问题还是业务的问题,维护成本变高。
3、业务逻辑塞进框架,在某些项目/产品开发完成后会有个复盘,要看下定制化的能力有没有必要收编到框架,这看似一句话,实则在破坏框架的稳定性,例如:
这些塞进框架的逻辑,进而造成框架的代码增大,同时配置参数变多造成学习成本增加,测试力度也会变大,如果覆盖不大,框架稳定性也会下降。
所以,在这个痛点中,我们需要实现几个目标:
1、保持业务开发人员开发的独立性,降低上手难度,让业务人员无需考虑框架的逻辑,也能快速开发业务逻辑;
2、从架构上改造,向上剥离业务,让“公众版/内部版逻辑”、“多维组织架构逻辑”等逻辑,沉淀为资产,支持在编译态选择;
我们能直接使用 uniapp 开发项目吗?我们能直接使用 Taro、FinClip等跨端引起开发我们的项目吗? 答案是肯定的,而且会更容易些。
这些跨端框架,均没有提供完整的App部分,提供了大量的API、SDK来协助开发者搭建App。举个例子,比如开发App的首页:
Uniapp、Taro、FinClip等跨端框架会提供 TabBar控件以及首页搭建的示例,而我们框架则是直接提供了完整的首页内容。很明显,三方在这一点上做的足够抽象,所以适应更多的开发场景。
说了那么多理论部分,那我们如何实现呢?
1、通过JS 虚拟引擎实现对框架能力的映射
这一点类似于把 Nodejs的运行环境搬到移动端运行(Android j2v8库、iOS javascriptCore库、HarmonyOS JSVM库),除了Nodejs的部分基础API,还需要提供 “APP的运行生命周期”、“线程操作”、“UI控件调用”、“组件调用”、“沙盒操作”等APP框架的能力,实现这一点后,开发人员就可以通过JS来写APP无UI的能力,众所周知,JS既可以是本地的,也可以云端的。
2、通过Vue3 引擎实现对框架基础UI控件的自定义渲染映射
在 Vue3 中有个能力是“createRenderer(自定义渲染器)”,Vue3会将需要渲染的部分从他提供的各个钩子中对外开放,并支持在非DOM环境中渲染。我们不需要实现浏览器,这很难做,也做不好,我们只需要对接自己的一套UI库,例如:“<fmui-tabar></fmui-tabar>”、“<fmui-layout></fmui-layout>”实现这类布局库难度更低,同时这种模式,支持渲染成“Android”、“iOS”、“HarmonyOS”、“微信/支付宝小程序”、“PC”、“Web”等多端,达到一次开发、多端渲染的目标;
3、架构改造
结合第1、2点,我们从传统的“半原生开发模式”改造成“VueApp开发模式”,在新的架构中,我们剥离了传统“表现层内容:登录、首页、通讯录等内容”,通过Vue3+JSRuntime的新形式,把UI开发、逻辑开发从传统框架开发中剥离了出来,让框架剔除业务,并且达到一次开发、多端运行的目标,同时还支持热更新。
同时在架构上,我们需要实现双线程渲染模式,JS运行在逻辑线程,UI运行在主线程,线程间运行隔离,通过消息中间件交互通信。这样一来,通过有效的逻辑运行管理,让逻辑运行的错误不会导致主线程崩溃,逻辑运行也不会阻塞主线程导致界面卡死。
通过新的架构模式,传统原生开发人员会大幅降低原生业务的开发,仅保留了 API 、SDK的开发工作,保持开发人员的独立性,让原生人员更纯粹地为表现层服务,达到了降低原生人员成本的目标。
为什么说 API、SDK 的开发工作,能保持开发人员的独立性,试想一下,一个开发人员在开发组件化API的时候,是否需要考虑框架的逻辑关联性,组件模式开发相对独立,所以开发人员更容易高效的开发业务。同时,组件开发模式下,问题也更容易定位。
4、深度结合小屏低代码平台,让App端到端
结合1、2、3点,我们做到了完全让 Vue3+JSRuntime来实现表现层和逻辑层,这套内容可以是本地的,也可以是小屏低代码上的,通过小屏低代码可视化编辑页面、以及拖拉拽关联逻辑关系形成App的预览内容,再利用集成平台一键打包,App的使用端就可以快速获取到构建的内容。同时结合低代码平台,可以实现资产的持续沉淀和编译态选择,达成目标。
回到的最初的问题,框架到底应该提供什么服务?我认为,框架应该是提供协助开发者搭建应用的API、SDK,而不是直接提供完整的App,完整的App应该是基于框架模式下提供的可选范例,辅助开发者搭建自己的业务,而各BG所需要的成品范例应该具备多样性,这样下来,框架和业务的职责就很明确,也会减少相应的成本。
当然,说了那么多,也只是逞口舌之快,这套内容,实现起来并不容易,目前仅停留在理论基础上,我会积极的在物理基础上实现它,后续有进展,也会带给大家新的分享,也欢迎大家讨论并纠正我的观点。