優雅地處理MVVM中各層次關係
轉載請標明出處:
https://blog.csdn.net/xuehuayous/article/details/84255731;
本文出自:【Kevin.zhou的部落格】
前言:相信大家對MVVM架構都有過一定的瞭解,如果不太瞭解的朋友可以看下我之前寫的《Android中MVVM是什麼?》。整體分為View、ViewModel、Model三層,View層處理使用者互動,ViewModel層進行業務處理;Model層進行資料處理。那麼如何進行優雅地處理他們之間的關係呢?
分層關係
首先來看下它們之間的關係,如下圖所示:
簡圖如下:
通過以上兩圖我們可以看出:
- View持有ViewModel引用;
- ViewModel持有Model引用;
- View與ViewModel存在一對多關係;
- 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
中的Fragment
與Activity
使用相同的ViewModel
。
ViewModel生命週期
有朋友不僅要問,ViewModel
生命週期是什麼鬼,由於我們定義View層
僅僅處理使用者互動,向用戶展示、響應使用者的事件,ViewModel層
進行業務邏輯處理,需要和Activity
生命週期繫結的業務處理就必須移到ViewModel層
進行處理,所以ViewModel層
也需要和View層
相同的生命週期。
還好Google在Android架構元件中已經想到了這個問題,可以通過新增LifecycleObserver
的方式方便實現。
由於使用Java
和Kotlin
都實現過,這裡都貼出了,還沒有入門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());
}
}
實現分析
通過以上原生實現可以發現,維護各層之間關係的基本都是模板程式碼,寫起來繁瑣,但是又沒有技術含量,能不能讓編輯器去自動生成呢?
我們想實現如下:
- 在
DataRepository
使用@Repository
標註這是一個數據倉庫;@Repository public class MainDataRepository { }
- 在
ViewModel
上使用@ViewModel
標註這是一個ViewModel
,如果DataRepository
的欄位上使用了@Autowired
標註,則自動例項化物件;@ViewModel public class MainViewModel extends BaseViewModel { @Autowired MainDataRepository mDataRepository; }
- 在
Activity
和Fragment
中對binding
及ViewModel
進行@Autowired
標註,也自動例項化。public class MainActivity extends BaseActivity { @Autowired private MainActivityBinding mBinding; @Autowired private MainViewModel mViewModel; }
這樣是不是有對MVVM
沒有那麼抗拒了,是不是有朋友已經拍手叫好,抓緊去實現吧,別逼逼了。
那麼怎麼實現呢?首先想到的是apt,我們熟識的ButterKnife
、Dagger
、EventBus
等都是通過這種方式實現的,思考良久我們會發現,使用apt很難優雅地實現。那能不能動態修改位元組碼檔案呢?這是一個很好的方式,只是實現成本比較大,那不要緊,實現成本大,實現之後就一勞永逸了。考察了一圈動態修改位元組碼的框架,最終選擇了ASM
,為啥是ASM
以及怎麼實現的,應該是一個特別長的篇幅。這裡直接把最終程式碼奉上。
如何使用
引入依賴
-
專案
build.gradle
新增dependenciesdependencies { // ... ... classpath 'com.kevin:mvvm-plugin:latest.release' }
-
模組
build.gradle
新增元件apply plugin: 'mvvm'
-
模組build.gradle開啟dataBinding
dataBinding { enabled true }
-
模組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
中對FirstDataRepository
和SecondDataRepository
例項化。
注意:
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
中對MainActivityBinding
和MainViewModel
例項化。
注意:成員變數使用@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層
可以方便地使用多個ViewModel
,ViewModel層
可以方便地使用多個DataRepository
。既可以省去了複雜的模板程式碼,又可以防止搭檔把程式碼寫錯層次。