优雅地处理MVVM中各层次关系

Posted 周文凯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了优雅地处理MVVM中各层次关系相关的知识,希望对你有一定的参考价值。

转载请标明出处:
https://blog.csdn.net/xuehuayous/article/details/84255731
本文出自:【Kevin.zhou的博客】

前言:相信大家对MVVM架构都有过一定的了解,如果不太了解的朋友可以看下我之前写的《Android中MVVM是什么?》。整体分为View、ViewModel、Model三层,View层处理用户交互,ViewModel层进行业务处理;Model层进行数据处理。那么如何进行优雅地处理他们之间的关系呢?

分层关系

首先来看下它们之间的关系,如下图所示:

简图如下:

通过以上两图我们可以看出:

  1. View持有ViewModel引用;
  2. ViewModel持有Model引用;
  3. View与ViewModel存在一对多关系;
  4. ViewModel与Model存在一对多关系。

原生实现

布局binding

使用dataBinding技术,可以轻松实现数据的双向绑定,不太了解的同学可以先看下我之前写的《认识Android中的双向绑定》,使用双向绑定可以轻松实现很多原生比较麻烦实现的效果。

Activity

Activity中初始化binding:

public class MainActivity extends AppCompatActivity 
    private MainActivityBinding mBinding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        mBinding = MainActivityBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
    

Fragment

Fragment中初始化binding:

public class MainFragment extends Fragment 
    private MainFragmentBinding mBinding;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
        mBinding = MainFragmentBinding.inflate(inflater, container, false);
        return mBinding.getRoot();
    

ViewModel初始化

Activity

Activity中初始化ViewModel:

public class MainActivity extends AppCompatActivity 
    private MainViewModel mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
    

Fragment

Ftagment中初始化ViewModel:

public class MainFragment extends Fragment 
    private MainViewModel mViewModel;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
         mViewModel = ViewModelProviders.of(getActivity()).get(MainViewModel.class);
    

这里需要说明的是使用的ViewModelProviders.of(getActivity())当然也可以使用ViewModelProviders.of(this)如果使用getActivity()则在Activity中的FragmentActivity使用相同的ViewModel

ViewModel生命周期

有朋友不仅要问,ViewModel生命周期是什么鬼,由于我们定义View层仅仅处理用户交互,向用户展示、响应用户的事件,ViewModel层进行业务逻辑处理,需要和Activity生命周期绑定的业务处理就必须移到ViewModel层进行处理,所以ViewModel层也需要和View层相同的生命周期。
还好Google在android架构组件中已经想到了这个问题,可以通过添加LifecycleObserver的方式方便实现。

由于使用JavaKotlin都实现过,这里都贴出了,还没有入门Kotlin的朋友以后也可以尝试使用下。

编写ViewModelObserver

Java实现

public class ViewModelObserver<T extends BaseViewModel> implements LifecycleObserver 
    private final T mViewModel;

    public ViewModelObserver(@NonNull T viewModel) 
        this.mViewModel = viewModel;
    

    @OnLifecycleEvent(Event.ON_CREATE)
    public void onCreate() 
        this.mViewModel.onCreate();
    

    @OnLifecycleEvent(Event.ON_START)
    public void onStart() 
        this.mViewModel.onStart();
    

    @OnLifecycleEvent(Event.ON_RESUME)
    public void onResume() 
        this.mViewModel.onResume();
    

    @OnLifecycleEvent(Event.ON_PAUSE)
    public void onPause() 
        this.mViewModel.onPause();
    

    @OnLifecycleEvent(Event.ON_STOP)
    public void onStop() 
        this.mViewModel.onStop();
    

    @OnLifecycleEvent(Event.ON_DESTROY)
    public void onDestroy() 
        this.mViewModel.onDestroy();
    

Kotlin实现

class ViewModelObserver<T : BaseViewModel>(private val viewModel: T) : LifecycleObserver 

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate() 
        viewModel.onCreate()
    

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart() 
        viewModel.onStart()
    

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() 
        viewModel.onResume()
    

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() 
        viewModel.onPause()
    

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop() 
        viewModel.onStop()
    

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() 
        viewModel.onDestroy()
    


绑定周期

Java实现

public class MainActivity extends AppCompatActivity 
    private MainViewModel mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        getLifecycle().addObserver(new ViewModelObserver(mViewModel));
    

Kotlin实现

class MainActivity : AppCompatActivity() 
    private lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        lifecycle.addObserver(ViewModelObserver(viewModel))
    

Model初始化

Model层主要做数据的存储与获取,有可能是本地的,也有可能是网络的,总之在ViewModel层使用数据的时候是不需要知道数据到底是存储在什么位置的。这一部分可以通过我之前写的《一步步封装Retrofit + RxJava2》来理解下。

Repository编写

通过《一步步封装Retrofit + RxJava2》,我们知道DataRepository(数据仓库)是Model层ViewModel层交互的外交官。

public class MainDataRepository 
    private CompositeDisposable mSubscriptions;

    public MainDataRepository(CompositeDisposable subscriptions) 
        this.mSubscriptions = subscriptions;
    


Repository初始化

public class MainViewModel extends BaseViewModel 
    MainDataRepository mDataRepository;
    
    public MainViewModel() 
        mDataRepository = new MainDataRepository(getSubscriptions());
    

