一种清晰, 便于扩展 android 项目架构方案,kotlin 编程
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>() {@Overridep
ublic 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
ViewModel
监听数据的变化利用
ViewModel
去访问数据
ViewModel
public class LoginViewModel extends BaseViewModel {...private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
private LoginRepository loginRepository;
LoginViewModel(LoginRepository loginRepository) {this.loginRepository = loginRepository;}
public LiveData<LoginResult> getLoginResult() {return loginResult;}
public void login(String username, String password, String verifyCode, String verifyKey) {loginRepository.login(username, password, verifyCode, verifyKey, new Gson2ModelCallback<NetResult<LoginNetResponse>>() {
@Overridepublic void onSuccess(NetResult<LoginNetResponse> result) {//成功 if ("SUCCESS".equals(result.getCode())) {
loginResult.postValue(new LoginResult(result.getData()));}}
@Overridepublic void onFail(Throwable e) {handleException(e);}});}}其功能是调用 repository 相关的业务方法去拿数据,拿到数据后通过postValue
方法再传递给ViewModel
Repository
Repository
层便会调用具体的数据源去加载数据,这里就不详情说明。Repository
在创建ViewModel
的时候就已经注入了。
评论