Android MVVM+Clean架构简介

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

一、ViewModel

ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。
在发生配置改变时 Activity 和 Fragment 会被销毁重建,它们内部的临时性数据(不是通过 Intent 传入的数据)就会丢失. 如果把这些临时数据放到 ViewModel 中, 则可以避免数据的丢失。当然也可以利用 onSaveInstanceState 来保留临时数据,但是如果临时数据的量较大,onSaveInstanceState 由于涉及了跨进程通信,较大的数据量会造成 marshallingunmashlling 消耗较大。而利用 ViewModel 其实是没有跨进程通信的消耗。但是它没有 onSaveInstanceState 提供的 Activity 被回收之后的数据恢复功能:在 Activity 位于后台时系统会在内存不足时将其回收,当 Activity 再次回到前台时,系统会把 onSaveInstanceState 中保存的数据通过 onRestoreInstanceState 和 onCreate 里的 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)的生命周期状态的变化。

java 复制代码
class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

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

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

java 复制代码
public class MyObserver implements DefaultLifecycleObserver {
    @Override
    public void onResume(LifecycleOwner owner) {
        connect()
    }
    @Override
    public void onPause(LifecycleOwner owner) {
        disconnect()
    }
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

四、DataBinding

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

java 复制代码
@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        dataBinding = DataBindingUtil.setContentView(this, layout());
        dataBinding.setVariable(getBindingVariable(), getViewModel());
        dataBinding.setLifecycleOwner(this);
        liveDataObserve();
    }

布局中修改如下:

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
    <import type="com.example.mvvm.ui.main.MainViewModel" />
        <variable
            name="viewModel"
            type="MainViewModel" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.google.android.material.button.MaterialButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="50sp"
            android:onClick="@{()->viewModel.getUsers()}"
            android:text="json获取"/>
        <com.google.android.material.button.MaterialButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="50sp"
            android:onClick="@{()->viewModel.saveUsers()}"
            android:text="数据库保存"/>
        <com.google.android.material.button.MaterialButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="50sp"
            android:onClick="@{()->viewModel.getUserDetail()}"
            android:text="数据库获取"/>
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_user"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>
</layout>

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

java 复制代码
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则是:

XML 复制代码
    <ImageView
            android:layout_width="90dp"
            android:layout_height="90dp"
            android:scaleType="centerCrop"
            app:imageUrl="@{url}"/>

五、什么是Clean Architecture

Clean Code 的作者 Robert C. Martin (Uncle Bob),写了一本书 Clean Architecture(《代码简洁之道》),从而提出了这个架构 (Clean Architecture)。

代码分为三个独立的层:

  1. Presentation Layer
  2. Domain Layer
  3. Data Layer

Presentation Layer

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

java 复制代码
@Inject
    public 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));
    }

Domain Layer

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

java 复制代码
public class GetUserListCase extends UseCase<GetUserListCase.Params,GetUserListCase.UsersEntity > {


    private UserRepository userRepository;

    @Inject
    public GetUserListCase(UserRepository userRepository) {
        super();
        this.userRepository = userRepository;
    }


    @Override
    protected Observable<UsersEntity> buildUseCaseObservable(GetUserListCase.Params params) {
        return userRepository.getUsers(params.fileName);
    }
      public static final class UsersEntity implements IResponseValue {
        private List<UserViewEntity> users;

        public List<UserViewEntity> getUsers() {
            return users;
        }

        public void setUsers(List<UserViewEntity> 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对应的方法。

Data Layer

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

java 复制代码
public class UserDataRepository implements IUserRepository {
    private final Context context;
    @Inject
    Gson gson;
    @Inject
    UserDataMapper userDataMapper;
    @Inject
    AppDbHelper appDbHelper;
    @Inject
    UserDataRepository(@ApplicationContext Context context) {
        this.context = context;
    }

    @Override
    public Observable<List<UserViewEntity>> getUsers(String fileName) {
        return LocalJsonResolutionUtils.getJson(context, fileName).map(it ->
                userDataMapper.transformList(gson.fromJson(it, new TypeToken<List<UserModel>>() {
                }.getType())));
    }

    @Override
    public Observable<UserViewEntity> getUserById(int userId) {
        return appDbHelper.getUserDetail(userId).map(this.userDataMapper::transform);
    }

    @Override
    public Observable<Void> saveUserList(String fileName) {
        return LocalJsonResolutionUtils.getJson(context, fileName).map(it -> {
        appDbHelper.saveUserList(gson.fromJson(it, new               TypeToken<List<UserModel>>() {}.getType()));
        return null;
        });
    }
}

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

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

六、总结Clean架构

优点:

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

缺点:

  • 它添加了很多额外的类,因此对于低复杂度的项目来说并不理想。
相关推荐
姑苏风20 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k4 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小104 小时前
JavaWeb项目-----博客系统
android
风和先行5 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.5 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰6 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶6 小时前
Android——网络请求
android
干一行,爱一行6 小时前
android camera data -> surface 显示
android
断墨先生7 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员8 小时前
PHP常量
android·ide·android studio