一、简介
良好的应用架构设计目的是让应用更易于维护和扩展。
随着应用规模的扩大和业务需求的复杂化,在应用开发过程中,经常面临各种问题。模块间的耦合度高,一个模块的变动可能对其他模块产生影响。应用的扩展性差,新功能的添加往往需要对现有代码进行大量的修改。为了解决这些问题,开发者需要关注以下几个方面的架构设计:
- 分层架构设计:将应用划分为产品定制层、基础特性层和公共能力层,可以降低层间的依赖性。通过分层架构设计进一步明确了每层的职责和层间的交互机制,为开发者呈现了一个清晰且结构化的开发框架。
- 模块化设计:将应用分解为多个功能模块,每个模块负责执行特定的功能。通过模块化设计提高了代码的可理解性和可复用性,同时降低了系统各部分之间的耦合度。
二、分层架构设计
2、1 公共能力层
公共能力层提供了一套基础的开发组件和服务,包括公共UI组件、数据存储、网络和工具库,为应用开发提供了基础设施支持,公共能力层的各子目录将被编译成HAR包。
在开发过程中,可以在编译器新建commons目录,在commons目录下创建静态库。如果下图所示,commons目录下有公共UI组件库、数据存储库、网络库和工具库。
2、2 基础特性层
应用的各种功能位于基础特性层,基础特性层将功能进行模块化处理。例如,一个应用的底部导航栏中的每个选项都可能是一个独立的业务模块。
在基础特性层中,功能模块根据部署需求被分为两类。对于需要通过Ability承载的功能,可以设计为Feature类型的HAP。对于不需要通过Ability承载的功能,根据是否需要实现按需加载,可以选择设计为HAR模块或者HSP模块,编译后对应HAR包或者HSP包。
在开发过程中,可以在编译器新建features目录,在features目录下创建各个功能库。如果下图所示,features目录下有登录、我的、学习、发现、挑战等各种功能库。
2、3 产品定制层
产品定制层依赖公共能力层和基础特性层,各个子目录会被编译成一个Entry类型的HAP,作为应用的主入口。该层主要针对跨多种设备,为各种设备形态集成相应的功能和特性。产品定制层被划分为多个功能模块,每个功能模块都针对特定的设备或使用场景设计,并根据具体的产品需求进行功能及交互的定制开发。
一般来说,产品定制层只会有一个Entry类型的模块,这个模块支持手机、平板、折叠屏等设备。但是如果项目支持2in1、车机等设备,这类设备与手机、平板在功能和UX设计上差异较大,这时可以再创建一个Entry类型的模块。这样就编译出两个Entry类型的HAP,当上架应用市场,应用市场会根据设备类型部署到不同的设备上。
在开发过程中,可以在编译器新建products目录。如下图所示,产品定制层目录下的phone模块是Entry类型的HAP,支持手机、平板。
如下图所示,产品定制层目录下还有一个叫other的模块,它也是Entry类型的HAP,支持2in1和车机。
下面以HMOS世界App为例来看下简单的架构图。HMOS世界App不支持2in1设备,产品定制层只有一个Entry类型的HAP,产品定制层依赖基础特性层和公共能力层。基础特性层依赖公共能力层,提供了登录、我的、探索、活动、学习和发现等核心功能模块,为各设备提供通用的功能支持,基础特性层的模块都是编译成har。公共能力层则提供了一套基础的开发组件和服务,包括公共UI组件、数据存储、网络和工具库,为应用开发提供了基础设施支持。
三、模块化设计
大型软件一般有多个团队,各个团队的业务独立发展,这就需要业务模块化。模块化将应用程序拆分为多个功能模块,功能模块可以独立开发、编译和部署,也可以在不同的设备上灵活组合和调用,实现真正的多端协同。
3、1 Ability应用组件设计
在多设备的背景下,应用的形态不一定是传统移动设备上的单任务单窗口形式.在一些场景下,多任务多窗口的形态可以让用户获得更好的用户体验,提升使用效率。例如在传统移动设备上的单任务单窗口下,对于文档应用来说,从文档列表进入文档详情页。如果想打开新的文档,只能先退出文档详情页,然后打开新的文档。如果文档列表页和详情页分别在不同的UIAbility里面,那列表页就在一个窗口里面,详情页就在另一个窗口里面。此时就可以打开最近任务列表,在不关闭详情页的情况下,从最近任务列表进入到列表页。如果再将详情页所在的UIAbility的启动模式设置为多实例,那就可以在不关闭上一个详情页的情况下,再打开一个新的详情页,此时就可以编辑多个文档。
对于这种类似独立应用一样的任务,每个任务对应一个UIAbility组件实例,并且每个任务可以单独显示一个窗口。对用户而言,可以在同个应用不同任务间切换,就像是单独的应用一样。所以在进行功能设计时,需要考虑应用是否支持多任务多窗口。
3、2 模块化选型
模块化选型需要根据具体场景来选择是使用具有动态特性的HAP模块,还是使用HAR模块,还是使用HSP模块。
对于某些用户不常用的功能,可以将该功能做成按需加载模块。用户首次从应用市场安装时,仅会下载不包含按需加载模块的内容,需要使用到对应功能时,由用户选择使用时下载安装对应的功能模块。按需加载模块可以减少包体积,减少系统资源。
如果某个特性做成了按需加载模块,该模块可以设计为Feature类型的HAP或者HSP,HAP和HSP都可以实现按需加载,区别在于Feature类型的HAP可以包含Ability组件,结合前面的Ability应用组件设计以及是否按需加载,从整体上可以划分两个大的场景:
- 单HAP场景:如果只包含一个UIAbility(包括UIAbility多实例/指定实例),无需使用ExtensionAbility组件,优先采用单HAP(Entry类型的HAP)。
- 多HAP场景:要实现多任务承载多个UIAbility组件,以及需要使用ExtensionAbility组件实现拓展功能,可以采用多HAP(即一个Entry类型的HAP+多个Feature类型的HAP)。虽然一个hap可以有多个UIAbility,但为了避免不必要的资源加载,一个hap里面还是只能有一个UIAbility。
3、3 没有按需加载
如果没有按需加载,那就将功能模块全部设计为har。无额外的HSP,节省HSP的安装和加载成本。HAR在编译进HAP时,可以利用ArkTS的语言特性和编译器功能,做类型推断和编译优化。代码工程架构简单,后续演进较为灵活。
3、4 包含按需加载
如果包含按需加载,那就使用hsp作为按需加载的模块。多个HAP/HSP如果依赖于同一份HAR,则该HAR在应用内会被存在多份,导致包体积变大。HSP安装和加载均会有一些性能损失,过多的HSP可能会影响安装效率和启动性能。根据业务实际情况,在包体积与启动性能之间做好平衡。
3、4、1 包体积优先
对于包体积比较看重的,可以将公共依赖的模块封装在一个HSP模块壳中。hap_A和按需加载模块hsp_B都依赖于har_C,可以创建一个名为“common_hsp”的模块,将har_C打包到common_hsp中,对外暴露har_C接口,让hap_A和hsp_B依赖于common_hsp工程。common_hsp工程是无实际意义的,它仅是一个“模块壳”,是为了减少包体积而存在的。
3、4、2 性能优先
HSP包需要安装和加载,会有一些性能损耗。对于性能比较敏感的,直接依赖公共HAR包。这样公共HAR会存在多个,包体积变大。如果公共HAR包本身不大,那可以允许存在多份,这样包体积增加比较少,性能相对好一些。
四、总结
- 分层架构设计将应用划分为产品定制层、基础特性层和公共能力层,可以降低层间的依赖性。
- 模块化设计将应用分解为多个功能模块,每个模块负责执行特定的功能。
- 如果只包含一个UIAbility(包括UIAbility多实例/指定实例),无需使用ExtensionAbility组件,优先采用单HAP(Entry类型的HAP)。
- 要实现多任务承载多个UIAbility组件,以及需要使用ExtensionAbility组件实现拓展功能,可以采用多HAP(即一个Entry类型的HAP+多个Feature类型的HAP)。虽然一个hap可以有多个UIAbility,但为了避免不必要的资源加载,一个hap里面还是只能有一个UIAbility。
- 对于用户使用频率较少的模块,使用HSP做成按需加载模块。对于需要共享的模块,采用HAR包。在多HAP或者按需加载场景下,公共HAR包会存在多份,需要合理采用公共HSP模块壳,使包体积最小化。如果公共HAR包本身不大,那可以允许存在多份,这样包体积增加比较少,性能相对好一些。