一种清晰, 便于扩展 android 项目架构方案
作者将功能与项目的基础架构的代码分离开。整个工程分为core
和features
两个包,core
下面放的是整个工程的基础代码。将业务进行分类组织在features
目录下,这样便将代码按照功能模块的维度组织起来。某个功能(如 login)相关的代码都会在features.login
下面。其好处:
代码的结构更加清晰,后面维护方便
后期如果业务起来了,想要做组件化,因为代码都在同一个目录下面,抽取出去也更加方便。
细化
再回到 Google 推荐的架构图,我们再将 Google 推荐的这个架构整合进项目中,于是,项目的结构便如下所示:
com.xxxx.app
core
整个 app 内所有模块共享,或公共的配置data
全局的数据访问,如用户信息,app 配置等model
AppConfigInfo
UserInfo
datasource
impl
AppConfigRemoteDataSource
AppConfigLocalDataSource
UserRemoteDataSource
UserLocalDataSource
IUserRemoteDataSource
IUserLocalDataSource
IAppConfigRemoteDataSource
IAppConfigLocalDataSource
AppConfigRepository
UserRepository
util
view
base
BaseActivity
BaseFragment
BaseViewModel
hybrid
features
下面是按业务来分组login
ui
xxxActivity
xxxViewModelFactory
xxxViewModel
可直接调用core/data
里的UserRepository
product
data
考虑到同一个模块中的数据层面可以共用,故放在外层model
ProductListInfo
ProductDetailInfo
datasource
IProductLocalDataSource
IProductRemoteDataSource
impl
ProductLocalDataSourceImpl
ProductRemoteDataSourceImpl
ProductRepository
ui
detail
view
放置自定义 ViewProductLabelView
ProductDetailActivity
ProductDetailViewModel
list
rendermodel
view
放置自定义 View...
ProductListViewModel
(和ProductDetailViewModel
都调用ProductRepository
来获取数据)....
xxxViewModelFactory
如果业务简单,ViewModelFactory
可以写在这里, detail 模块和 list 可以共用personal
data
ui
xxxViewModelFactory
xxxViewModel
此处可能需要获取用户信息,则直接调用core/data
里的UserRepository
以下对上面的结构进行说明
core 目录
服务于整个工程,一些基础代码。关于系统配置,用户信息等数据的操作全部放在这个里面。
data
目录:DAO 相关的操作,model
包下面的是 pojo, 数据的获取或持久化全部由xxxRepository
去调用datasource
包下面的数据源去实现util
目录:放置一些公共的工具类view
目录: 放置公共的自定义 view,这些自定义 view 脱离具体的业务联系,能够在各业务模块使用base
目录:用来放置一些基础的组件,如BaseActivity
,BaseFragment
,BaseViewModel
features 目录
按业务模块来划分不同的包,组织在该目录下。以下对代码的组织做一些说明
login 模块
在上面的目录中,由于涉及到用户登录相关的 DAO 操作都已经放到core
目录下了, 假设可以满足要求,那么login
模块下只有 UI 相关文件和ViewModel
,xxxViewModelFactory
product 模块
这个模块是用来模拟某个模块下多个页面的场景。以商品列表页和详情页为例。由于 DAO 的操作可能会有重叠的场景,这里将它们的数据操作写在一起。UI 层面按功能再分为list
, detail
两个包。list
, detail
两个页面的 ViewModel 可以采用同一个 ViewModelFactory 来创建。
rendermodel
包:这个目录下有一个需要单独说明一下,当我们从服务器拿到数据了如ProductInfo
之后,将数据显示在页面上,我们显示在页面上的一些信息很有可能是需要根据ProductInfo
的数据进行加工的。为此,我们定义一个ProductInfoRenderModel.java
用来承载只需要显示在页面上的数据。ProductInfoRenderModel.java
则放在rendermodel
包下面。
personal 模块
personal
模块中也会涉及到用户相关的信息,这也就是为什么一开始设计把用户相关信息的 dao 操作放到core
目录下。personal
模块下的xxViewModel
如果要查用户相关的信息,可以直接调用core
下面的UserRepository
至此,整个项目的大体架构便梳理完成了。采用这种方案将代码以功能模块进行划分,方便后期的维护。既使后续某个模块中进行了技术方案的改革,也能保证其影响的粒度最小。当然这里面主要是为了说明项目的主要结构,在实际项目中,除了这些,我们还会有adapter
, 自己写的各种工具等等,这个就根据实际情况再自己分包了。接下来我们看一下涉及到的相关技术栈
技术栈
在这种项目架构中我们主要用到的技术栈有Jetpack
中的ViewModel
, ViewModelFactory
, LiveData
, ROOM
,下面简单介绍一下这几种技术以及它们之间的整合。当然用于网络请求相关的我们可以用Okhttp
, retrofit
,此处就不介绍。
ViewModel
Jetpack
组件中提供了ViewModel
可以方便的将数据,对象与组件的生命周期绑定起来,方便进行组件间的数据共享,如一个activity
中多fragment
的情况。同时它可以有效的从架构层面上进行解藕,和 mvp 架构模式相比,可以大大减少接口/方法的个数。以登录为例,用户调用登录接口时需要调用 presenter.login 方法,login 成功后调用 view.loginSuccess
方法。而采用ViewMode
后,用户在登录时调用viewModel.login
方法,登录成功后,更新ViewModel
中的LiveData
,然后在调用处观察LiveData
做相应的行为就可以。
ViewModelProvider.Factory
用来创建ViewModel
,ViewModel
不可以自己创建,必须要借助ViewModelProvider.Factory
来创建。在创建时通常为 ViewModel 指定数据仓库,如下:
public class LoginViewModelFactory implements ViewModelProvider.Factory {@NonNull@Overridepublic <T extends ViewModel> T create(@NonNull Class<T> modelClass) {if (modelClass.isAssignableFrom(LoginViewModel.class)) {return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));} else {throw new IllegalArgumentException("Unknown ViewModel class");
}}}
LiveData
在数据发生变化时,需要通知给页面。通常可以采用接口的方工去做,但如果要观察的数据很多,就需要定义大量的接口,代码会十分冗余。为此, Google 提供了LiveData
组件,它是一个可被观察的数据容器类,将数据包装起来,使数据成为被观察者,当该数据发生变化时,观察者能获得通知。
ViewModel
是用来存储数据,LiveData
的作用是在ViewModel
发生变化时通知页面。因此, LiveData
通常放在ViewModel
中使用,用于包装ViewModel
中那些需要被外界观察的数据。
我们来结合具体的例子(登录)看这三者的配合使用
示例
UI 层面(LoginActivity)
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityLoginBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());...viewModel = new ViewModelProvider(this, new LoginViewModelFactory()).get(LoginViewModel.class);registerObserver();bindClickEvent();
}
private void registerObserver() {viewModel.getLoginResult().observe(this, new Observer<LoginResult>() {@Overridepublic void onChanged(LoginResult loginResult) {if (loginResult.success) {//登录成功}}});//出现异常 viewModel.exceptionLiveData.observe(this, new Observer<AppException>() {@Overridepublic void onChanged(AppException e) {Toast.makeText(LoginActivity.this, e.getBizMsg(), Toast.LENGTH_SHORT).show();}});
}
private void bindClickEvent() {binding.btnLogin.setOnClickListener(v-> {viewModel.login(phone, pwd, verifyCode, verifyKey);});}
采用
ViewBinding
进行资源的绑定绑定
ViewModel
评论