Android 需要哪些架构手段
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情
架构
前置知识
- 已入门 Android
- 了解过一些设计原则
前言
关于 Android 的架构问题,想必大家都听说过 MVC、MVP 和 MVVM ,且当下又出现了更新的 MVI。诸如此类的这些架构,都是我们日常所在使用的。这是我们代码设计中,对于业务开发会经常要用到的。但是除却业务开发呢?我们需要将系统分为更多层的时候需要怎么办呢?要了解哪些东西呢?
我们不妨将视野放宽,看看 Android 本身的架构,或者是其他客户端手段的架构,亦或是前端后端的架构,学习他们的架构手段,我们也能收获很多设计的思想。所以本文将会简单的讲一下一些值得我们学习的架构手段。
架构的作用
我们经常说一个架构帮助我们解耦,让我们开发得更顺畅。那么架构的作用,其出现的根本原因只是帮助我们解耦吗?
当然,架构能帮助我们进行解耦这是其出现的一个重要原因;但事实上更重要的是,它能够帮助我们解决特定领域在不同阶段发送的业务问题。它是伴随着一整个软件的生命周期而存在的,继而也要对应的解决每个生命周期中出现的问题。
我们软件周期包括:新生期、稳定期和老年期。
在新生期,架构要适应业务的不断膨胀问题;而在稳定期,架构则要让业务不断地重构和优化;最后到了老年期,架构要做的是如何让项目老得更慢,不要让过分臃肿的代码影响业务的正常运作。
我们可以有理想的架构,一开始就设定好来解决这些个问题,但实际上,理想总归是理想,我们要接受现实的骨感。事实上,我们很难存在一个理想的架构,因为业务是不断在变化的,变化才是软件唯一的不变点,所以架构也只能随着变化而变化,不断去解决出现的问题。
Android 的架构设计
我们可以看一下 Android 的架构设计,看看它的设计是如何解决出现的问题的。
下面的图大家都很熟悉了,这里不做详细介绍,只做简单的罗列。
- APP 层:业务代码层,在这一层实现自己的业务
- FrameWork 层:应用框架层,开发者对系统的 UI 编写,对数据的使用和调用接口,使用的都是这一层的 API
- Binder IPC:由于 Android 是有进程隔离的,所以使用 Binder 来跨进程高效通信
- 系统服务层:提供窗口、音视频、相机等能力。往下是对硬件能力的封装,往上是可以通过 Binder 暴露给其他人
- HAL 硬件抽象层:其屏蔽底层驱动的差异,使得系统服务层可以快速匹配到不同的硬件设备
- LINUX 内核层:CPU 、内存等重要的驱动服务在这一层实现。
由此,我们可以看出,Android 内部架构的设计是很有逻辑性的,底层为高层服务,越高层使用的越是定制化的内容。
不同领域的架构设计
由下图,我们可以看出在不同的端侧,其使用的语言、框架和平台都是不同的。但是在他们身上是可以找出共同点的,这些共同点是经过长期积累,抽象出来的方法论,是很值得我们学习的点。
公共的点包括:
- 编程思想:OOP、IOP、IOC 等
- 问题分解:按业务和技术分解
- 领域建模:接口设计、DSL 等
- 服务治理:模块化、组件化、容器化等
- 流程机制:敏捷开发模型、软件测试手段等
- 架构标准:公约文档,数据监测等
所以说,对于架构的设计,我们应当知晓它是不断随着需求和出现的问题不断变更优化的,且不同的架构虽有差异,但是差异之外的共同点很值得我们去学习。
值得学习的架构手段
了解了架构的作用之后,我们需要学习一些架构手段,以便解决软件生命周期中出现的各种问题。
设计模式
要做好架构设计,首先逃不掉需要学习的点及时设计模式,而设计模式又是基于设计原则来进行拓展的。学习设计模式,其最重要的就是学习其设计思想了。设计模式 - Yj家的孺子牛的专栏
而就如前文中讲过的 volatile
和 synchronize
,synchronize
中瘦锁容易被打断变成胖锁;而使用了 volatile
关键字之后就不会被打断。所以,我们认为使用了 volatile
关键字的单例模式更优。
这些对设计模式的深入使用的理解,是很容易影响到我们对架构设计的好坏的。
下文中,DoubleCheck 设计的单例模式就会相对更优些。
//LazySafe
public class SingleTonTest {
private static SingleTonTest instance;
private SingleTonTest() {
}
public static synchronized SingleTonTest getInstance() {
if (instance == null) {
instance = new SingleTonTest();
}
return instance;
}
}
//DoubleCheck
public class SingleTonTest {
private static volatile SingleTonTest singleton;
private SingleTonTest() {
}
public static SingleTonTest getInstance() {
if (singleton == null) {
synchronized (SingleTonTest.class) {
if (singleton == null) {
singleton = new SingleTonTest();
}
}
}
return singleton;
}
}
MVX
这里说的 MVX ,是 MVC、MVP、MVVM 的统称,我们在这里暂不介绍 MVI ,大家可自行了解。
MVC
MVC:视图+控制器+模型。其中视图(View)接收到用户的操作,继而调用控制器(Controller)来控制模型,模型(Model)处理数据后就会更新UI。
其中,View 指的是 XML 布局,Controller 由 Activity 承载,Model 是提供接口给 Controller 层处理数据的类,同时 Model 层在处理好数据后会通知更新 UI。
这个架构中,很好的将数据和控制器分离,让各自的分工和操作更加的简便。但是 Model 层和 View 层没有很好的解耦,以及 Controller 层与 View 层之间却还是有耦合的,当视图的控制逻辑变多的时候,控制层就会变得很臃肿。
MVP
为解决 MVC 中出现的问题,出现了 MVP 架构,其解决了 C 层和 V 层的耦合问题。
在 MVP 中,将控制器(Controller)换成显示器(Presenter),让 P 层充当 V 层和 M 层的中间人,让 P 层来控制这些交互逻辑。这样子,Activity 就不会很臃肿,数据和视图也完全解耦了。但是同样的,其控制回路会变得很庞大,我们就需要加重去维护 P 层了。
而通常情况下,我们会使用接口类,利用抽象来做 V 层和 P 层的连接。但同时,也会使得类的数量变得很多。
ps:笔者有写过关于封装 MVP 的文章,此处附上:带你封装MVP架构(上)|青训营笔记 - 掘金 (juejin.cn)
MVVM
为解决 MVP 中控制回路复杂的问题,MVVM 诞生了。
MVVM 相对于 MVP 的不同就是 P 层变化为了 VM 层,这个 VM 层可以在 V 层中建立一个模型,将 V 层中的点击事件或者是数据更新等与 VM 层绑定。这个架构是通过提前约定好视图和数据的绑定关系,让控制逻辑自动化和简化了,这个看不见的逻辑是由 Android 内部来做的,所以我们不会像 P 层一样需要定义很多的接口,也不会因为控制回路变得很臃肿。
MVVM 架构很适合于解决如下问题:
- 需要页面监听功能
- 界面元素多且需要和数据绑定的复杂页面
优缺点总结
MVC -> MVP -> MVVM 这三个架构的演进是不断的解决前者的问题而提出的,其复杂度也是在不断提升的。
我们在日常使用的时候,业务简单的时候使用简单的架构就好,而复杂的业务则要使用复杂的架构来实现,将他们各自应用到适合他们的地方即可。
架构模型 | 优点 | 缺点 |
---|---|---|
MVC | 1. 模块职责划分明确。主要为 M、V、C 三个层次,利与代码的维护 | 1. View 和 Controller 容易膨胀 2. View 与 Model 没有完全分离 |
MVP | 1. View 和 Model 完全分离,可以修改视图而不会影响模型,交互逻辑全部放置于 Presenter 层 2. P 层和 V 层的交互是通过接口来进行的,方便了单元测试 |
1. 页面逻辑复杂的话,对应的接口会变多,维护成本会增大 |
MVVM | 1. VM 层与 V 层解耦更加彻底,VM 层只负责处理和提供数据 2. VM 层只包括数据和业务逻辑、没有 UI ,方便单元测试 |
1. 数据绑定后使得程序难以调试,因为绑定过程是黑盒话的,数据是自动更新到 UI 的。 |
AOP
我们经常听到的有 OOP ,所谓 OOP 指的是面向对象编程,将对象作为第一公民,以对象出发来编程,然后再定义该对象的属性。
而 AOP,则为面向切面编程,它会抽离出各种对象的共同属性,以这个共同属性作为一个切面,再将该切面定义到各种的类中 。
这类切面编程的方法就是使用注解或者字节码注入的方式把功能进行植入。例如 @Aspect
注解就是 AOP 编程方式。
IOC
IOC 即为控制反转,在依赖反转原则|设计原则一文中,我有做讲解,此处直接搬运其解释过来:
控制反转 缩写为IOC(Inversion Of Control),简单解释其意思为:把控制权反转过来
具体是把控制权从谁手中反转过来呢?从程序员手中。那反转到谁那里呢,反转到代码框架那里。
这个概念的本意是,程序员在编写代码的时候,编写了设置了整个代码流程,此时控制权在程序员手中。
当对代码抽象框架化,代码拓展性提高,且最终由框架实现代码流程。程序员只输入少量代码启动代码流程,这个就是符合控制反转思想。
框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。
但是控制反转只是一种设计思想,具体的实现方式各式各样。前文 开闭原则 的demo中,其设计也是符合控制反转原则的,最终是由框架自行驱动程序。
对应的,LiveData
也是一种控制反转思想的变体,与 MVP
的由 P层
驱动 View
层一样,使用数据驱动 UI。
上面的这个说的这个框架,我们多数时候使用的是 接口 。这个接口掌管控制权的同时,也让其更加解耦,更加容易变更。
Android 中使用 IOC 的典型例子:
像 ActivityManager
、PackageManager
和 WindowManager
这些的服务,是注册到 ServiceManager 中的;而开发者需要使用这些服务的话,是从 ServiceManager 中获取的,并未直接与该服务打交道。
小结
上述讲解了我们 Android 开发者需要学习的一些架构手段,这些架构手段都是在不断的解决我们不同阶段所遇到的问题,其共同目标是为了实现 高内聚低耦合 ,使用合适的手段解决合适的问题,是我们学习的初衷。