注意,本文介紹的架構基於Dagger二、DataBinding以及Android架構組件,若是對這些不熟悉,建議先去簡單瞭解一下再來看此文章,以避免浪費你的時間。java
先上地址,你的star是我分享的動力android
本文主要記錄此架構搭建的思路以及過程當中遇到的問題,再到問題的解決,但願給一樣在研究MVVM的朋友一點幫助,也但願把本身的思路講述出來,聽取一下你們的建議,共同進步。git
MVVM和MVP的區別主要在於把P層換成了VM層,MVP中業務邏輯寫在P層,P層持有M層和V層的引用,經過接口主動調用兩個層的方法。而MVVM中,業務邏輯依然在VM層,不過它只持有M層的引用,不可持有V層的引用(會內存泄漏),而數據到視圖的轉換使用DataBinding來自動完成。相同的,兩種架構的V層都持有「業務邏輯層」(P或VM)的引用。對MVVM就說這麼多,還不清楚的請先去了解一下MVVM。github
下面看項目,主要看mvvmarch這個module,以下圖bash
其中di(依賴注入)下面目前只定義了兩個Dagger的scope,PerActivity和PerFragment,沒必要多說。架構
下面介紹mvvm下的類和接口。首先IModel、IView、IViewModel這三個接口,它們並未定義任何方法,是空接口,主要做用就是單純地標記M、V、VM三個層,在它們的基礎上進一步細化咱們的要求,從而細化出了IArchModel、IArchView、ArchViewModel。app
那咱們有什麼要求呢?mvvm
M層:暫時沒想到什麼要求,因此IArchModel接口繼承自IModel,也沒有添加新的方法。ide
VM層:首先它是VM層,要持有M層的引用;而後我但願能使用架構組件裏的ViewModel(爲何?由於我看到它名字的第一時間想到的就是MVVM裏的ViewModel層啊!開玩笑的,主要由於它解決了屏幕旋轉時Activity重建,數據保存的問題),因此ArchViewModel要繼承自ViewModel;另外我還但願引入架構組件裏的LifeCycle,讓VM層能感知到V層的生命週期,因此ArchViewModel要實現LifecycleObserver接口。佈局
最終ArchViewModel是這樣的
public abstract class ArchViewModel<M extends IArchModel> extends ViewModel implements IViewModel, LifecycleObserver {
@Inject
protected M mModel;
}
複製代碼
V層:在Android中V層主要就是Activity和Fragment,IArchView接口中的方法我並非一開始就肯定好要有哪些了,而是在編寫ArchActivity和ArchFragment時,把裏面公有的方法提出來而有的一個接口(待會兒來分析ArchActivity)。能夠肯定的是V層要持有VM的引用,因此有個VM的泛型;要使用DataBinding,因此要有一個對應的B(ViewDataBinding)泛型;最後,要使用Dagger來注入,因此有一個C(Component)的泛型。
下面看一下ArchActivity
public abstract class ArchActivity<B extends ViewDataBinding, VM extends ArchViewModel, C> extends AppCompatActivity implements IArchView<B, VM, C> {
protected B mBinding;
@Inject
protected VM mViewModel;
/**
* 在子類的buildComponent方法中實例化
*/
protected C mComponent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//data binding
mBinding = DataBindingUtil.setContentView(this, getLayoutId());
//要使用livedata,須要設置lifecycle owner
mBinding.setLifecycleOwner(this);
//build dagger component
mComponent = buildComponent();
//用mComponent執行inject操做
executeInject(mComponent); //此方法執行後mViewModel已經有值,但此時只是簡單地經過構造器注入的,下面的代碼會以標準的factory方式實現
//獲取mViewModel
mViewModel = ViewModelProviders.of(this, new ViewModelInstanceFactory<VM>(mViewModel)).get(getViewModelClazz());
//爲lifecycle添加observer,viewmodel已經實現了LifecycleObserver接口
this.getLifecycle().addObserver(mViewModel);
}
@Override
public C getComponent() {
return mComponent;
}
@Override
public Class<? extends VM> getViewModelClazz() {
//注意!!默認經過反射獲取ViewModel的class,若是對穩定性與性能有要求,請在子類中重寫此方法,返回viewmodel的class
return (Class<? extends VM>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
}
複製代碼
ArchFragment中也是相似的代碼,databinding須要用佈局id,因此IArchView中定義一個getLayoutId方法;Dagger注入部分,我把Component的build和inject分開了,向外提供getComponent方法,主要是考慮這樣一種狀況,一個Activity中有一個Fragment,可能Fragment的Component依賴外層Activity的Component,那就能夠在Fragment中getActivity().getComponent()方便地獲取;ViewModel這裏先不說,我會在下邊詳細介紹。
採用的這篇文章(Dagger2實戰(詳細))中介紹的第二種依賴方式
參考UserComponent
@PerActivity
@Component(modules = arrayOf(UserModule::class,CommonActivityModule::class), dependencies = arrayOf(AppComponent::class))
interface UserComponent {
fun inject(activity: UserActivity)
}
複製代碼
AppComponent在自定義的Application中初始化,提供全局的實例,如gson、application等。CommonActivityModule在BaseActivity中實例化,提供Activity中通用的實例,如RxPermissions等。UserComponent依賴AppComponent,modules爲UserModule(提供一些useractivity獨有的東西)和CommonActivityModule(提供Activity通用的東西)。建議不懂的看一下上邊那篇文章。
在寫如何注入ViewModel時我參考了一些mvp項目中注入Presenter的方式,通常都是用構造器注入。可是ViewModel是這樣建立的 MyViewModel model = ViewModelProviders.of(this,factory).get(MyViewModel.class);
,若是直接用構造器注入,那它就只是一個普通的對象,不擁有ViewModel的全部特性。ViewModel的源碼很簡單,核心就是用了一個沒有視圖的HolderFragment來存放ViewModel,get的時候根據Class去找對應的ViewModel,若是有就返回,沒有就用factory的create方法建立一個,存到holderfragment中,並返回。而它之因此能在屏幕旋轉Activity重建時倖存下來,是由於HolderFragment調用了setRetainInstance(true),也就是無論你屏幕旋轉多少次,獲得的holderfragment都是同一個,獲得的某個class對應的ViewModel也是同一個。若是但願使用ViewModel的有參構造器,一般是把參數傳給factory,在factory的create方法中用這些參數return new MyViewModel(xx,xxx)(能夠看下這個)。
個人方案,先用構造器的方式注入,和mvp的presenter同樣。而後使用這樣一個ViewModelInstanceFactory,在factory的構造器中傳入mViewModel,此時的mViewModel只是經過Dagger注入了,但這時還不具備ViewModel的特性,還沒存到holderfragment中。在create方法中把mViewModel又原封不動地返回來了。但這時mViewModel就已經被存到holderFragment中了,是一個真正的ViewModel了。後邊若是手機屏幕旋轉,Activity重建,Dagger會從新注入,在mViewModel = ViewModelProviders.of(this, new ViewModelInstanceFactory<VM>(mViewModel)).get(getViewModelClazz());
這句代碼以前mViewModel是一個經過構造器建立的新ViewModel,可是執行這句代碼以後,返回的仍是以前第一次的mViewModel,由於這時候從holderfragment根據class找的話是有以前viewmodel的實例的,就直接返回以前的了。
public class ViewModelInstanceFactory<VM extends ArchViewModel> extends ViewModelProvider.NewInstanceFactory {
private VM mViewModel;
public ViewModelInstanceFactory(VM mViewModel) {
this.mViewModel = mViewModel;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) mViewModel;
}
}
複製代碼
若是是簡單的不涉及業務邏輯的跳轉,能夠在View層直接Intent跳轉。可是不少狀況下跳轉時是依賴於業務邏輯的,好比說跳轉頁面時要攜帶一些業務數據,這時候就須要在ViewModel層進行頁面跳轉了,正如咱們以前所說,ViewModel不能夠持有View的引用,那怎麼經過context跳轉頁面呢?
若是隻是跳轉,不涉及startActivityForResult能夠採用Arouter,雖然沒用過,但看了一下使用說明,它是不須要activity或context做爲參數的。但涉及到forResult的,仍是須要一個Activity參數,那怎麼解決呢?首先你固然能夠選用EventBus或RxBus的方式,其次,若是我非要使用Activity呢?接下來會講如何在ViewModel中獲取Activity。
幾乎全部介紹架構組件ViewModel的文章都告訴你不能引用Activity,但卻沒告訴你在你非用Activity不可的狀況該怎麼辦。這裏提供兩種方法,推薦第二種
HolderFragment.holderFragmentFor(activity)
,能夠在CommonActivityModule中寫一個provideHolderFragment方法提供holderFragment,在ViewModel中用@Inject注入就能夠了。這種方式我在網上沒找到先例,目前暫時沒想到會有什麼潛在的問題,若是你發現什麼問題的話,歡迎評論告訴我。好比咱們在VM層startActivityForResult,該怎麼處理獲取到的結果呢?首先咱們知道V層是持有VM層的引用的,咱們固然能夠在Activity的onActivityResult方法中調用VM的public方法,可是爲了保證V層擁有最少的業務邏輯,能夠看下我這篇文章如何避免使用onActivityResult,以提升代碼可讀性,採用的也是相似holderFragment的方式,讓fragment去startForResult。請求權限能夠用RxPermissions的方式,也是這種原理。若是萬不得已的話,也能夠直接調用VM的public的方法,可是推薦不要濫用。
MVVM通常經過數據來驅動UI(固然也能夠作到雙向),例子中使用的是LoadSir,固然你也可使用其餘的,只是在我收藏的此類庫中只有三個(另兩個是MultiStateView、MultipleStatusView),我就隨意選了個star數多點的,若是你有更好的,歡迎推薦給我。
loadsir的話是用的MutableLiveData<Class<*>> state,而後在Activity中
val loadService = LoadSir.getDefault().register(this)
mViewModel.state.observe(this@UserActivity,object :Observer<Class<*>>{
override fun onChanged(t: Class<*>?) {
loadService.showCallback(t)
}
})
複製代碼
固然不一樣的庫可能須要使用的方式不一樣,loadsir用的livedata,而有些庫是使用xml的方式,能夠經過DataBinding的BindingAdapter或BindingMethod。
能夠參考demo,在自定義的Application中初始化AppComponent,按照BaseActivity的方式封裝你本身的基類,demo中的BaseActivity只簡單地初始化了CommonActivityModule(注意要在super.onCreate以前),你能夠按照本身的習慣再進行其餘的封裝。固然不止Activity,你還能夠爲M層和VM層建立基類。
而後參照UserComponent、UserModule、UserModel、UserViewModel、UserActivity的方式編寫你的應用。
原本打算可以在實際項目中使用過完善以後再發出來的,可是想了想短期內可能沒有機會了,就先發出來這個半成品吧!後續若是有改動或補充,我會直接在此文章中更新,因此有興趣的話能夠收藏一下。
最後仍是要說,使用以前但願你對Dagger二、DataBinding和架構組件有所瞭解。