实现分析

通过以上原生实现可以发现,维护各层之间关系的基本都是模板代码,写起来繁琐,但是又没有技术含量,能不能让编辑器去自动生成呢?

我们想实现如下:

  1. DataRepository使用@Repository标注这是一个数据仓库;
    @Repository
    public class MainDataRepository 
    
    
  2. ViewModel上使用@ViewModel标注这是一个ViewModel,如果DataRepository的字段上使用了@Autowired标注,则自动实例化对象;
    @ViewModel
    public class MainViewModel extends BaseViewModel 
        @Autowired
        MainDataRepository mDataRepository;
    
    
  3. ActivityFragment中对bindingViewModel进行@Autowired标注,也自动实例化。
    public class MainActivity extends BaseActivity 
        @Autowired
        private MainActivityBinding mBinding;
        @Autowired
        private MainViewModel mViewModel;
    
    

这样是不是有对MVVM没有那么抗拒了,是不是有朋友已经拍手叫好,抓紧去实现吧,别逼逼了。

那么怎么实现呢?首先想到的是apt,我们熟识的ButterKnifeDaggerEventBus等都是通过这种方式实现的,思考良久我们会发现,使用apt很难优雅地实现。那能不能动态修改字节码文件呢?这是一个很好的方式,只是实现成本比较大,那不要紧,实现成本大,实现之后就一劳永逸了。考察了一圈动态修改字节码的框架,最终选择了ASM,为啥是ASM以及怎么实现的,应该是一个特别长的篇幅。这里直接把最终代码奉上。

Android-MVVMEasy

如何使用

引入依赖

  1. 项目build.gradle添加dependencies

    dependencies 
       // ... ...
       classpath 'com.kevin:mvvm-plugin:latest.release'
    
    
  2. 模块build.gradle添加组件

    apply plugin: 'mvvm'
    
  3. 模块build.gradle开启dataBinding

    dataBinding 
        enabled true
    
    
  4. 模块build.gradle引入架构组件及rx依赖

    dependencies 
        // ... ...
        implementation 'android.arch.lifecycle:extensions:1.1.1' // latest.release
        annotationProcessor 'android.arch.lifecycle:compiler:1.1.1'
        implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
        implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    
    

简单使用

在2个数据仓库获取数据,在ViewModel层处理然后显示在View层,特别简单的Demo。

尽管框架中使用的Kotlin编写,这里方便大家理解仍然使用Java,令人欣喜的是Java调用Kotlin也是一样的顺畅。

编写XML

布局中仅有一个TextView,用于显示文案。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <data class=".MainActivityBinding"></data>

    <android.support.constraint.ConstraintLayout

            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">

        <TextView
                android:id="@+id/text_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>

    </android.support.constraint.ConstraintLayout>
</layout>

编写First数据仓库

在first数据仓库中仅仅有一个获取框架名称的方法。
注意:该类需要使用@Repository进行注解。

@Repository
public class FirstDataRepository extends DataRepository 

    public FirstDataRepository(@NotNull CompositeDisposable subscriptions) 
        super(subscriptions);
    

    public String getFrameName() 
        return "Android-MVVMEasy";
    

编写Second数据仓库

在数据仓库中仅仅有一个获取框架描述的方法。
注意:该类需要使用@Repository进行注解。

@Repository
public class SecondDataRepository extends DataRepository 

    public SecondDataRepository(@NotNull CompositeDisposable subscriptions) 
        super(subscriptions);
    

    public String getFrameDesc() 
        return "An elegant way to use MVVM in Android.";
    

编写ViewMode

ViewModel中对FirstDataRepositorySecondDataRepository实例化。
注意
1. 该类需要使用@ViewModel注解;
2. 成员变量使用@Autowired注解。

@ViewModel
public class MainViewModel extends BaseViewModel 

    @Autowired
    private FirstDataRepository mFirstRepository;
    @Autowired
    private SecondDataRepository mSecondRepository;

    public String getText() 
        return mFirstRepository.getFrameName() + ": " + mSecondRepository.getFrameDesc();
    

编写Activity

Activity中对MainActivityBindingMainViewModel实例化。
注意:成员变量使用@Autowired注解。

public class MainActivity extends BaseActivity 

    @Autowired
    private MainActivityBinding mBinding;
    @Autowired
    private MainViewModel mViewModel;

    @Override
    protected void onResume() 
        super.onResume();
        String text = mViewModel.getText();
        mBinding.textView.setText(text);
    

至此,最简单的Demo就完成了,只是使用了几个注解就完成了各层之间的关系管理及初始化。当然View层可以方便地使用多个ViewModelViewModel层可以方便地使用多个DataRepository。既可以省去了复杂的模板代码,又可以防止搭档把代码写错层次。

以上是关于优雅地处理MVVM中各层次关系的主要内容,如果未能解决你的问题,请参考以下文章

MATLAB如何更优雅的统计集合中各元素出现的次数?

MVI 架构封装:快速优雅地实现网络请求

MVI 架构封装:快速优雅地实现网络请求

OSI模型中各层次对等通信

Android binder流程简图

SpringBoot:如何优雅地处理全局异常?