原文:https://medium.com/@ravisahani28/designing-the-android-application-architecture-50bbdf34840d
设计Android应用程序架构:
多年来,Android开发已经发生来很多的变化,技术栈也变得越来越完善,开发者希望能编写出整洁,松耦合,可维护和易于理解的高质量的代码,这边文章将简要介绍如何使用Clean结构去设计Android应用程序。
这个架构由Robert C. Martin(鲍勃叔叔)于2012年在 clean code blog.
提出。
为什么要采用更Clean的方法?
- 通过职责分配将不同层中的代码分隔开来,这样代码维护会变得更容易;
- 高层次的抽象;
- 代码的松耦合;
-
单元测试更容易;
“整洁的代码看起来总是像是有人在乎的。” -迈克尔·费瑟斯(Michael Feathers)
架构:
强大的基础架构对于应用程序的扩展和更快的开发需求至关重要。我所就职的Rebel公司恰好有一个全新的项目需要开发,用来合并我们没办法在现有项目中添加的功能。这篇文章说明来我们决定重新开发一个新的应用而不在程序上继续加功能的缘由。
因此,在开始项目之前,我们作为对程序架构设计做了一些思考。什么是好的架构?能让我们团队中的开发者发挥出最大生产力的才是好架构。
在制定研发计划的时候,我们考虑了每一个模块,工具,功能,库,架构层之间的通信(即数据层,界面层,业务逻辑层)等。来保证团队开发能不浪费时间在底层代码上,避免一些简单的错误,也让我们能更容易的实行代码审查,更专注于业务功能的实现。
我们采用Clean Architecture的概念来解决我们的问题,并且在我们的项目中引入来Android的组件,以保证我们的结构的尽可能的松耦合。
网络请求:
在Android应用中,网络请求是不可避免的,比如加载一张网络上的图片,或者是与你的服务进行通信,都是要进行网络连接的。
很多时候,我们在做开发的是,后台服务都是并行开发的,这就意味着,我们在做某个功能的时候,后台接口可能还没开发完成。而你能做的,就是与后台开发人员定义好服务请求的数据结构。这样就不会影响开发进度。
那Android开发人员怎么样才能做到不与后台交互的情况下对功能进行测试和开发呢?
这里我们用Retrofit和OKHttp来解决这个问题,OKHttp 2.2中引入来InterCeptor,它能在你的http请求中插入钩子,以便你可以拦截所有请求和响应。这是我们决定在项目中使用Retrofit进行联网的原因之一。
下面是我总结的Retrofit的优点:
- Retrofit是一个基于OKHttp封装的类型安全的RESTful 网络请求适配器,它能让我们的网络请求方法简化。
- Retrofit针对URL操作,网络请求,加载,缓存和多线程和同步都做了优化。
- 它支持同步和异步调用,简化来开发的复杂度。
- Retrofit 几乎能用于所有网络请求接口。
- Retrofit通过拦截能使用GSON或者Jackson对Api请求和响应进行解析,来简化开发复杂度。
- Retrofit使用注解来减少样板代码。开发人员能将更多的精力用在功能的实现上。
图像加载和缓存:
对于图像的加载和缓存,我们有Glide,Picasso,Volley,NetworkImage,Fresco等第三方库。在我们的程序中,我们需要播放Gif和加载缓存图像,所以我们决定使用Glide来进行图像的加载。
下面是我总结的Glide的优点:
- 它提供了更快,更高效,更易用的方法在Android中加载图像。
- Glide支持获取,解码,显示视频截图,Gif和图像缓存
- Glide里有灵活的的API,让开发人员能使用任何网络请求库进行图片的网络请求加载。
- Glide注重于解决如何平滑,快速的在滚动的图像列表中加载图像。
- 它还能防止程序因为图片加载引起的OOM异常。
离线数据存储:
因为业务需要,我们需要将数据保存在本地,Android框架中自带有SQLite数据库。
但是使用原生的数据库来存储数据,我们需要用ContentProvider来编写SQL语句,这样不仅增加来很多样板代码,而且降低来代码的可重用性。
随着代码量的不断增加和团队规模的扩大,我们开发一个功能所需要的时间也会越来越多,且代码的维护成本也会增大。
- 因此,我们提出一个解决方案,就是用ORM数据库框架来存储数据。ORM能减少开发人员在处理底层数据库花费的时间,而且通过将Java对象映射到数据库表(ORM,“对象/关系映射”),它可以让你不需要编写太多的SQL语句。这样,你就可以用简单的面对对象的API来对数据进行增删改查了。
- 下面是我们列出的几款ORM数据库框架:
a) Room
b) SugarORM
c) Realm
d) GreenDao etc. - 最后我们决定使用Room,因为Google在最新的Android架构组件中引入了它。它可以减少样板代码,并且能轻松的将SQLite数据表的转换称Java对象。
- Room还提供了SQLite预计的编译时检查,并且它可以与Rxjava结合使用。我们将在下一篇文章中深入研究Room数据库框架。
架构模式(MVP,MVC,MVVM):
在项目开始的时候没有选择适合的架构模式是一个错误的做法。
这将会导致以下问题:
a) 这会让你无法跟踪一个复杂类里面的逻辑。
b) 接手你的项目的开发人员会很难维护你的代码和在代码中增加新功能。
c) 调试会变得异常的困难,因为一个类里面包含了太多的功能。
d) 无法很好的执行单元测试。
因此。我们想要构建一个高质量的项目,就必须有一个合适的项目架构。
在Android中,最著名的三层架构模式有以下几种:
- MVC: Model-View-Controller
- MVP: Model-View-Presenter
- MVVM: Model-View-ViewModel
在这几种架构中,我们最后决定使用MVVM架构模式来构建我们的项目。
Google引入了Android architecture components来支持MVVM架构模式:
a) Room, LiveData, ViewModel, Paging, WorkManager and Navigation.
b) 通过它们能解决复杂的架构问题,例如管理Android生命周期(LiveData/ViewModel), or 或者处理后台任务 (WorkManager).
c) 它是基于观察者模式,能减少样板代码的编写,加快构建效率,让我们能专注与业务功能的实现。
关注点分离:
在设计代码架构的适合,要遵循的最重要的原则,就是关注点分离。开发人员经常将所有代码写在一个类里面。只是一个很常见的错误,例如Android中的Activity或者Fragment。
在程序结构里,构建分层应用的关键在于关注点分离。在传统的N层架构体系中,层有可能包括了数据访问层,业务逻辑层,和界面展示层。
除了通过分层的方式来分离逻辑之外,还可以根据程序功能来分离关注点,将程序的功能模块化。
依照这个原则是项目更加容易维护和扩展,也能让我们不会那么容易的违反 Don’t Repeat Yourself principle这个基本原则。
“[关注分离],即使不能完全实现,但这也是我所知道但唯一能有效整理思想的可用技术。”---埃格·迪克斯特拉(Edsger W. Dijkstra)
单一数据来源:
在信息系统设计和理论中,Single Source of Truth (SSOT)
是一种构造信息模型和关联图式的时间,这样每一个数据元素只会存储一次。
在应用程序中使用单一的数据来源,我们确保我们的数据始终来自一个来源,即我们的数据库。在这个模型中,数据库是唯一的可信数据来源,而程序的其他模块则通过repository或者Managers来获取数据(这里repository或者Managers是获取数据的唯一连接)。
Repository可以有以下实例:
- Remote(远程数据,例如通过api进行网络请求获取数据)
- Database(数据库,例如通过数据库存储和获取数据)
- Cache or Shared Preferences(缓存或者Shared Preferences)
这里Repository根据关注分离原则属于N层架构中的数据层。
我们使用了LiveData,Retrofit,LiveDataCallAdapter和Room来使我们的应用遵循单一数据来源原则。下面是示例代码,在接下来的文章中,我们介绍其设计原理和实现:
fun getProducts():LiveData<DataResponse<Product>>{
return object :NetworkDbBindingResource<Product, Product> () {
/* Store the Products in the Room Database.*/
override fun storeApiResult(@NonNull item:Product){
ProductDbManager.addProducts(item)
}
/* Return Product list data from the database.
i.e Single Source of Truth to access data from one source */
@NonNull
override fun loadFromDb():LiveData<Product> {
return ProductDbManager.getProductListLiveData()
}
/* If true then it will fetch the data from the server
using Api call.
If false then it will return the data from the database. */
override fun shouldAlwaysFetchData():Boolean {
return true or
false
}
/* If condition shouldAlwaysFetchData() = false then it
will fetch from the database
If condition shouldAlwaysFetchData() = true then it will
fetch the data from the server using api call and
store the results in the Database. */
override fun shouldRefreshData(@Nullable data:Product ?):
Boolean {
return true or false
}
/* Retrofit Network Call to fetch data from the server. */
@NonNull
override fun createCall():LiveData<DataResponse<Product>> {
return ProductApiManager.getProducts()
}
}.resultLiveData
}
SOLID原则:
SOLID是五个单词的首字母缩写词,用来定义五个面向对象的基本设计原则,SOLID原则可以确保我们的代码体系结构实现可伸缩性,灵活性和功能扩展。
- 单一责任原则
- 开放封闭原则
- 里氏替换原则
- 接口隔离原则
- 依赖倒置原则
现在我们已经设计好了我们的项目代码体系结构,现在是时候动手来实现它来,接下来的博客中,我们将讨论如何在我们的框架中实现上面内容。