本文主要介绍Android开发中MVVM Clean架构。

一、ViewModel

ViewModel类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在activity之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

在发生配置改变时Activity和Fragment会被销毁重建,它们内部的临时性数据(不是通过Intent传入的数据)就会丢失.如果把这些临时数据放到ViewModel中,则可以避免数据的丢失。当然也可以利用onSaveInstanceState来保留临时数据,但是如果临时数据的量较大,onSaveInstanceState由于涉及了跨进程通信,较大的数据量会造成marshallingunmashlling消耗较大。而利用ViewModel其实是没有跨进程通信的消耗。但是它没有onSaveInstanceState提供的Activity被回收之后的数据恢复功能:在Activity位于后台时系统会在内存不足时将其回收,当Activity再次回到前台时,系统会把onSaveInstanceState中保存的数据通过onRestoreInstanceStateonCreate里的savedInstanceState参数传递给Activity。

二、LiveData

LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData具有生命周期感知能力,意指它遵循其他应用组件(如activity、fragment或service)的生命周期。这种感知能力可确保LiveData仅更新处于活跃生命周期状态的应用组件观察者。

使用LiveData具有以下优势:

·确保界面符合数据状态

LiveData遵循观察者模式。当底层数据发生变化时,LiveData会通知Observer对象。您可以整合代码以在这些Observer对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。

·不会发生内存泄漏

观察者会绑定到Lifecycle对象,并在其关联的生命周期遭到销毁后进行自我清理。

·不会因Activity停止而导致崩溃

如果观察者的生命周期处于非活跃状态(如返回堆栈中的activity),它便不会接收任何LiveData事件。

不再需要手动处理生命周期

界面组件只是观察相关数据,不会停止或恢复观察。LiveData将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

·数据始终保持最新状态

如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的Activity会在返回前台后立即接收最新的数据。

·适当的配置更改

如果由于配置更改(如设备旋转)而重新创建了Activity或Fragment,它会立即接收最新的可用数据。

三、Lifecycle

生命周期感知型组件可执行操作来响应另一个组件(如Activity和Fragment)的生命周期状态的变化。

class MyActivity extends AppCompatActivity {private MyLocationListener myLocationListener;public void onCreate(...) {myLocationListener = new MyLocationListener(this, location -> {// update UI});}@Overridepublic void onStart() {super.onStart();Util.checkUserStatus(result -> {// what if this callback is invoked AFTER activity is stopped?if (result) {myLocationListener.start();}});}@Overridepublic void onStop() {super.onStop();myLocationListener.stop();}}

在我们需要执行长时间运行的操作(如onStart()中的某种配置检查)时尤其如此。这可能会导致出现一种竞态条件,在这种条件下,onStop()方法会在onStart()之前结束,这使得组件留存的时间比所需的时间要长。

public class MyObserver implements DefaultLifecycleObserver {@Overridepublic void onResume(LifecycleOwner owner) {connect()}@Overridepublic void onPause(LifecycleOwner owner) {disconnect()}}myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

四、DataBinding

使用声明性格式将布局中的界面组件绑定到应用中的数据源。

@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);dataBinding = DataBindingUtil.setContentView(this, layout());dataBinding.setVariable(getBindingVariable(), getViewModel());dataBinding.setLifecycleOwner(this);liveDataObserve();}

布局中修改如下:

viewModel.getUsers()}"android:text="json获取"/>viewModel.saveUsers()}"android:text="数据库保存"/>viewModel.getUserDetail()}"android:text="数据库获取"/>

BindingAdapter:绑定适配器,是JetpackDataBinding中用来扩展布局xml属性行为的注解,允许你针对布局xml中的一个或多个属性进行绑定行为扩展,这个属性可以是自定义属性,也可以是原生属性。

public class ImageHelper {@BindingAdapter({"imageUrl"})public static void loadImage(ImageView imageView,String url){Glide.with(imageView.getContext()).load(url).error(R.mipmap.ff).placeholder(R.mipmap.ff).into(imageView);}

对应布局中的view则是:

五、什么是CleanArchitecture

CleanCode的作者RobertC.Martin(UncleBob),写了一本书CleanArchitecture(《代码简洁之道》),从而提出了这个架构(CleanArchitecture)。

代码分为三个独立的层:

