1. 程式人生 > >優雅地處理MVVM中各層次關係

優雅地處理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。既可以省去了複雜的模板程式碼,又可以防止搭檔把程式碼寫錯層次。