本文共 5135 字,大约阅读时间需要 17 分钟。
随着业务的快速扩张,原本小作坊式的单个工程的开发模式越来越难以满足实际需求。早在两年多以前,51信用卡管家就向下沉淀出了单独的公用基础库,以及一些通用的功能组件和个别独立的业务被拆分成 SDK,形成了一套中型项目、多人并行的开发模式,也为未来组件化拆分做了准备。
随着单应用内业务需求的增加、开发人员数量的增多以及基础库数量的膨胀,原有的开发模式逐渐暴露出一系列问题:
这些问题推动我们更进一步升级开发架构,选择了组件化或插件化的方向。
近两年,插件化框架层出不穷,各大厂都推出了自家开源的插件化框架。51信用卡选择了 Native 动态化与性能兼顾的插件化方案。动态性主要体现在两方面:一是动态热修复,二是动态下发业务插件。虽然插件化在性能上有所欠缺,但完全切换到 Native 的方式显得有些推倒重车。通过与业界的交流,对于动态下发业务插件的实际需求并不多,业务更新主要依然依靠 App 升级。技术方案没有最优解,选择适合自己的才是最好的。
插件化也存在一些弊端,如不可避免的 hook framework、修改 aapt、包装 Gradle Plugin、代理组件等不常规操作,日常维护成本较高,稳定性、兼容性、新版本适配等问题都需要考虑进去。经过内部讨论,公司决定不急于采用插件化,边走边看,先把业务组件拆分出来再说。
随着 Android P 的发布,限制 hook framework 后,插件化逐渐式微,维护成本越来越高,成本收益比逐渐降低,最终被弃坑不用。
除了插件化外,动态化方案近两年比较火的是以 ReactNative、Weex 为代表的大前端方向。51信用卡结合实际情况,最终选择拥抱大前端,Weex 作为动态化方案,以 Native 为主,Hybrid 离线化方案为辅。Weex 逐步迭代的架构开发模式。
Weex 的基础建设和前端同学的合作历经大半年时间,目前已经稳定应用于51信用卡各个 App 上,Weex 作为动态化页面的首选方案,已经完成了线上数百个页面的开发需求。配合离线化方案,各项性能指标也都达到了要求。
代码解耦与代码隔离,最有效的方案是工程隔离。审视我们最初的方案,每个 SDK 对应单独的仓库,通过 Maven 依赖,通过工程分离隔离代码,这种方案没有问题,只不过需要进一步拓展,各个业务模块也需要独立主工程,拆分成独立的业务组件。
同时,划分清楚代码边界,控制依赖关系,梳理清楚层次结构,形成如图的架构。整体架构上提供三种容器:
Native 容器对应上图中各个层级的定义:
组件化拆分的核心诉求是解耦合,提高内聚性。应该从诉求出发,在沿用当下开发模式的同时,逐渐进行组件化拆分。
通过工程隔离进而进行组件化拆分后,基本可以解决上述提到的问题:
页面路由:采用分总分结构,在编译时生成汇总代码,调用时进行管理分发。
模块间调用:采用 ServiceLoader 模式,组件工程目录如下:
<组件工程目录> ├── api ├── imp └── app 组件工程目录>
每个组件内一般声明三个 module:
业务组件之间依赖 api 库的服务接口,imp 库作为实现动态查找。版本发布时,同时发布 api 和 imp 两个库,并且保证 api 和 imp 具有相同版本号。
消息总线:基于实现的跨三端(Native、Hybrid、Weex)事件管理分发组件。
数据总线:支持按模块进行存取,每个业务组件都可以定义自有 tag,避免字段冲突问题。
无论是早期的 PhoneGap、Cordova,还是近年来比较火的 ReactNative、Weex,到最近两年崛起的 Flutter,跨平台混合开发一直深受开发者青睐。其跨平台和动态化是原生开发所不具备的特性。
Native 和 H5 混合开发一般是比较常见的混合开发模式,H5 开发效率高、迭代快速、不依赖 App 发版。51信用卡众多 App 产品中,有很多页面都是用 H5 来开发,嵌入原生 App 中使用 webview 进行加载显示。
早期 H5 容器在各个 App 中分别独立实现,没有统一的架构和规范,导致对 H5 的支持效率较低,PG 方法(来源于 PhoneGap)的开发、测试和维护都相当混乱,重复性工作太多。Native 层提供一套通用性强、功能丰富、稳定性高的 H5 容器对业务的高速发展至关重要。
由于 H5 不具备直接调用原生方法,所以原生壳要提供一套通用的通信方式,一般为 JsBridge。在 Android 端,实现 JsBridge 通信的通道一般有以下几种:
插件管理也是采用分总分结构,在各个业务组件中分别注册,编译是通过 APT 生成汇总代码,运行时进行插件汇总,最后调用通过 PluginManager 查找分发逻辑。
插件注册代码如下:
@JsPlugin(name = TestPlugin.PLUGIN_NAME, loadOnInit = false, version = 1)public class TestPlugin extends EnNiuJsPlugin { public static final String PLUGIN_NAME = "TestPlugin"; @Override public String getPluginName() { return PLUGIN_NAME; } ... @Override public boolean onExecute(String args) { doSomething(); callbackContext.callback(...); return true; }} 插件的生命周期如下:
Hybrid 混合开发的一大劣势就是性能比较差,打开页面较慢,特别是在弱网情况下。由于 51信用卡业务大部分都是静态资源请求,参考业界做法,我们实现了动态下发离线包的方式来提升 H5 页面打开速度。
在 Hybrid 已有配套基础上,51信用卡选择了 Weex 作为跨平台方案,经过一年的踩坑填坑过程,目前已经有 20+ 个项目、数百个 Weex 页面在线上稳定运行。Weex 方案趋于成熟,已经作为 51信用卡端内首选业务方案。
由于 Hybrid 良好的面向接口编程特性,在进行 Weex 基础建设过程中,很方便地将已有的插件集成进来,并且共享已沉淀的配套设施。
public class ENBridgeModule extends WXModule { @JSMethod public synchronized void send(String method, String args, JSCallback jsCallback) { ... weexWebView = weexEngine.getWeexVirtualWebView(); EnNiuJsBridge enNiuJsBridge = weexWebView.getEnNiuJsBridge(); enNiuJsBridge.notify(pg); }} 注册 Weex 的 Module,并且每个 Weex Engine 中会新建出一个虚拟 webview,用于桥接 JsBridge 进而调用 PluginManager 进行插件逻辑分发。
工程化本质上是为了提高研发效率。51信用卡自研的大风车管理平台,用于 App 管理、持续集成、类库管理、发版管理等,围绕客户端研发上下游流程,建立统一的管理入口。
51信用卡目前有 100 多个 Android 类库,每个类库对应一个独立的 Gitlab 仓库。过多的独立组件及独立仓库,管理起来有些麻烦。依托于大风车平台,所有类库的名称、最新版本及标签类型都会展示在列表页,标签类型对应组件化架构的层次结构,包括:基础组件、单业务功能组件、多业务功能组件。
类库之间是仓库隔离,所以它们的依赖关系是 Maven 依赖,所有类库的 aar 包都需要发布到内部 Maven 服务器上,上传工作由 PublishMavenPlugin 完成。
为了提高效率,我们创建了组件库的模板工程,只需要 clone 下来模板仓库,然后修改一些代码即可开发需求代码。新建类库时,填入必要的参数,一键就可以创建出可用的类库项目。
好的架构不是设计出来的,而是演进出来的。本文简单阐述了 51信用卡 Android 架构演进的一些实践经验。我们坚信技术方案没有最优解,重要的是要选择适合自己的。脱离所处环境和问题本身谈技术方案,都将不能得到适合自身的开发架构。同时,我们也应当吸取和借鉴业界优秀的架构和设计理念,并将其根据自身适用场景加以改造,在理论和实践中逐渐交替探索演进。
当然,我们目前所使用的架构依然存在一些问题,比如组件拆分不完全、主工程业务仍然很多、CodeReview 机制不健全、代码扫描不够严格、一些组件库没有严格按照 api 工程来改造、一些老的组件依然没有 api module 等等问题。我们也应该看到,正是因为这些实际的问题在推动我们进行技术改造,架构升级。同时,我们也要审视行业内大的方向,紧跟技术趋势,主动拥抱变化,毕竟技术世界唯一不变的,便是变化。
转载地址:http://pezb.baihongyu.com/