Android MVVM+Clean架构简介

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

一、ViewModel

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

在发生配置改变时 Activity 和 Fragment 会被销毁重建,它们内部的临时性数据(不是通过 Intent 传入的数据)就会丢失. 如果把这些临时数据放到 ViewModel 中, 则可以避免数据的丢失。当然也可以利用 onSaveInstanceState 来保留临时数据,但是如果临时数据的量较大,onSaveInstanceState 由于涉及了跨进程通信,较大的数据量会造成 marshalling 和 unmashlling 消耗较大。而利用 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)的生命周期状态的变化。

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() 之前结束,这使得组件留存的时间比所需的时间要长。

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

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

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

布局中修改如下:



    
    
        
    
    
        
        
        
        
    

BindingAdapter :绑定适配器,是 Jetpack DataBinding 中用来扩展布局 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则是:

    

五、什么是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向下传递。

@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接口。

public class GetUserListCase extends UseCase {


    private UserRepository userRepository;

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


    @Override
    protected 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对应的方法。

Data Layer

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

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> getUsers(String fileName) {
        return LocalJsonResolutionUtils.getJson(context, fileName).map(it ->
                userDataMapper.transformList(gson.fromJson(it, new TypeToken>() {
                }.getType())));
    }

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

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

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

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

六、总结Clean架构

优点:

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

缺点:

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

你可能感兴趣的:(Android进阶,android)