  1. PresentationLayer
  2. DomainLayer
  3. DataLayer

PresentationLayer

这部分主要包括我们的Activity,Fragment,ViewModel。Activity与Domain层通讯执行操作,不直接和Data层通讯。用户所有的操作(如点击事件等)在Presentation层通过ViewModel向下传递。

@Injectpublic MainViewModel(GetUserListCase getUserListCase, GetUserDetailCase getUserDetailCase, SaveUserListCase saveUserListCase) {this.getUserListCase = getUserListCase;this.getUserDetailCase = getUserDetailCase;this.saveUserListCase = saveUserListCase;}public void getUsers() {this.getUserListCase.execute(new UserListObserver(), GetUserListCase.Params.forFileName("users.json"));}public void saveUsers() {this.saveUserListCase.execute(new SaveUserListObserver(), SaveUserListCase.Params.forFileName("users.json"));}public void getUserDetail() {this.getUserDetailCase.execute(new UserDetailsObserver(), GetUserDetailCase.Params.forUser(1));}

DomainLayer

这一层主要包含我们所有的UseCase。我们定义一个抽象类,所有的case都要继承这个UseCase,以规范UseCase的使用。基类UseCase会对入参和出参进行规范,入参需要实现IRequestValues接口,出参要实现IResponseValue接口。

public class GetUserListCase extends UseCase {private UserRepository userRepository;@Injectpublic GetUserListCase(UserRepository userRepository) {super();this.userRepository = userRepository;}@Overrideprotected Observable buildUseCaseObservable(GetUserListCase.Params params) {return userRepository.getUsers(params.fileName);}public static final class UsersEntity implements IResponseValue {private List users;public List getUsers() {return users;}public void setUsers(List users) {this.users = users;}}public static final class Params implements IRequestValues {private final String fileName;private Params(String fileName) {this.fileName = fileName;}}

UseCase是ViewModel和Repository之间的媒介,当我们需要新增功能时,只需要新增一个UseCase。UseCase面向接口,调用需要使用的Repository接口类中的对应方法。UseCase中通过Hitl依赖注入,调用对应的Repository接口类的实现类。

单个UseCase只应该具备单个的功能,如:获取用户的列表,并且对应User的Repository中单个方法(获取用户的列表)如果需要获取单个用户的详情等场景,需要增加UseCase(如GetUserDetailCase),并调用Repository对应的方法。

DataLayer

这里具有Domain层可以使用的所有存储库。此层向外部类公开数据源API。

public class UserDataRepository implements IUserRepository {private final Context context;@InjectGson gson;@InjectUserDataMapper userDataMapper;@InjectAppDbHelper appDbHelper;@InjectUserDataRepository(@ApplicationContext Context context) {this.context = context;}@Overridepublic Observable<List> getUsers(String fileName) {return LocalJsonResolutionUtils.getJson(context, fileName).map(it ->userDataMapper.transformList(gson.fromJson(it, new TypeToken<List>() {}.getType())));}@Overridepublic Observable getUserById(int userId) {return appDbHelper.getUserDetail(userId).map(this.userDataMapper::transform);}@Overridepublic Observable saveUserList(String fileName) {return LocalJsonResolutionUtils.getJson(context, fileName).map(it -> {appDbHelper.saveUserList(gson.fromJson(it, new TypeToken<List>() {}.getType()));return null;});}}

UserDataRepository可以区分从网络或者本地获取数据,当获取的数据发生改变时,只需要修改此处的代码,并进行相应的转换即可,而不需要改变上层代码。

UserDataRepositoryIUserRepository接口的具体实现,后续根据业务需要,可以在IUserRepository中新增接口。在业务变动较大的情况下,可以新增IUserRepository的接口实现类,然后通过Hilt对外提供注入,这样只需要修改data层,上层的调用可以基本保持不变。

六、总结Clean架构

优点:

  • 代码比使用普通MVVM更容易测试。
  • 代码进一步解耦(最大的优势)。
  • 包结构更易于导航。
  • 该项目甚至更容易维护。
  • 可以更快地添加新功能。

缺点:

  • 它添加了很多额外的类,因此对于低复杂度的项目来说并不理想。